/* * Copyright (C) 2008-2010 Karel Zak * * This file may be redistributed under the terms of the * GNU Lesser General Public License. */ /** * SECTION: tab * @title: Filesystems 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: * * 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 #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; 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) * @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): * * 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; 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 * * 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, " 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 */