From: Karel Zak Date: Wed, 13 Jan 2010 21:08:16 +0000 (+0100) Subject: libmount: add fstab/mtab/mountinfo parsing routines X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6bd8b7a79dffb532ae8639e563f75005a10bc41d;p=util-linux libmount: add fstab/mtab/mountinfo parsing routines Signed-off-by: Karel Zak --- diff --git a/shlibs/mount/src/Makefile.am b/shlibs/mount/src/Makefile.am index de265c9d..5d80779e 100644 --- a/shlibs/mount/src/Makefile.am +++ b/shlibs/mount/src/Makefile.am @@ -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) diff --git a/shlibs/mount/src/cache.c b/shlibs/mount/src/cache.c index 7b9040ff..18cf9fad 100644 --- a/shlibs/mount/src/cache.c +++ b/shlibs/mount/src/cache.c @@ -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) diff --git a/shlibs/mount/src/mount.h.in b/shlibs/mount/src/mount.h.in index d179620c..b4c9bc53 100644 --- a/shlibs/mount/src/mount.h.in +++ b/shlibs/mount/src/mount.h.in @@ -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) */ diff --git a/shlibs/mount/src/mountP.h b/shlibs/mount/src/mountP.h index b6eb8cd3..f08f435a 100644 --- a/shlibs/mount/src/mountP.h +++ b/shlibs/mount/src/mountP.h @@ -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 index 00000000..fd929313 --- /dev/null +++ b/shlibs/mount/src/tab.c @@ -0,0 +1,820 @@ +/* + * Copyright (C) 2008 Karel Zak + * + * 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=", &fs); + * + * will returns the first entry (if UUID matches with the device). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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, " parse and print tab" }, + { "--find-forward", test_find_fw, " " }, + { "--find-backward", test_find_bw, " " }, + { 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 index 00000000..feaab74a --- /dev/null +++ b/shlibs/mount/src/tab_parse.c @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2009 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#include +#include +#include +#include + +#include "nls.h" +#include "mountP.h" + +static inline char *skip_spaces(char *s) +{ + assert(s); + + while (*s == ' ' || *s == '\t') + s++; + return s; +} + +static inline char *skip_nonspaces(char *s) +{ + assert(s); + + while (*s && !(*s == ' ' || *s == '\t')) + s++; + return s; +} + +#define isoctal(a) (((a) & ~7) == '0') + +/* returns malloced pointer - no more strdup required */ +static void unmangle(char *s, char *buf, size_t len) +{ + size_t sz = 0; + assert(s); + + while(*s && sz < len - 1) { + if (*s == '\\' && sz + 4 < len - 1 && isoctal(s[1]) && + isoctal(s[2]) && isoctal(s[3])) { + + *buf++ = 64*(s[1] & 7) + 8*(s[2] & 7) + (s[3] & 7); + s += 4; + sz += 4; + } else { + *buf++ = *s++; + sz++; + } + } + *buf = '\0'; +} + +static size_t next_word_size(char *s, char **start, char **end) +{ + char *e; + + assert(s); + + s = skip_spaces(s); + if (!*s) + return 0; + e = skip_nonspaces(s); + + if (start) + *start = s; + if (end) + *end = e; + + return e - s; +} + +static char *next_word(char **s) +{ + size_t sz; + char *res, *end; + + assert(s); + + sz = next_word_size(*s, s, &end) + 1; + if (sz == 1) + return NULL; + + res = malloc(sz); + if (!res) + return NULL; + + unmangle(*s, res, sz); + *s = end + 1; + return res; +} + +static int next_word_skip(char **s) +{ + *s = skip_spaces(*s); + if (!**s) + return 1; + *s = skip_nonspaces(*s); + return 0; +} + +static int next_number(char **s, int *num) +{ + char *end = NULL; + + assert(num); + assert(s); + + *s = skip_spaces(*s); + if (!**s) + return -1; + *num = strtol(*s, &end, 10); + if (end == NULL || *s == end) + return -1; + + *s = end; + + /* valid end of number is space or terminator */ + if (*end == ' ' || *end == '\t' || *end == '\0') + return 0; + return -1; +} + +/* + * Parses one line from {fs,m}tab + */ +static int mnt_tab_parse_file_line(mnt_fs *fs, char *s) +{ + /* SOURCE */ + if (__mnt_fs_set_source(fs, next_word(&s)) != 0) + return 1; + + /* TARGET */ + fs->target = next_word(&s); + if (!fs->target) + return 1; + + /* TYPE */ + if (__mnt_fs_set_fstype(fs, next_word(&s)) != 0) + return 1; + + /* OPTS */ + fs->optstr = next_word(&s); + if (!fs->optstr) + return 1; + /* default */ + fs->passno = fs->freq = 0; + + /* FREQ (optional) */ + if (next_number(&s, &fs->freq) != 0) { + if (*s) + return 1; + + /* PASSNO (optional) */ + } else if (next_number(&s, &fs->passno) != 0 && *s) + return 1; + + return 0; +} + +/* + * Parses one line from mountinfo file + */ +static int mnt_parse_mountinfo_line(mnt_fs *fs, char *s) +{ + /* ID */ + if (next_number(&s, &fs->id) != 0) + return 1; + + /* PARENT */ + if (next_number(&s, &fs->parent) != 0) + return 1; + + /* : (ignore) */ + if (next_word_skip(&s) != 0) + return 1; + + /* MOUNTROOT */ + fs->mntroot = next_word(&s); + if (!fs->mntroot) + return 1; + + /* TARGET (mountpoit) */ + fs->target = next_word(&s); + if (!fs->target) + return 1; + + /* OPTIONS (fs-independent) */ + fs->vfs_optstr = next_word(&s); + if (!fs->vfs_optstr) + return 1; + + /* optional fields (ignore) */ + do { + s = skip_spaces(s); + if (s && *s == '-' && + (*(s + 1) == ' ' || *(s + 1) == '\t')) { + s++; + break; + } + if (s && next_word_skip(&s) != 0) + return 1; + } while (s); + + /* FSTYPE */ + if (__mnt_fs_set_fstype(fs, next_word(&s)) != 0) + return 1; + + /* SOURCE or "none" */ + if (__mnt_fs_set_source(fs, next_word(&s)) != 0) + return 1; + + /* OPTIONS (fs-dependent) */ + fs->fs_optstr = next_word(&s); + if (!fs->fs_optstr) + return 1; + + return 0; +} + +/* + * Returns {m,fs}tab or mountinfo file format (MNT_FMT_*) + * + * The "mountinfo" format is always: " ... " + */ +static int detect_fmt(char *line) +{ + int num; + + /* ID */ + if (next_number(&line, &num) != 0) + return MNT_FMT_FSTAB; + + /* PARENT */ + if (next_number(&line, &num) != 0) + return MNT_FMT_FSTAB; + + return MNT_FMT_MOUNTINFO; +} + + +/* + * Merges @vfs and @fs options strings into a new string + * This function skips the generic part of @fs options. + * For example (see "rw"): + * + * mnt_merge_optstr("rw,noexec", "rw,journal=update") + * + * returns --> "rw,noexec,journal=update" + * + * We need this function for /proc/self/mountinfo parsing. + */ +static char *merge_optstr(const char *vfs, const char *fs) +{ + const char *p1 = vfs, *p2 = fs; + char *res; + size_t sz; + + if (!vfs && !fs) + return NULL; + if (!vfs || !fs) + return strdup(fs ? fs : vfs); + if (!strcmp(vfs, fs)) + return strdup(vfs); /* e.g. "aaa" and "aaa" */ + + /* skip the same FS options */ + while (*p1 && *p2 && *++p1 == *++p2); + + if (*p1 == ',') + p1++; + if (*p2 == ',') + p2++; + if (!*p1 && !*p2) /* e.g. "aaa,bbb" and "aaa,bbb," */ + return strdup(vfs); + if (!*p1 || !*p2) /* e.g. "aaa" and "aaa,bbb" */ + return strdup(*p1 ? vfs : fs); + + p1 = vfs; + sz = strlen(p1) + strlen(p2) + 2; /* 2= separator + '\0' */ + + res = malloc(sz); + if (!res) + return NULL; + + snprintf(res, sz, "%s,%s", p1, p2); + return res; +} + +/* + * Read and parse the next line from {fs,m}tab or mountinfo + */ +static int mnt_tab_parse_next(mnt_tab *tb, FILE *f, mnt_fs *fs) +{ + char buf[BUFSIZ]; + char *s; + + assert(tb); + assert(f); + assert(fs); + + /* read the next non-blank non-comment line */ + do { + if (fgets(buf, sizeof(buf), f) == NULL) + return -1; + tb->nlines++; + s = index (buf, '\n'); + if (!s) { + /* Missing final newline? Otherwise extremely */ + /* long line - assume file was corrupted */ + if (feof(f)) { + DBG(DEBUG_TAB, fprintf(stderr, + "libmount: WARNING: no final newline at the end of %s\n", + tb->filename)); + s = index (buf, '\0'); + } else { + DBG(DEBUG_TAB, fprintf(stderr, + "libmount: %s: %d: missing newline at line\n", + tb->filename, tb->nlines)); + goto err; + } + } + *s = '\0'; + if (--s >= buf && *s == '\r') + *s = '\0'; + s = skip_spaces(buf); + } while (*s == '\0' || *s == '#'); + + DBG(DEBUG_TAB, fprintf(stderr, "libmount: %s:%d: %s\n", + tb->filename, tb->nlines, s)); + + if (!tb->fmt) + tb->fmt = detect_fmt(s); + + if (tb->fmt == MNT_FMT_FSTAB) { + /* parse /etc/{fs,m}tab */ + if (mnt_tab_parse_file_line(fs, s) != 0) + goto err; + } else if (tb->fmt == MNT_FMT_MOUNTINFO) { + /* parse /proc/self/mountinfo */ + if (mnt_parse_mountinfo_line(fs, s) != 0) + goto err; + } + + /* merge fs_optstr and vfs_optstr into optstr (necessary for "mountinfo") */ + if (!fs->optstr && (fs->vfs_optstr || fs->fs_optstr)) { + fs->optstr = merge_optstr(fs->vfs_optstr, fs->fs_optstr); + if (!fs->optstr) + goto err; + } + + fs->lineno = tb->nlines; + + DBG(DEBUG_TAB, fprintf(stderr, + "libmount: %s: %d: SOURCE:%s, MNTPOINT:%s, TYPE:%s, " + "OPTS:%s, FREQ:%d, PASSNO:%d\n", + tb->filename, fs->lineno, + fs->source, fs->target, fs->fstype, + fs->optstr, fs->freq, fs->passno)); + + return 0; +err: + /* we don't report parse errors to caller; caller has to check + * errors by mnt_tab_get_nerrs() or internaly by MNT_ENTRY_ERR flag + */ + fs->lineno = tb->nlines; + fs->flags |= MNT_FS_ERROR; + return 0; +} + +/** + * mnt_tab_parse_file: + * @tb: tab pointer + * + * Parses whole table (e.g. /etc/fstab). + * + * Returns 0 on success and -1 in case of error. The parse errors is possible + * to detect by mnt_tab_get_nerrs() and error message is possible to create by + * mnt_tab_strerror(). + * + * Example: + * + * mnt_tab *tb = mnt_new_tab("/etc/fstab"); + * int rc; + * + * rc = mnt_tab_parse_file(tb); + * if (rc) { + * if (mnt_tab_get_nerrs(tb)) { / * parse error * / + * mnt_tab_strerror(tb, buf, sizeof(buf)); + * fprintf(stderr, "%s: %s\n", progname, buf); + * } else + * perror(mnt_tab_get_name(tb)); / * system error * / + * } else + * mnt_fprintf_tab(tb, stdout, NULL); + * + * mnt_free_tab(tb); + */ +int mnt_tab_parse_file(mnt_tab *tb) +{ + FILE *f; + + assert(tb); + assert(tb->filename); + + if (!tb->filename) + return -1; + + f = fopen(tb->filename, "r"); + if (!f) + return -1; + + while (!feof(f)) { + int rc; + mnt_fs *fs = mnt_new_fs(); + if (!fs) + goto error; + + rc = mnt_tab_parse_next(tb, f, fs); + if (!rc) + rc = mnt_tab_add_fs(tb, fs); + else if (feof(f)) { + mnt_free_fs(fs); + break; + } + if (rc) { + mnt_free_fs(fs); + goto error; + } + } + + fclose(f); + return 0; +error: + fclose(f); + return -1; +} + +/** + * mnt_new_tab_parse: + * @filename: /etc/{m,fs}tab or /proc/self/mountinfo path + * + * Same as mnt_new_tab() + mnt_tab_parse_file(). Note that this function does + * not provide details (by mnt_tab_strerror()) about failed parsing -- so you + * should not to use this function for user-writeable files like /etc/fstab. + * + * Returns newly allocated tab on success and NULL in case of error. + */ +mnt_tab *mnt_new_tab_from_file(const char *filename) +{ + mnt_tab *tb; + + assert(filename); + + if (!filename) + return NULL; + tb = mnt_new_tab(filename); + if (tb && mnt_tab_parse_file(tb) != 0) { + mnt_free_tab(tb); + tb = NULL; + } + return tb; +} + +/** + * mnt_tab_get_nerrs: + * @tb: pointer to table + * + * Returns number of broken (parse error) entries in the table. + */ +int mnt_tab_get_nerrs(mnt_tab *tb) +{ + assert(tb); + return tb->nerrs; +} + +/** + * mnt_tab_strerror: + * @tb: pointer to table + * @buf: buffer to return error message + * @buflen: lenght of the buf + * + * Returns error message for table (file) parse errors. For example: + * + * "/etc/fstab: parse error at line(s): 1, 2 and 3." + */ +char *mnt_tab_strerror(mnt_tab *tb, char *buf, size_t buflen) +{ + struct list_head *p; + int last = -1; + char *b = buf; + char *end = buf + buflen - 1; + + assert(tb); + assert(buf); + assert(buflen); + + if (!tb || !tb->nerrs || !buf || buflen <=0) + return NULL; + + if (tb->filename) { + snprintf(b, end - b, "%s: ", tb->filename); + b += strnlen(b, end - b); + } + + if (tb->nerrs > 1) + strncpy(b, _("parse error at lines: "), end - b); + else + strncpy(b, _("parse error at line: "), end - b); + b += strnlen(b, end - b); + *b = '\0'; + + list_for_each(p, &tb->ents) { + mnt_fs *fs = list_entry(p, mnt_fs, ents); + if (b == end) + goto done; + if (fs->flags & MNT_FS_ERROR) { + if (last != -1) { + snprintf(b, end - b, "%d, ", last); + b += strnlen(b, end - b); + } + last = fs->lineno; + } + } + + if (tb->nerrs == 1) + snprintf(b, end - b, "%d.", last); + else + snprintf(b - 1, end - b, _(" and %d."), last); +done: + return buf; +} + +#ifdef LIBMOUNT_TEST_PROGRAM +int test_parse(struct mtest *ts, int argc, char *argv[]) +{ + mnt_tab *tb; + mnt_fs *fs; + mnt_iter *itr; + + if (argc != 2) + goto err; + + tb = mnt_new_tab(argv[1]); + if (!tb) + goto err; + if (mnt_tab_parse_file(tb) != 0) + goto err; + if (mnt_tab_get_nerrs(tb)) { + char buf[BUFSIZ]; + + mnt_tab_strerror(tb, buf, sizeof(buf)); + printf("\t%s\n", buf); + goto err; + } + + itr = mnt_new_iter(MNT_ITER_FORWARD); + if (!itr) + goto err; + while(mnt_tab_next_fs(tb, itr, &fs) == 0) { + const char *tg, *vl; + + if (mnt_fs_get_tag(fs, &tg, &vl) == 0) + printf("%s=%s", tg, vl); + else + printf("%s", mnt_fs_get_srcpath(fs)); + + printf("|%s|%s|%s|%d|%d|\n", + mnt_fs_get_target(fs), + mnt_fs_get_fstype(fs), + mnt_fs_get_optstr(fs), + mnt_fs_get_freq(fs), + mnt_fs_get_passno(fs)); + } + mnt_free_tab(tb); + mnt_free_iter(itr); + + return 0; +err: + return -1; +} + +int main(int argc, char *argv[]) +{ + struct mtest tss[] = { + { "--parse", test_parse, " parse the {fs,m}tab or mountinfo file" }, + { NULL } + }; + return mnt_run_test(tss, argc, argv); +} + +#endif /* LIBMOUNT_TEST_PROGRAM */