]> err.no Git - util-linux/commitdiff
libmount: add fstab/mtab/mountinfo parsing routines
authorKarel Zak <kzak@redhat.com>
Wed, 13 Jan 2010 21:08:16 +0000 (22:08 +0100)
committerKarel Zak <kzak@redhat.com>
Thu, 3 Jun 2010 13:20:11 +0000 (15:20 +0200)
Signed-off-by: Karel Zak <kzak@redhat.com>
shlibs/mount/src/Makefile.am
shlibs/mount/src/cache.c
shlibs/mount/src/mount.h.in
shlibs/mount/src/mountP.h
shlibs/mount/src/tab.c [new file with mode: 0644]
shlibs/mount/src/tab_parse.c [new file with mode: 0644]

index de265c9d57f1d03aac2ccc614d87153bd191f129..5d80779e022c44bc4dcfdeaf1a6e1bb4327ef81a 100644 (file)
@@ -13,7 +13,7 @@ libmount_la_SOURCES = $(mountinc_HEADERS)
 
 nodist_libmount_la_SOURCES = mount.h version.c utils.c test.c init.c cache.c \
                        optstr.c optmap.c optent.c optls.c iter.c list.h lock.c \
-                       fs.c \
+                       fs.c tab.c tab_parse.c \
                        $(top_srcdir)/lib/canonicalize.c
 
 libmount_la_LIBADD = $(ul_libblkid_la)
@@ -42,7 +42,8 @@ uninstall-hook:
        rm -f $(DESTDIR)$(libdir)/libmount.so*
 
 # tests
-noinst_PROGRAMS = test_version test_cache test_optstr test_optls test_lock
+noinst_PROGRAMS = test_version test_cache test_optstr test_optls test_lock \
+                 test_tab
 tests_cppflags = $(AM_CPPFLAGS) -DTEST_PROGRAM
 tests_ldadd = .libs/libmount.a $(ul_libblkid_la)
 
@@ -65,3 +66,7 @@ test_optls_LDADD = $(tests_ldadd)
 test_lock_SOURCES = lock.c
 test_lock_CPPFLAGS = $(tests_cppflags)
 test_lock_LDADD = $(tests_ldadd)
+
+test_tab_SOURCES = tab_parse.c tab.c
+test_tab_CPPFLAGS = $(tests_cppflags)
+test_tab_LDADD = $(tests_ldadd)
index 7b9040ff8dd9ba51e7ccadc2ed3e4ae343320c47..18cf9fad21a627a100ef56585b9f412dfca8dc28 100644 (file)
@@ -112,9 +112,6 @@ static int mnt_cache_add_entry(mnt_cache *cache, char *native,
        e->flag = flag;
        cache->nents++;
 
-       DBG(DEBUG_CACHE,
-               printf("cache: [%zd entry] added %s\n", cache->nents, real));
-
        return 0;
 }
 
@@ -146,6 +143,8 @@ static int mnt_cache_add_tag(mnt_cache *cache, const char *token,
 
        if (mnt_cache_add_entry(cache, native, real, flag))
                goto error;
+       DBG(DEBUG_CACHE,
+               printf("cache: added %s: %s=%s\n", real, token, value));
        return 0;
 error:
        free(native);
@@ -237,6 +236,9 @@ int mnt_cache_read_tags(mnt_cache *cache, const char *devname)
        if (!cache || !devname)
                return -1;
 
+
+       DBG(DEBUG_CACHE, printf("cache: tags for %s requested\n", devname));
+
        /* check is device is already cached */
        for (i = 0; i < cache->nents; i++) {
                struct mnt_cache_entry *e = &cache->ents[i];
@@ -352,7 +354,7 @@ char *mnt_resolve_path(const char *path, mnt_cache *cache)
                }
        }
 
-       DBG(DEBUG_CACHE, printf("cache: %s --> %s\n", path, p));
+       DBG(DEBUG_CACHE, printf("cache: added %s: %s\n", path, p));
        return p;
 error:
        if (real != native)
index d179620c18ca27fa9e214c2a638404d3f3f25413..b4c9bc53abcaea60572ca427f5bac84b1806cb46 100644 (file)
@@ -95,6 +95,13 @@ struct mnt_optmap
  */
 typedef struct _mnt_fs mnt_fs;
 
+/**
+ * mnt_tab:
+ *
+ * List of mnt_fs entries (parsed fstab/mtab/mountinfo)
+ */
+typedef struct _mnt_tab mnt_tab;
+
 
 /* version.c */
 extern int mnt_parse_version_string(const char *ver_string);
