summaryrefslogtreecommitdiffstats
path: root/shlibs/mount/src/tab_parse.c
diff options
context:
space:
mode:
authorKarel Zak2010-01-13 22:08:16 +0100
committerKarel Zak2010-06-03 15:20:11 +0200
commit6bd8b7a79dffb532ae8639e563f75005a10bc41d (patch)
tree470fe4db5d45f28e2f5c49db4388d315f39e5b61 /shlibs/mount/src/tab_parse.c
parentlibmount: rename mnt_optls_iterate_options() (diff)
downloadkernel-qcow2-util-linux-6bd8b7a79dffb532ae8639e563f75005a10bc41d.tar.gz
kernel-qcow2-util-linux-6bd8b7a79dffb532ae8639e563f75005a10bc41d.tar.xz
kernel-qcow2-util-linux-6bd8b7a79dffb532ae8639e563f75005a10bc41d.zip
libmount: add fstab/mtab/mountinfo parsing routines
Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'shlibs/mount/src/tab_parse.c')
-rw-r--r--shlibs/mount/src/tab_parse.c593
1 files changed, 593 insertions, 0 deletions
diff --git a/shlibs/mount/src/tab_parse.c b/shlibs/mount/src/tab_parse.c
new file mode 100644
index 000000000..feaab74af
--- /dev/null
+++ b/shlibs/mount/src/tab_parse.c
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <limits.h>
+
+#include "nls.h"
+#include "mountP.h"
+
+static inline char *skip_spaces(char *s)
+{
+ assert(s);
+
+ while (*s == ' ' || *s == '\t')
+ s++;
+ return s;
+}
+
+static inline char *skip_nonspaces(char *s)
+{
+ assert(s);
+
+ while (*s && !(*s == ' ' || *s == '\t'))
+ s++;
+ return s;
+}
+
+#define isoctal(a) (((a) & ~7) == '0')
+
+/* returns malloced pointer - no more strdup required */
+static void unmangle(char *s, char *buf, size_t len)
+{
+ size_t sz = 0;
+ assert(s);
+
+ while(*s && sz < len - 1) {
+ if (*s == '\\' && sz + 4 < len - 1 && isoctal(s[1]) &&
+ isoctal(s[2]) && isoctal(s[3])) {
+
+ *buf++ = 64*(s[1] & 7) + 8*(s[2] & 7) + (s[3] & 7);
+ s += 4;
+ sz += 4;
+ } else {
+ *buf++ = *s++;
+ sz++;
+ }
+ }
+ *buf = '\0';
+}
+
+static size_t next_word_size(char *s, char **start, char **end)
+{
+ char *e;
+
+ assert(s);
+
+ s = skip_spaces(s);
+ if (!*s)
+ return 0;
+ e = skip_nonspaces(s);
+
+ if (start)
+ *start = s;
+ if (end)
+ *end = e;
+
+ return e - s;
+}
+
+static char *next_word(char **s)
+{
+ size_t sz;
+ char *res, *end;
+
+ assert(s);
+
+ sz = next_word_size(*s, s, &end) + 1;
+ if (sz == 1)
+ return NULL;
+
+ res = malloc(sz);
+ if (!res)
+ return NULL;
+
+ unmangle(*s, res, sz);
+ *s = end + 1;
+ return res;
+}
+
+static int next_word_skip(char **s)
+{
+ *s = skip_spaces(*s);
+ if (!**s)
+ return 1;
+ *s = skip_nonspaces(*s);
+ return 0;
+}
+
+static int next_number(char **s, int *num)
+{
+ char *end = NULL;
+
+ assert(num);
+ assert(s);
+
+ *s = skip_spaces(*s);
+ if (!**s)
+ return -1;
+ *num = strtol(*s, &end, 10);
+ if (end == NULL || *s == end)
+ return -1;
+
+ *s = end;
+
+ /* valid end of number is space or terminator */
+ if (*end == ' ' || *end == '\t' || *end == '\0')
+ return 0;
+ return -1;
+}
+
+/*
+ * Parses one line from {fs,m}tab
+ */
+static int mnt_tab_parse_file_line(mnt_fs *fs, char *s)
+{
+ /* SOURCE */
+ if (__mnt_fs_set_source(fs, next_word(&s)) != 0)
+ return 1;
+
+ /* TARGET */
+ fs->target = next_word(&s);
+ if (!fs->target)
+ return 1;
+
+ /* TYPE */
+ if (__mnt_fs_set_fstype(fs, next_word(&s)) != 0)
+ return 1;
+
+ /* OPTS */
+ fs->optstr = next_word(&s);
+ if (!fs->optstr)
+ return 1;
+ /* default */
+ fs->passno = fs->freq = 0;
+
+ /* FREQ (optional) */
+ if (next_number(&s, &fs->freq) != 0) {
+ if (*s)
+ return 1;
+
+ /* PASSNO (optional) */
+ } else if (next_number(&s, &fs->passno) != 0 && *s)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Parses one line from mountinfo file
+ */
+static int mnt_parse_mountinfo_line(mnt_fs *fs, char *s)
+{
+ /* ID */
+ if (next_number(&s, &fs->id) != 0)
+ return 1;
+
+ /* PARENT */
+ if (next_number(&s, &fs->parent) != 0)
+ return 1;
+
+ /* <maj>:<min> (ignore) */
+ if (next_word_skip(&s) != 0)
+ return 1;
+
+ /* MOUNTROOT */
+ fs->mntroot = next_word(&s);
+ if (!fs->mntroot)
+ return 1;
+
+ /* TARGET (mountpoit) */
+ fs->target = next_word(&s);
+ if (!fs->target)
+ return 1;
+
+ /* OPTIONS (fs-independent) */
+ fs->vfs_optstr = next_word(&s);
+ if (!fs->vfs_optstr)
+ return 1;
+
+ /* optional fields (ignore) */
+ do {
+ s = skip_spaces(s);
+ if (s && *s == '-' &&
+ (*(s + 1) == ' ' || *(s + 1) == '\t')) {
+ s++;
+ break;
+ }
+ if (s && next_word_skip(&s) != 0)
+ return 1;
+ } while (s);
+
+ /* FSTYPE */
+ if (__mnt_fs_set_fstype(fs, next_word(&s)) != 0)
+ return 1;
+
+ /* SOURCE or "none" */
+ if (__mnt_fs_set_source(fs, next_word(&s)) != 0)
+ return 1;
+
+ /* OPTIONS (fs-dependent) */
+ fs->fs_optstr = next_word(&s);
+ if (!fs->fs_optstr)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Returns {m,fs}tab or mountinfo file format (MNT_FMT_*)
+ *
+ * The "mountinfo" format is always: "<number> <number> ... "
+ */
+static int detect_fmt(char *line)
+{
+ int num;
+
+ /* ID */
+ if (next_number(&line, &num) != 0)
+ return MNT_FMT_FSTAB;
+
+ /* PARENT */
+ if (next_number(&line, &num) != 0)
+ return MNT_FMT_FSTAB;
+
+ return MNT_FMT_MOUNTINFO;
+}
+
+
+/*
+ * Merges @vfs and @fs options strings into a new string
+ * This function skips the generic part of @fs options.
+ * For example (see "rw"):
+ *
+ * mnt_merge_optstr("rw,noexec", "rw,journal=update")
+ *
+ * returns --> "rw,noexec,journal=update"
+ *
+ * We need this function for /proc/self/mountinfo parsing.
+ */
+static char *merge_optstr(const char *vfs, const char *fs)
+{
+ const char *p1 = vfs, *p2 = fs;
+ char *res;
+ size_t sz;
+
+ if (!vfs && !fs)
+ return NULL;
+ if (!vfs || !fs)
+ return strdup(fs ? fs : vfs);
+ if (!strcmp(vfs, fs))
+ return strdup(vfs); /* e.g. "aaa" and "aaa" */
+
+ /* skip the same FS options */
+ while (*p1 && *p2 && *++p1 == *++p2);
+
+ if (*p1 == ',')
+ p1++;
+ if (*p2 == ',')
+ p2++;
+ if (!*p1 && !*p2) /* e.g. "aaa,bbb" and "aaa,bbb," */
+ return strdup(vfs);
+ if (!*p1 || !*p2) /* e.g. "aaa" and "aaa,bbb" */
+ return strdup(*p1 ? vfs : fs);
+
+ p1 = vfs;
+ sz = strlen(p1) + strlen(p2) + 2; /* 2= separator + '\0' */
+
+ res = malloc(sz);
+ if (!res)
+ return NULL;
+
+ snprintf(res, sz, "%s,%s", p1, p2);
+ return res;
+}
+
+/*
+ * Read and parse the next line from {fs,m}tab or mountinfo
+ */
+static int mnt_tab_parse_next(mnt_tab *tb, FILE *f, mnt_fs *fs)
+{
+ char buf[BUFSIZ];
+ char *s;
+
+ assert(tb);
+ assert(f);
+ assert(fs);
+
+ /* read the next non-blank non-comment line */
+ do {
+ if (fgets(buf, sizeof(buf), f) == NULL)
+ return -1;
+ tb->nlines++;
+ s = index (buf, '\n');
+ if (!s) {
+ /* Missing final newline? Otherwise extremely */
+ /* long line - assume file was corrupted */
+ if (feof(f)) {
+ DBG(DEBUG_TAB, fprintf(stderr,
+ "libmount: WARNING: no final newline at the end of %s\n",
+ tb->filename));
+ s = index (buf, '\0');
+ } else {
+ DBG(DEBUG_TAB, fprintf(stderr,
+ "libmount: %s: %d: missing newline at line\n",
+ tb->filename, tb->nlines));
+ goto err;
+ }
+ }
+ *s = '\0';
+ if (--s >= buf && *s == '\r')
+ *s = '\0';
+ s = skip_spaces(buf);
+ } while (*s == '\0' || *s == '#');
+
+ DBG(DEBUG_TAB, fprintf(stderr, "libmount: %s:%d: %s\n",
+ tb->filename, tb->nlines, s));
+
+ if (!tb->fmt)
+ tb->fmt = detect_fmt(s);
+
+ if (tb->fmt == MNT_FMT_FSTAB) {
+ /* parse /etc/{fs,m}tab */
+ if (mnt_tab_parse_file_line(fs, s) != 0)
+ goto err;
+ } else if (tb->fmt == MNT_FMT_MOUNTINFO) {
+ /* parse /proc/self/mountinfo */
+ if (mnt_parse_mountinfo_line(fs, s) != 0)
+ goto err;
+ }
+
+ /* merge fs_optstr and vfs_optstr into optstr (necessary for "mountinfo") */
+ if (!fs->optstr && (fs->vfs_optstr || fs->fs_optstr)) {
+ fs->optstr = merge_optstr(fs->vfs_optstr, fs->fs_optstr);
+ if (!fs->optstr)
+ goto err;
+ }
+
+ fs->lineno = tb->nlines;
+
+ DBG(DEBUG_TAB, fprintf(stderr,
+ "libmount: %s: %d: SOURCE:%s, MNTPOINT:%s, TYPE:%s, "
+ "OPTS:%s, FREQ:%d, PASSNO:%d\n",
+ tb->filename, fs->lineno,
+ fs->source, fs->target, fs->fstype,
+ fs->optstr, fs->freq, fs->passno));
+
+ return 0;
+err:
+ /* we don't report parse errors to caller; caller has to check
+ * errors by mnt_tab_get_nerrs() or internaly by MNT_ENTRY_ERR flag
+ */
+ fs->lineno = tb->nlines;
+ fs->flags |= MNT_FS_ERROR;
+ return 0;
+}
+
+/**
+ * mnt_tab_parse_file:
+ * @tb: tab pointer
+ *
+ * Parses whole table (e.g. /etc/fstab).
+ *
+ * Returns 0 on success and -1 in case of error. The parse errors is possible
+ * to detect by mnt_tab_get_nerrs() and error message is possible to create by
+ * mnt_tab_strerror().
+ *
+ * Example:
+ *
+ * mnt_tab *tb = mnt_new_tab("/etc/fstab");
+ * int rc;
+ *
+ * rc = mnt_tab_parse_file(tb);
+ * if (rc) {
+ * if (mnt_tab_get_nerrs(tb)) { / * parse error * /
+ * mnt_tab_strerror(tb, buf, sizeof(buf));
+ * fprintf(stderr, "%s: %s\n", progname, buf);
+ * } else
+ * perror(mnt_tab_get_name(tb)); / * system error * /
+ * } else
+ * mnt_fprintf_tab(tb, stdout, NULL);
+ *
+ * mnt_free_tab(tb);
+ */
+int mnt_tab_parse_file(mnt_tab *tb)
+{
+ FILE *f;
+
+ assert(tb);
+ assert(tb->filename);
+
+ if (!tb->filename)
+ return -1;
+
+ f = fopen(tb->filename, "r");
+ if (!f)
+ return -1;
+
+ while (!feof(f)) {
+ int rc;
+ mnt_fs *fs = mnt_new_fs();
+ if (!fs)
+ goto error;
+
+ rc = mnt_tab_parse_next(tb, f, fs);
+ if (!rc)
+ rc = mnt_tab_add_fs(tb, fs);
+ else if (feof(f)) {
+ mnt_free_fs(fs);
+ break;
+ }
+ if (rc) {
+ mnt_free_fs(fs);
+ goto error;
+ }
+ }
+
+ fclose(f);
+ return 0;
+error:
+ fclose(f);
+ return -1;
+}
+
+/**
+ * mnt_new_tab_parse:
+ * @filename: /etc/{m,fs}tab or /proc/self/mountinfo path
+ *
+ * Same as mnt_new_tab() + mnt_tab_parse_file(). Note that this function does
+ * not provide details (by mnt_tab_strerror()) about failed parsing -- so you
+ * should not to use this function for user-writeable files like /etc/fstab.
+ *
+ * Returns newly allocated tab on success and NULL in case of error.
+ */
+mnt_tab *mnt_new_tab_from_file(const char *filename)
+{
+ mnt_tab *tb;
+
+ assert(filename);
+
+ if (!filename)
+ return NULL;
+ tb = mnt_new_tab(filename);
+ if (tb && mnt_tab_parse_file(tb) != 0) {
+ mnt_free_tab(tb);
+ tb = NULL;
+ }
+ return tb;
+}
+
+/**
+ * mnt_tab_get_nerrs:
+ * @tb: pointer to table
+ *
+ * Returns number of broken (parse error) entries in the table.
+ */
+int mnt_tab_get_nerrs(mnt_tab *tb)
+{
+ assert(tb);
+ return tb->nerrs;
+}
+
+/**
+ * mnt_tab_strerror:
+ * @tb: pointer to table
+ * @buf: buffer to return error message
+ * @buflen: lenght of the buf
+ *
+ * Returns error message for table (file) parse errors. For example:
+ *
+ * "/etc/fstab: parse error at line(s): 1, 2 and 3."
+ */
+char *mnt_tab_strerror(mnt_tab *tb, char *buf, size_t buflen)
+{
+ struct list_head *p;
+ int last = -1;
+ char *b = buf;
+ char *end = buf + buflen - 1;
+
+ assert(tb);
+ assert(buf);
+ assert(buflen);
+
+ if (!tb || !tb->nerrs || !buf || buflen <=0)
+ return NULL;
+
+ if (tb->filename) {
+ snprintf(b, end - b, "%s: ", tb->filename);
+ b += strnlen(b, end - b);
+ }
+
+ if (tb->nerrs > 1)
+ strncpy(b, _("parse error at lines: "), end - b);
+ else
+ strncpy(b, _("parse error at line: "), end - b);
+ b += strnlen(b, end - b);
+ *b = '\0';
+
+ list_for_each(p, &tb->ents) {
+ mnt_fs *fs = list_entry(p, mnt_fs, ents);
+ if (b == end)
+ goto done;
+ if (fs->flags & MNT_FS_ERROR) {
+ if (last != -1) {
+ snprintf(b, end - b, "%d, ", last);
+ b += strnlen(b, end - b);
+ }
+ last = fs->lineno;
+ }
+ }
+
+ if (tb->nerrs == 1)
+ snprintf(b, end - b, "%d.", last);
+ else
+ snprintf(b - 1, end - b, _(" and %d."), last);
+done:
+ return buf;
+}
+
+#ifdef LIBMOUNT_TEST_PROGRAM
+int test_parse(struct mtest *ts, int argc, char *argv[])
+{
+ mnt_tab *tb;
+ mnt_fs *fs;
+ mnt_iter *itr;
+
+ if (argc != 2)
+ goto err;
+
+ tb = mnt_new_tab(argv[1]);
+ if (!tb)
+ goto err;
+ if (mnt_tab_parse_file(tb) != 0)
+ goto err;
+ if (mnt_tab_get_nerrs(tb)) {
+ char buf[BUFSIZ];
+
+ mnt_tab_strerror(tb, buf, sizeof(buf));
+ printf("\t%s\n", buf);
+ goto err;
+ }
+
+ itr = mnt_new_iter(MNT_ITER_FORWARD);
+ if (!itr)
+ goto err;
+ while(mnt_tab_next_fs(tb, itr, &fs) == 0) {
+ const char *tg, *vl;
+
+ if (mnt_fs_get_tag(fs, &tg, &vl) == 0)
+ printf("%s=%s", tg, vl);
+ else
+ printf("%s", mnt_fs_get_srcpath(fs));
+
+ printf("|%s|%s|%s|%d|%d|\n",
+ mnt_fs_get_target(fs),
+ mnt_fs_get_fstype(fs),
+ mnt_fs_get_optstr(fs),
+ mnt_fs_get_freq(fs),
+ mnt_fs_get_passno(fs));
+ }
+ mnt_free_tab(tb);
+ mnt_free_iter(itr);
+
+ return 0;
+err:
+ return -1;
+}
+
+int main(int argc, char *argv[])
+{
+ struct mtest tss[] = {
+ { "--parse", test_parse, "<file> parse the {fs,m}tab or mountinfo file" },
+ { NULL }
+ };
+ return mnt_run_test(tss, argc, argv);
+}
+
+#endif /* LIBMOUNT_TEST_PROGRAM */