--- /dev/null
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Note:
+ * mnt_tab_find_* functions are mount(8) compatible. It means it tries
+ * to found an entry in more iterations where the first attempt is always
+ * based on comparison with unmodified (non-canonicalized or un-evaluated)
+ * paths or tags. For example fstab with two entries:
+ *
+ * LABEL=foo /foo auto rw
+ * /dev/foo /foo auto rw
+ *
+ * where both lines are used for the *same* device, then
+ *
+ * mnt_tab_find_source(tb, "/dev/foo", &fs);
+ *
+ * will returns the second line, and
+ *
+ * mnt_tab_find_source(tb, "LABEL=foo", &fs);
+ *
+ * will returns the first entry, and
+ *
+ * mnt_tab_find_source(tb, "UUID=<anyuuid>", &fs);
+ *
+ * will returns the first entry (if UUID matches with the device).
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <blkid/blkid.h>
+
+#include "nls.h"
+#include "mountP.h"
+
+/**
+ * mnt_new_tab:
+ * @filename: file name or NULL
+ *
+ * The tab is a container for mnt_fs entries that usually represents a fstab,
+ * mtab or mountinfo file from your system.
+ *
+ * Note that this function does not parse the file. See also
+ * mnt_tab_parse_file().
+ *
+ * Returns newly allocated tab struct.
+ */
+mnt_tab *mnt_new_tab(const char *filename)
+{
+ mnt_tab *tb = NULL;
+
+ tb = calloc(1, sizeof(struct _mnt_tab));
+ if (!tb)
+ goto err;
+
+ if (filename) {
+ tb->filename = strdup(filename);
+ if (!tb->filename)
+ goto err;
+ }
+ INIT_LIST_HEAD(&tb->ents);
+ return tb;
+err:
+ free(tb);
+ return NULL;
+}
+
+/**
+ * mnt_free_tab:
+ * @tab: tab pointer
+ *
+ * Deallocates tab struct and all entries.
+ */
+void mnt_free_tab(mnt_tab *tb)
+{
+ if (!tb)
+ return;
+ free(tb->filename);
+
+ while (!list_empty(&tb->ents)) {
+ mnt_fs *fs = list_entry(tb->ents.next, mnt_fs, ents);
+ mnt_free_fs(fs);
+ }
+
+ free(tb);
+}
+
+/**
+ * mnt_tab_get_nents:
+ * @tb: pointer to tab
+ *
+ * Returns number of valid entries in tab.
+ */
+int mnt_tab_get_nents(mnt_tab *tb)
+{
+ assert(tb);
+ return tb ? tb->nents : 0;
+}
+
+/**
+ * mnt_tab_set_cache:
+ * @tb: pointer to tab
+ * @mpc: pointer to mnt_cache instance
+ *
+ * Setups a cache for canonicalized paths and evaluated tags (LABEL/UUID). The
+ * cache is recommended for mnt_tab_find_*() functions.
+ *
+ * The cache could be shared between more tabs. Be careful when you share the
+ * same cache between more threads -- currently the cache does not provide any
+ * locking method.
+ *
+ * See also mnt_new_cache().
+ *
+ * Returns 0 on success or -1 in case of error.
+ */
+int mnt_tab_set_cache(mnt_tab *tb, mnt_cache *mpc)
+{
+ assert(tb);
+ if (!tb)
+ return -1;
+ tb->cache = mpc;
+ return 0;
+}
+
+/**
+ * mnt_tab_get_cache:
+ * @tb: pointer to tab
+ *
+ * Returns pointer to mnt_cache instance or NULL.
+ */
+mnt_cache *mnt_tab_get_cache(mnt_tab *tb)
+{
+ assert(tb);
+ return tb ? tb->cache : NULL;
+}
+
+/**
+ * mnt_tab_get_name:
+ * @tb: tab pointer
+ *
+ * Returns tab filename or NULL.
+ */
+const char *mnt_tab_get_name(mnt_tab *tb)
+{
+ assert(tb);
+ return tb ? tb->filename : NULL;
+}
+
+/**
+ * mnt_tab_add_fs:
+ * @tb: tab pointer
+ * @fs: new entry
+ *
+ * Adds a new entry to tab.
+ *
+ * Returns 0 on success or -1 in case of error.
+ */
+int mnt_tab_add_fs(mnt_tab *tb, mnt_fs *fs)
+{
+ assert(tb);
+ assert(fs);
+
+ if (!tb || !fs)
+ return -1;
+
+ list_add_tail(&fs->ents, &tb->ents);
+
+ DBG(DEBUG_TAB, fprintf(stderr,
+ "libmount: %s: add entry: %s %s\n",
+ tb->filename, mnt_fs_get_source(fs),
+ mnt_fs_get_target(fs)));
+
+ if (fs->flags & MNT_FS_ERROR)
+ tb->nerrs++;
+ else
+ tb->nents++;
+ return 0;
+}
+
+/**
+ * mnt_tab_remove_fs:
+ * @tb: tab pointer
+ * @fs: new entry
+ *
+ * Returns 0 on success or -1 in case of error.
+ */
+int mnt_tab_remove_fs(mnt_tab *tb, mnt_fs *fs)
+{
+ assert(tb);
+ assert(fs);
+
+ if (!tb || !fs)
+ return -1;
+
+ list_del(&fs->ents);
+
+ if (fs->flags & MNT_FS_ERROR)
+ tb->nerrs--;
+ else
+ tb->nents--;
+ return 0;
+}
+
+/**
+ * mnt_tab_next_fs:
+ * @tb: tab pointer
+ * @itr: iterator
+ * @fs: returns the next tab entry
+ *
+ * Returns 0 on success, -1 in case of error or 1 at end of list.
+ *
+ * Example (list all mountpoints from fstab in backward order):
+ *
+ * mnt_fs *fs;
+ * mnt_tab *tb = mnt_new_tab("/etc/fstab");
+ * mnt_iter *itr = mnt_new_iter(MNT_ITER_BACKWARD);
+ *
+ * mnt_tab_parse_file(tb);
+ *
+ * while(mnt_tab_next_fs(tb, itr, &fs) == 0) {
+ * const char *dir = mnt_fs_get_target(fs);
+ * printf("mount point: %s\n", dir);
+ * }
+ * mnt_free_tab(fi);
+ */
+int mnt_tab_next_fs(mnt_tab *tb, mnt_iter *itr, mnt_fs **fs)
+{
+ assert(tb);
+ assert(itr);
+ assert(fs);
+
+ if (!tb || !itr || !fs)
+ return -1;
+again:
+ if (!itr->head)
+ MNT_ITER_INIT(itr, &tb->ents);
+ if (itr->p != itr->head) {
+ MNT_ITER_ITERATE(itr, *fs, struct _mnt_fs, ents);
+ return 0;
+ }
+
+ /* ignore broken entries */
+ if (*fs && ((*fs)->flags & MNT_FS_ERROR))
+ goto again;
+
+ return 1;
+}
+
+/**
+ * mnt_tab_set_iter:
+ * @tb: tab pointer
+ * @itr: iterator
+ * @fs: tab entry
+ *
+ * Sets @iter to the position of @fs in the file @tb.
+ *
+ * Returns 0 on success, -1 in case of error.
+ */
+int mnt_tab_set_iter(mnt_tab *tb, mnt_iter *itr, mnt_fs *fs)
+{
+ assert(tb);
+ assert(itr);
+ assert(fs);
+
+ if (!tb || !itr || !fs)
+ return -1;
+
+ MNT_ITER_INIT(itr, &tb->ents);
+ itr->p = &fs->ents;
+
+ return 0;
+}
+
+/**
+ * mnt_tab_find_target:
+ * @tb: tab pointer
+ * @path: mountpoint directory
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * Try to lookup an entry in given tab, possible are three iterations, first
+ * with @path, second with realpath(@path) and third with realpath(@path)
+ * against realpath(fs->target). The 2nd and 3rd iterations are not performed
+ * when @tb cache is not set (see mnt_tab_set_cache()).
+ *
+ * Returns a tab entry or NULL.
+ */
+mnt_fs *mnt_tab_find_target(mnt_tab *tb, const char *path, int direction)
+{
+ mnt_iter itr;
+ mnt_fs *fs = NULL;
+ char *cn;
+
+ assert(tb);
+ assert(path);
+
+ DBG(DEBUG_TAB, fprintf(stderr,
+ "libmount: %s: lookup target: %s\n", tb->filename, path));
+
+ /* native @target */
+ mnt_reset_iter(&itr, direction);
+ while(mnt_tab_next_fs(tb, &itr, &fs) == 0)
+ if (fs->target && strcmp(fs->target, path) == 0)
+ return fs;
+
+ if (!tb->cache || !(cn = mnt_resolve_path(path, tb->cache)))
+ return NULL;
+
+ /* canonicalized paths in mnt_tab */
+ mnt_reset_iter(&itr, direction);
+ while(mnt_tab_next_fs(tb, &itr, &fs) == 0) {
+ if (fs->target && strcmp(fs->target, cn) == 0)
+ return fs;
+ }
+
+ /* non-canonicaled path in mnt_tab */
+ mnt_reset_iter(&itr, direction);
+ while(mnt_tab_next_fs(tb, &itr, &fs) == 0) {
+ char *p;
+ if (!fs->target)
+ continue;
+ p = mnt_resolve_path(fs->target, tb->cache);
+ if (strcmp(cn, p) == 0)
+ return fs;
+ }
+ return NULL;
+}
+
+/**
+ * mnt_tab_find_srcpath:
+ * @tb: tab pointer
+ * @path: source path (devname or dirname)
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * Try to lookup an entry in given tab, possible are four iterations, first
+ * with @path, second with realpath(@path), third with tags (LABEL, UUID, ..)
+ * from @path and fourth with realpath(@path) against realpath(entry->srcpath).
+ *
+ * The 2nd, 3rd and 4th iterations are not performed when @tb cache is not
+ * set (see mnt_tab_set_cache()).
+ *
+ * Returns a tab entry or NULL.
+ */
+mnt_fs *mnt_tab_find_srcpath(mnt_tab *tb, const char *path, int direction)
+{
+ mnt_iter itr;
+ mnt_fs *fs = NULL;
+ int ntags = 0;
+ char *cn;
+ const char *p;
+
+ assert(tb);
+ assert(path);
+
+ DBG(DEBUG_TAB, fprintf(stderr,
+ "libmount: %s: lookup srcpath: %s\n", tb->filename, path));
+
+ /* native paths */
+ mnt_reset_iter(&itr, direction);
+ while(mnt_tab_next_fs(tb, &itr, &fs) == 0) {
+ p = mnt_fs_get_srcpath(fs);
+ if (p && strcmp(p, path) == 0)
+ return fs;
+ if (!p)
+ /* mnt_fs_get_srcpath() returs nothing, it's TAG */
+ ntags++;
+ }
+
+ if (!tb->cache || !(cn = mnt_resolve_path(path, tb->cache)))
+ return NULL;
+
+ /* canonicalized paths in mnt_tab */
+ if (ntags < mnt_tab_get_nents(tb)) {
+ mnt_reset_iter(&itr, direction);
+ while(mnt_tab_next_fs(tb, &itr, &fs) == 0) {
+ p = mnt_fs_get_srcpath(fs);
+ if (p && strcmp(p, cn) == 0)
+ return fs;
+ }
+ }
+
+ /* evaluated tag */
+ if (ntags && mnt_cache_read_tags(tb->cache, cn) > 0) {
+ mnt_reset_iter(&itr, direction);
+ while(mnt_tab_next_fs(tb, &itr, &fs) == 0) {
+ const char *t, *v;
+
+ if (mnt_fs_get_tag(fs, &t, &v))
+ continue;
+
+ if (mnt_cache_device_has_tag(tb->cache, cn, t, v))
+ return fs;
+ }
+ }
+
+ /* non-canonicalized paths in mnt_tab */
+ if (ntags <= mnt_tab_get_nents(tb)) {
+ mnt_reset_iter(&itr, direction);
+ while(mnt_tab_next_fs(tb, &itr, &fs) == 0) {
+ p = mnt_fs_get_srcpath(fs);
+ if (p)
+ p = mnt_resolve_path(p, tb->cache);
+ if (p && strcmp(cn, p) == 0)
+ return fs;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * mnt_tab_find_tag:
+ * @tb: tab pointer
+ * @tag: tag name (e.g "LABEL", "UUID", ...)
+ * @val: tag value
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * Try to lookup an entry in given tab, first attempt is lookup by @tag and
+ * @val, for the second attempt the tag is evaluated (converted to the device
+ * name) and mnt_tab_find_srcpath() is preformed. The second attempt is not
+ * performed when @tb cache is not set (see mnt_tab_set_cache()).
+
+ * Returns a tab entry or NULL.
+ */
+mnt_fs *mnt_tab_find_tag(mnt_tab *tb, const char *tag,
+ const char *val, int direction)
+{
+ mnt_iter itr;
+ mnt_fs *fs = NULL;
+
+ assert(tb);
+ assert(tag);
+ assert(val);
+
+ if (!tb || !tag || !val)
+ return NULL;
+
+ DBG(DEBUG_TAB, fprintf(stderr,
+ "libmount: %s: lookup by TAG: %s %s\n", tb->filename, tag, val));
+
+ /* look up by TAG */
+ mnt_reset_iter(&itr, direction);
+ while(mnt_tab_next_fs(tb, &itr, &fs) == 0) {
+ if (fs->tagname && fs->tagval &&
+ strcmp(fs->tagname, tag) == 0 &&
+ strcmp(fs->tagval, val) == 0)
+ return fs;
+ }
+
+ if (tb->cache) {
+ /* look up by device name */
+ char *cn = mnt_resolve_tag(tag, val, tb->cache);
+ if (cn)
+ return mnt_tab_find_srcpath(tb, cn, direction);
+ }
+ return NULL;
+}
+
+/**
+ * mnt_tab_find_source:
+ * @tb: tab pointer
+ * @source: TAG or path
+ *
+ * This is high-level API for mnt_tab_find_{srcpath,tag}. You needn't to care
+ * about @source format (device, LABEL, UUID, ...). This function parses @source
+ * and calls mnt_tab_find_tag() or mnt_tab_find_srcpath().
+ *
+ * Returns a tab entry or NULL.
+ */
+mnt_fs *mnt_tab_find_source(mnt_tab *tb, const char *source, int direction)
+{
+ mnt_fs *fs = NULL;
+
+ assert(tb);
+ assert(source);
+
+ if (!tb || !source)
+ return NULL;
+
+ DBG(DEBUG_TAB, fprintf(stderr,
+ "libmount: %s: lookup SOURCE: %s\n", tb->filename, source));
+
+ if (strchr(source, '=')) {
+ char *tag, *val;
+
+ if (blkid_parse_tag_string(source, &tag, &val) == 0) {
+
+ fs = mnt_tab_find_tag(tb, tag, val, direction);
+
+ free(tag);
+ free(val);
+ }
+ } else
+ fs = mnt_tab_find_srcpath(tb, source, direction);
+
+ return fs;
+}
+
+/**
+ * mnt_tab_find_pair:
+ * @tb: tab pointer
+ * @srcpath: canonicalized source path (devname or dirname)
+ * @target: canonicalized mountpoint
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * Returns a tab entry or NULL.
+ */
+mnt_fs *mnt_tab_find_pair(mnt_tab *tb, const char *srcpath,
+ const char *target, int direction)
+{
+ mnt_iter itr;
+ mnt_fs *fs;
+ int has_tags = -1;
+ const char *p;
+
+ assert(tb);
+ assert(srcpath);
+ assert(target);
+
+ DBG(DEBUG_TAB, fprintf(stderr,
+ "libmount: %s: lookup pair: srcpath(%s) target(%s)\n",
+ tb->filename, srcpath, target));
+
+ /* native paths */
+ mnt_reset_iter(&itr, direction);
+ while(mnt_tab_next_fs(tb, &itr, &fs) == 0) {
+ if (!fs->target || strcmp(fs->target, target))
+ continue;
+ p = mnt_fs_get_srcpath(fs);
+ if (p && strcmp(p, srcpath) == 0)
+ return fs;
+ }
+
+ if (!tb->cache)
+ return NULL;
+
+ mnt_reset_iter(&itr, direction);
+ while(mnt_tab_next_fs(tb, &itr, &fs) == 0) {
+ const char *src;
+
+ if (!fs->target)
+ continue;
+
+ /* canonicalized or non-canonicalied target */
+ if (strcmp(fs->target, target)) {
+ p = mnt_resolve_path(fs->target, tb->cache);
+ if (!p || strcmp(p, target))
+ continue;
+ }
+
+ src = mnt_fs_get_srcpath(fs);
+ if (src) {
+ /* canonicalized or non-canonicalied srcpath */
+ if (strcmp(src, srcpath)) {
+ p = mnt_resolve_path(src, tb->cache);
+ if (!p || strcmp(p, srcpath))
+ continue;
+ }
+ } else if (has_tags != 0) {
+ /* entry source is tag */
+ const char *t, *v;
+
+ if (mnt_fs_get_tag(fs, &t, &v))
+ continue;
+ if (has_tags == -1 &&
+ mnt_cache_read_tags(tb->cache, srcpath)) {
+ has_tags = 0;
+ continue;
+ }
+ has_tags = 1;
+ if (!mnt_cache_device_has_tag(tb->cache, srcpath, t, v))
+ continue;
+ } else
+ continue;
+
+ return fs;
+ }
+ return NULL;
+}
+
+/**
+ * mnt_tab_fprintf:
+ * @f: FILE
+ * @fmt: per line printf-like format string (see MNT_MFILE_PRINTFMT)
+ * @tb: tab pointer
+ *
+ * Returns 0 on success, -1 in case of error.
+ */
+int mnt_tab_fprintf(mnt_tab *tb, FILE *f, const char *fmt)
+{
+ mnt_iter itr;
+ mnt_fs *fs;
+
+ assert(f);
+ assert(fmt);
+ assert(tb);
+
+ if (!f || !fmt || !tb)
+ return -1;
+
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+ while(mnt_tab_next_fs(tb, &itr, &fs) == 0) {
+ if (mnt_fs_fprintf(fs, f, fmt) == -1)
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * mnt_tab_update_file
+ * @tb: tab pointer
+ *
+ * Writes tab to disk. Don't forget to lock the file (see mnt_lock()).
+ *
+ * Returns 0 on success, -1 in case of error.
+ */
+int mnt_tab_update_file(mnt_tab *tb)
+{
+ FILE *f = NULL;
+ char tmpname[PATH_MAX];
+ const char *filename;
+ struct stat st;
+ int fd;
+
+ assert(tb);
+ if (!tb)
+ goto error;
+
+ filename = mnt_tab_get_name(tb);
+ if (!filename)
+ goto error;
+
+ if (snprintf(tmpname, sizeof(tmpname), "%s.tmp", filename)
+ >= sizeof(tmpname))
+ goto error;
+
+ f = fopen(tmpname, "w");
+ if (!f)
+ goto error;
+
+ if (mnt_tab_fprintf(tb, f, MNT_MFILE_PRINTFMT) != 0)
+ goto error;
+
+ fd = fileno(f);
+
+ if (fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0)
+ goto error;
+
+ /* Copy uid/gid from the present file before renaming. */
+ if (stat(filename, &st) == 0) {
+ if (fchown(fd, st.st_uid, st.st_gid) < 0)
+ goto error;
+ }
+
+ fclose(f);
+ f = NULL;
+
+ if (rename(tmpname, filename) < 0)
+ goto error;
+
+ return 0;
+error:
+ if (f)
+ fclose(f);
+ return -1;
+}
+
+#ifdef TEST_PROGRAM
+int test_strerr(struct mtest *ts, int argc, char *argv[])
+{
+ char buf[BUFSIZ];
+ mnt_tab *tb;
+ int i;
+
+ tb = mnt_new_tab("-test-");
+ if (!tb)
+ goto err;
+
+ for (i = 0; i < 10; i++) {
+ mnt_fs *fs = mnt_new_fs();
+ if (!fs)
+ goto err;
+ if (i % 2)
+ fs->flags |= MNT_FS_ERROR; /* mark entry as broken */
+ fs->lineno = i+1;
+ mnt_tab_add_fs(tb, fs);
+ }
+
+ printf("\tadded %d valid lines\n", mnt_tab_get_nents(tb));
+ printf("\tadded %d broken lines\n", mnt_tab_get_nerrs(tb));
+
+ if (!mnt_tab_get_nerrs(tb)) /* report broken entries */
+ goto err;
+ mnt_tab_strerror(tb, buf, sizeof(buf));
+ printf("\t%s\n", buf);
+
+ mnt_free_tab(tb);
+ return 0;
+err:
+ return -1;
+}
+
+mnt_tab *create_tab(const char *file)
+{
+ mnt_tab *tb;
+
+ if (!file)
+ return NULL;
+ tb = mnt_new_tab(file);
+ 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));
+ fprintf(stderr, "%s\n", buf);
+ goto err;
+ }
+ return tb;
+err:
+ mnt_free_tab(tb);
+ return NULL;
+}
+
+int test_parse(struct mtest *ts, int argc, char *argv[])
+{
+ mnt_tab *tb;
+
+ tb = create_tab(argv[1]);
+ if (!tb)
+ return -1;
+
+ mnt_tab_fprintf(tb, stdout, MNT_MFILE_PRINTFMT);
+ mnt_free_tab(tb);
+ return 0;
+}
+
+int test_find(struct mtest *ts, int argc, char *argv[], int dr)
+{
+ mnt_tab *tb;
+ mnt_fs *fs = NULL;
+ mnt_cache *mpc;
+ const char *file, *find, *what;
+
+ if (argc != 4) {
+ fprintf(stderr, "try --help\n");
+ goto err;
+ }
+
+ file = argv[1], find = argv[2], what = argv[3];
+
+ tb = create_tab(file);
+ if (!tb)
+ goto err;
+
+ /* create a cache for canonicalized paths */
+ mpc = mnt_new_cache();
+ if (!mpc)
+ goto err;
+ mnt_tab_set_cache(tb, mpc);
+
+ if (strcasecmp(find, "source") == 0)
+ fs = mnt_tab_find_source(tb, what, dr);
+ else if (strcasecmp(find, "target") == 0)
+ fs = mnt_tab_find_target(tb, what, dr);
+
+ if (!fs)
+ fprintf(stderr, "%s: not found %s '%s'\n", file, find, what);
+ else {
+ const char *s = mnt_fs_get_srcpath(fs);
+ if (s)
+ printf("%s", s);
+ else {
+ const char *tag, *val;
+ mnt_fs_get_tag(fs, &tag, &val);
+ printf("%s=%s", tag, val);
+ }
+ printf("|%s|%s\n", mnt_fs_get_target(fs),
+ mnt_fs_get_optstr(fs));
+ }
+ mnt_free_tab(tb);
+ mnt_free_cache(mpc);
+ return 0;
+err:
+ return -1;
+}
+
+int test_find_bw(struct mtest *ts, int argc, char *argv[])
+{
+ return test_find(ts, argc, argv, MNT_ITER_BACKWARD);
+}
+
+int test_find_fw(struct mtest *ts, int argc, char *argv[])
+{
+ return test_find(ts, argc, argv, MNT_ITER_FORWARD);
+}
+
+int main(int argc, char *argv[])
+{
+ struct mtest tss[] = {
+ { "--strerror", test_strerr, " test tab error reporting" },
+ { "--parse", test_parse, "<file> parse and print tab" },
+ { "--find-forward", test_find_fw, "<file> <source|target> <string>" },
+ { "--find-backward", test_find_bw, "<file> <source|target> <string>" },
+ { NULL }
+ };
+
+ return mnt_run_test(tss, argc, argv);
+}
+
+#endif /* TEST_PROGRAM */
--- /dev/null
+/*
+ * 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 */