@@ -237,6 +244,35 @@ extern int mnt_fprintf_line(
 extern int mnt_fs_fprintf(mnt_fs *ent, FILE *f, const char *fmt);
 extern int mnt_fs_print_debug(mnt_fs *ent, FILE *file);
 
+/* tab-parse.c */
+extern mnt_tab *mnt_new_tab_from_file(const char *filename);
+extern int mnt_tab_parse_file(mnt_tab *tb);
+extern char *mnt_tab_strerror(mnt_tab *tb, char *buf, size_t buflen);
+extern int mnt_tab_get_nerrs(mnt_tab *tb);
+
+/* tab.c */
+extern mnt_tab *mnt_new_tab(const char *filename);
+extern void mnt_free_tab(mnt_tab *tb);
+extern int mnt_tab_get_nents(mnt_tab *tb);
+extern int mnt_tab_set_cache(mnt_tab *tb, mnt_cache *mpc);
+extern mnt_cache *mnt_tab_get_cache(mnt_tab *tb);
+extern const char *mnt_tab_get_name(mnt_tab *tb);
+extern int mnt_tab_add_fs(mnt_tab *tb, mnt_fs *fs);
+extern int mnt_tab_remove_fs(mnt_tab *tb, mnt_fs *fs);
+extern int mnt_tab_next_fs(mnt_tab *tb, mnt_iter *itr, mnt_fs **fs);
+extern int mnt_tab_set_iter(mnt_tab *tb, mnt_iter *itr, mnt_fs *fs);
+
+extern mnt_fs *mnt_tab_find_target(mnt_tab *tb, const char *path, int direction);
+extern mnt_fs *mnt_tab_find_srcpath(mnt_tab *tb, const char *path, int direction);
+extern mnt_fs *mnt_tab_find_tag(mnt_tab *tb, const char *tag,
+                               const char *val, int direction);
+extern mnt_fs *mnt_tab_find_source(mnt_tab *tb, const char *source, int direction);
+extern mnt_fs *mnt_tab_find_pair(mnt_tab *tb, const char *srcpath,
+                               const char *target, int direction);
+extern int mnt_tab_fprintf(mnt_tab *tb, FILE *f, const char *fmt);
+extern int mnt_tab_update_file(mnt_tab *tb);
+
+
 /*
  * mount(8) userspace options masks (MNT_MAP_USERSPACE map)
  */
index b6eb8cd3432116fb0e31f9638cec2b864c3ff95d..f08f435a401c0ff87bc8faceaf320c5faf3ba81a 100644 (file)
@@ -35,6 +35,7 @@
 #define DEBUG_CACHE    (1 << 2)
 #define DEBUG_OPTIONS  (1 << 3)
 #define DEBUG_LOCKS    (1 << 4)
+#define DEBUG_TAB      (1 << 5)
 #define DEBUG_ALL      0xFFFF
 
 #ifdef CONFIG_LIBMOUNT_DEBUG
@@ -162,6 +163,31 @@ struct _mnt_fs {
 #define MNT_FS_PSEUDO  (1 << 2) /* pseudo filesystem */
 #define MNT_FS_NET     (1 << 3) /* network filesystem */
 
+/*
+ * File format
+ */
+enum {
+       MNT_FMT_FSTAB = 1,              /* /etc/{fs,m}tab */
+       MNT_FMT_MOUNTINFO               /* /proc/#/mountinfo */
+};
+
+/*
+ * mtab/fstab/mountinfo file
+ */
+struct _mnt_tab {
+       char            *filename;      /* file name or NULL */
+       int             fmt;            /* MNT_FMT_* file format */
+
+       int             nlines;         /* number of lines in the file (include commentrys) */
+       int             nents;          /* number of valid entries */
+       int             nerrs;          /* number of broken entries (parse errors) */
+
+       mnt_cache       *cache;         /* canonicalized paths/tags cache */
+
+       struct list_head        ents;   /* list of entries (mentry) */
+};
+
+
 /* optmap.c */
 extern const struct mnt_optmap *mnt_optmap_get_entry(struct mnt_optmap const **maps,
                              int nmaps, const char *name,
@@ -189,4 +215,6 @@ extern int mnt_optent_assign_map(mnt_optent *op,
 extern int __mnt_fs_set_source(mnt_fs *fs, char *source);
 extern int __mnt_fs_set_fstype(mnt_fs *fs, char *fstype);
 
+
+
 #endif /* _LIBMOUNT_PRIVATE_H */
diff --git a/shlibs/mount/src/tab.c b/shlibs/mount/src/tab.c
new file mode 100644 (file)
index 0000000..fd92931
--- /dev/null
@@ -0,0 +1,820 @@
+/*
+ * 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 */
diff --git a/shlibs/mount/src/tab_parse.c b/shlibs/mount/src/tab_parse.c
new file mode 100644 (file)
index 0000000..feaab74
--- /dev/null
@@ -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 */