summaryrefslogblamecommitdiffstats
path: root/shlibs/mount/src/tab.c
blob: 98c56602b8d7327f2f6bcecc20341c443ea61db0 (plain) (tree)
































                                                                               
                  

















































































































































































                                                                              


























































































                                                                                






















                                                                 

               






                               
               



                                                                 
                       





                                                 



































                                                                         
                   







































































































































                                                                               
                    
                                                
 



                                                                    
 

















                                                                                  










































































































                                                                                 





















































































































































                                                                                  

                      




                                 






                                                 













































































                                                                                
/*
 * 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 <errno.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_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;

	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)
 * @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;

	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):
 *
 *	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)
{
	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
 * @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;

	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) {
		mnt_reset_iter(&itr, direction);

		if (mnt_cache_read_tags(tb->cache, cn) > 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 (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
 *
 * 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:
 * @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;
	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 */