From 6bd8b7a79dffb532ae8639e563f75005a10bc41d Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Wed, 13 Jan 2010 22:08:16 +0100 Subject: libmount: add fstab/mtab/mountinfo parsing routines Signed-off-by: Karel Zak --- shlibs/mount/src/tab_parse.c | 593 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 593 insertions(+) create mode 100644 shlibs/mount/src/tab_parse.c (limited to 'shlibs/mount/src/tab_parse.c') 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 + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#include +#include +#include +#include + +#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; + + /* : (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: " ... " + */ +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, " parse the {fs,m}tab or mountinfo file" }, + { NULL } + }; + return mnt_run_test(tss, argc, argv); +} + +#endif /* LIBMOUNT_TEST_PROGRAM */ -- cgit v1.2.3-55-g7522