diff options
Diffstat (limited to 'shlibs/mount/src/tab.c')
-rw-r--r-- | shlibs/mount/src/tab.c | 929 |
1 files changed, 929 insertions, 0 deletions
diff --git a/shlibs/mount/src/tab.c b/shlibs/mount/src/tab.c new file mode 100644 index 00000000..cb6a91d3 --- /dev/null +++ b/shlibs/mount/src/tab.c @@ -0,0 +1,929 @@ +/* + * Copyright (C) 2008-2010 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: tab + * @title: FS container + * @short_description: container for entries from fstab/mtab/mountinfo + * + * + * Note that mnt_tab_find_* functions are mount(8) compatible. These functions + * try 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: + * <informalexample> + * <programlisting> + * LABEL=foo /foo auto rw + * /dev/foo /foo auto rw + * </programlisting> + * </informalexample> + * + * where both lines are used for the *same* device, then + * <informalexample> + * <programlisting> + * mnt_tab_find_source(tb, "/dev/foo", &fs); + * </programlisting> + * </informalexample> + * will returns the second line, and + * <informalexample> + * <programlisting> + * mnt_tab_find_source(tb, "LABEL=foo", &fs); + * </programlisting> + * </informalexample> + * will returns the first entry, and + * <informalexample> + * <programlisting> + * mnt_tab_find_source(tb, "UUID=anyuuid", &fs); + * </programlisting> + * </informalexample> + * will returns the first entry (if UUID matches with the device). + */ + +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <blkid.h> + +#include "nls.h" +#include "mountP.h" +#include "c.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: + * @tb: 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_get_root_fs: + * @tb: mountinfo file (/proc/self/mountinfo) + * @root: returns pointer to the root filesystem (/) + * + * Returns: 0 on success or -1 case of error. + */ +int mnt_tab_get_root_fs(mnt_tab *tb, mnt_fs **root) +{ + mnt_iter itr; + mnt_fs *fs; + int root_id = 0; + + assert(tb); + assert(root); + + if (!tb || !root) + return -1; + + DBG(DEBUG_TAB, fprintf(stderr, + "libmount: %s: lookup root fs\n", tb->filename)); + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + while(mnt_tab_next_fs(tb, &itr, &fs) == 0) { + int id = mnt_fs_get_parent_id(fs); + if (!id) + break; /* @tab is not mountinfo file? */ + + if (!*root || id < root_id) { + *root = fs; + root_id = id; + } + } + + return root_id ? 0 : -1; +} + +/** + * mnt_tab_next_child_fs: + * @tb: mountinfo file (/proc/self/mountinfo) + * @itr: iterator + * @parent: parental FS + * @chld: returns the next child filesystem + * + * Note that filesystems are returned in the order how was mounted (according to + * IDs in /proc/self/mountinfo). + * + * Returns: 0 on success, -1 in case of error or 1 at end of list. + */ +int mnt_tab_next_child_fs(mnt_tab *tb, mnt_iter *itr, + mnt_fs *parent, mnt_fs **chld) +{ + mnt_fs *fs; + int parent_id, lastchld_id = 0, chld_id = 0; + + if (!tb || !itr || !parent) + return -1; + + DBG(DEBUG_TAB, fprintf(stderr, + "libmount: %s: lookup next child of %s\n", + tb->filename, mnt_fs_get_target(parent))); + + parent_id = mnt_fs_get_id(parent); + if (!parent_id) + return -1; + + /* get ID of the previously returned child */ + if (itr->head && itr->p != itr->head) { + MNT_ITER_ITERATE(itr, fs, struct _mnt_fs, ents); + lastchld_id = mnt_fs_get_id(fs); + } + + *chld = NULL; + + mnt_reset_iter(itr, MNT_ITER_FORWARD); + while(mnt_tab_next_fs(tb, itr, &fs) == 0) { + int id; + + if (mnt_fs_get_parent_id(fs) != parent_id) + continue; + + id = mnt_fs_get_id(fs); + + if ((!lastchld_id || id > lastchld_id) && + (!*chld || id < chld_id)) { + *chld = fs; + chld_id = id; + } + } + + if (!chld_id) + return 1; /* end of iterator */ + + /* set the iterator to the @chld for the next call */ + mnt_tab_set_iter(tb, itr, *chld); + + 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): + * <informalexample> + * <programlisting> + * 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); + * </programlisting> + * </informalexample> + */ +int mnt_tab_next_fs(mnt_tab *tb, mnt_iter *itr, mnt_fs **fs) +{ + int rc; + + assert(tb); + assert(itr); + assert(fs); + + if (!tb || !itr || !fs) + return -1; +again: + rc = 1; + if (!itr->head) + MNT_ITER_INIT(itr, &tb->ents); + if (itr->p != itr->head) { + MNT_ITER_ITERATE(itr, *fs, struct _mnt_fs, ents); + rc = 0; + } + + /* ignore broken entries */ + if (*fs && ((*fs)->flags & MNT_FS_ERROR)) + goto again; + + return rc; +} + +/** + * mnt_tab_find_next_fs: + * @tb: table + * @itr: iterator + * @match_func: function returns 1 or 0 + * @userdata: extra data for match_func + * @fs: returns pointer to the next matching table entry + * + * This function allows search in @tb. + * + * Returns: -1 in case of error, 1 at end of table or 0 o success. + */ +int mnt_tab_find_next_fs(mnt_tab *tb, mnt_iter *itr, + int (*match_func)(mnt_fs *, void *), void *userdata, + mnt_fs **fs) +{ + if (!tb || !itr || !fs || !match_func) + return -1; + + DBG(DEBUG_TAB, fprintf(stderr, + "libmount: %s: lookup next fs\n", tb->filename)); + + if (!itr->head) + MNT_ITER_INIT(itr, &tb->ents); + + do { + if (itr->p != itr->head) + MNT_ITER_ITERATE(itr, *fs, struct _mnt_fs, ents); + else + break; /* end */ + + if ((*fs)->flags & MNT_FS_ERROR) + continue; + if (match_func(*fs, userdata)) + return 0; + } while(1); + + *fs = NULL; + 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) { + int rc = mnt_cache_read_tags(tb->cache, cn); + + mnt_reset_iter(&itr, direction); + + if (rc == 0) { + /* @path's TAGs are in the cache */ + 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; + } + } else if (rc < 0 && errno == EACCES) { + /* @path is unaccessible, try evaluate all TAGs in @tb + * by udev symlinks -- this could be expensive on systems + * with huge fstab/mtab */ + while(mnt_tab_next_fs(tb, &itr, &fs) == 0) { + const char *t, *v, *x; + if (mnt_fs_get_tag(fs, &t, &v)) + continue; + x = mnt_resolve_tag(t, v, tb->cache); + if (x && !strcmp(x, cn)) + 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 + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * 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_fprintf: + * @tb: tab pointer + * @f: FILE + * @fmt: per line printf-like format string (see MNT_TAB_PRINTFMT) + * + * 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_TAB_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; + mnt_iter *itr; + mnt_fs *fs; + + tb = create_tab(argv[1]); + if (!tb) + return -1; + + itr = mnt_new_iter(MNT_ITER_FORWARD); + if (!itr) + goto err; + while(mnt_tab_next_fs(tb, itr, &fs) == 0) + mnt_fs_print_debug(fs, stdout); +err: + mnt_free_iter(itr); + 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 */ |