/* * Copyright (C) 2010 Karel Zak * * This file may be redistributed under the terms of the * GNU Lesser General Public License. */ /** * SECTION: update * @title: mtab (fstab) managment * @short_description: userspace mount information management * * The libmount library allows to use more modes for mtab management: * * - /etc/mtab is regular file * * then libmount manages the file in classical way (all mounts are added to * the file). This mode is always used for /etc/fstab updates as well. * * - /etc/mtab is symlink * * then libmount ignores mtab at all * * - /etc/mtab is symlink and /var/run/mount/ directory exists * * then libmount stores userspace specific mount options to the * /var/run/mount/mountinfo file (the file format compatible to * /proc/self/mountinfo) * * Note that mnt_update_* interface does not manage mount options. It's callers * responsibility set the right mount options (for example merge options from * mtab with command line on MS_REMOUNT). * * The small exception is the /var/run/mount/mountinfo file where are store * userspace mount options only. This is done by mnt_prepare_update(). * * The mtab is always updated in two steps. The first step is to prepare a new * update entry -- mnt_prepare_update(), this step has to be done before * mount(2) syscall. The second step is to update the file -- * mnt_update_file(), this step should be done after mount(2) syscall. * * The mnt_update_file() behaviour is undefined if mnt_prepare_update() has * not been used. */ #include #include #include #include #include #include #include #include "c.h" #include "mountP.h" #include "mangle.h" #include "pathnames.h" /* * mtab update description */ struct _mnt_update { int action; /* MNT_ACT_{MOUNT,UMOUNT} */ unsigned long mountflags; /* MS_* flags */ char *filename; /* usually /etc/mtab or /var/run/mount/mountinfo */ char *old_target; /* for MS_MOVE */ int format; /* MNT_FMT_{MTAB,FSTAB,MOUNTINFO} */ int nolock; /* don't alloca private mnt_lock */ mnt_fs *fs; /* entry */ mnt_lock *lc; /* lock or NULL */ int pointless; /* update is unnecessary */ }; /** * mnt_new_update: * @action: MNT_ACT_{MOUNT,UMOUNT} * @mountflags: MS_{REMOUNT,BIND,MOVE} * @fs: FS description * * Returns: newly allocated update description */ mnt_update *mnt_new_update(int action, unsigned long mountflags, const mnt_fs *fs) { mnt_update *upd; upd = calloc(1, sizeof(struct _mnt_update)); if (!upd) return NULL; DBG(UPDATE, mnt_debug_h(upd, "allocate")); if (action) mnt_update_set_action(upd, action); if (mountflags) mnt_update_set_mountflags(upd, mountflags); if (fs) mnt_update_set_fs(upd, fs); return upd; } /** * mnt_free_update: * @upd: update * * Deallocates mnt_update handler. */ void mnt_free_update(mnt_update *upd) { if (!upd) return; DBG(UPDATE, mnt_debug_h(upd, "free")); mnt_free_lock(upd->lc); free(upd->filename); free(upd->old_target); free(upd); } /** * mnt_update_set_filename: * @upd: update * @filename: path to update (default is /etc/update or /var/run/mount/mountinfo) * * Returns: 0 on success, -1 in case of error. */ int mnt_update_set_filename(mnt_update *upd, const char *filename) { char *p = NULL; assert(upd); if (!upd) return -EINVAL; if (filename) { p = strdup(filename); if (!p) return -ENOMEM; } free(upd->filename); upd->filename = p; return 0; } /** * mnt_update_get_filename * @upd: update * * Note that function returns NULL if the filename has not been defined (see * mnt_update_set_filename()) or mnt_prepare_update() has not been called. * * Returns: filename or NULL. */ const char *mnt_update_get_filename(mnt_update *upd) { return upd ? upd->filename : NULL; } /** * mnt_update_set_action: * @upd: update * @action: MNT_ACT_{MOUNT,UMOUNT} * * Overwrites the previously defined (usually by mnt_new_update()) action. * * Returns: 0 on success, -1 in case of error. */ int mnt_update_set_action(mnt_update *upd, int action) { assert(upd); if (!upd) return -EINVAL; upd->action = action; return 0; } /** * mnt_update_set_format: * @upd: update * @format: MNT_FMT_{MTAB,FSTAB,MOUNTINFO} * * Sets update file format, default is MNT_FMT_MTAB for paths that end with * "update", MNT_FMT_MOUNTINFO for paths that end with "mountinfo" and * MNT_FMT_FSTAB for paths that end with "fstab". * * Returns: 0 on success, -1 in case of error. */ int mnt_update_set_format(mnt_update *upd, int format) { assert(upd); if (!upd) return -EINVAL; upd->format = format; return 0; } /** * mnt_update_get_format * @upd: update * * Note that function returns zero if the format has not been defined (see * mnt_update_set_format()) or mnt_prepare_update() has not been called. * * Returns: MNT_FMT_{MTAB,FSTAB,MOUNTINFO} or 0. */ int mnt_update_get_format(mnt_update *upd) { return upd ? upd->format : 0; } /** * mnt_update_set_fs: * @upd: update * @fs: filesystem to write to file * * This function replaces the current setting related to the current FS. Note that * format, old target and mountflags are not reseted. * * Returns; 0 on success, -1 in case of error. */ int mnt_update_set_fs(mnt_update *upd, const mnt_fs *fs) { mnt_fs *x = NULL; assert(upd); if (!upd) return -EINVAL; if (fs) { x = mnt_copy_fs(fs); if (!x) return -ENOMEM; } mnt_free_fs(upd->fs); upd->fs = x; upd->pointless = FALSE; return 0; } /** * mnt_update_set_mountflags: * @upd: update * @flags: MS_{REMOUNT,MOVE,BIND,...} * * Sets mount flags for mount/umount action. The flags are also * extracted from mount options by mnt_prepare_update(). The mount flags * are used for mtab update to differentiate between move, remount, ... * * Returns: 0 on success, -1 in case of error. */ int mnt_update_set_mountflags(mnt_update *upd, unsigned long flags) { assert(upd); if (!upd) return -EINVAL; upd->mountflags = flags; return 0; } /** * mnt_update_get_lock: * @upd: update * * This function should not be called before mnt_prepare_update(). The lock * is initialized when mtab update is required only. * * Note that after mnt_update_disable_lock(mt, TRUE) or after mnt_free_update() * the lock will be automatically deallocated. * * Returns: libmount lock handler or NULL if locking is disabled or update is * not prepared yet. */ mnt_lock *mnt_update_get_lock(mnt_update *upd) { return upd ? upd->lc : NULL; } /** * mnt_update_disable_lock: * @upd: update * @disable: TRUE/FALSE * * Enable or disable update locking, the locking is enabled by default. * * Returns: 0 on success, -1 in case of error. */ int mnt_update_disable_lock(mnt_update *upd, int disable) { if (!upd) return -1; if (disable) { mnt_free_lock(upd->lc); upd->lc = NULL; } upd->nolock = disable; return 0; } /** * mnt_update_set_old_target: * @upd: update * @target: old mountpoint * * Sets the original target for the MS_MOVE operation. * * Returns: 0 on success, -1 in case of error. */ int mnt_update_set_old_target(mnt_update *upd, const char *target) { char *p = NULL; if (!upd) return -EINVAL; if (target) { p = strdup(target); if (!p) return -1; } free(upd->old_target); upd->old_target = p; return 0; } /* * The format is same as /proc/self/mountinfo, but it contains userspace * mount options and some unnecessary fields are ignored. */ static int fprintf_mountinfo_fs(FILE *f, mnt_fs *fs) { char *root = NULL, *target = NULL, *optstr = NULL, *fstype = NULL, *source = NULL; int rc = -1; dev_t devno; assert(fs); assert(f); if (!fs || !f) return -EINVAL; devno = mnt_fs_get_devno(fs); source = mangle(mnt_fs_get_source(fs)); root = mangle(mnt_fs_get_root(fs)); target = mangle(mnt_fs_get_target(fs)); fstype = mangle(mnt_fs_get_fstype(fs)); optstr = mangle(mnt_fs_get_optstr(fs)); if (!root || !target || !optstr) goto done; rc = fprintf(f, "%i %i %u:%u %s %s %s - %s %s %s\n", mnt_fs_get_id(fs), mnt_fs_get_parent_id(fs), major(devno), minor(devno), root, target, optstr, fstype ? fstype : "auto", source ? source : "none", "none"); rc = 0; done: free(root); free(target); free(optstr); free(fstype); free(source); return rc; } /* mtab and fstab update */ static int fprintf_mtab_fs(FILE *f, mnt_fs *fs) { char *m1 = NULL, *m2 = NULL, *m3 = NULL, *m4 = NULL; int rc = -1; assert(fs); assert(f); if (!fs || !f) return -EINVAL; m1 = mangle(mnt_fs_get_source(fs)); m2 = mangle(mnt_fs_get_target(fs)); m3 = mangle(mnt_fs_get_fstype(fs)); m4 = mangle(mnt_fs_get_optstr(fs)); if (!m1 || !m2 || !m3 || !m4) goto done; rc = fprintf(f, "%s %s %s %s %d %d\n", m1, m2, m3, m4, mnt_fs_get_freq(fs), mnt_fs_get_passno(fs)); rc = 0; done: free(m1); free(m2); free(m3); free(m4); return rc; } static int update_file(const char *filename, int fmt, mnt_tab *tb) { mnt_iter itr; mnt_fs *fs; FILE *f = NULL; char tmpname[PATH_MAX]; struct stat st; int fd; int (*line_fn)(FILE *, mnt_fs *) = fprintf_mountinfo_fs; assert(tb); if (!tb) return -EINVAL; DBG(UPDATE, mnt_debug("%s: update from tab %p", filename, tb)); if (snprintf(tmpname, sizeof(tmpname), "%s.tmp", filename) >= sizeof(tmpname)) goto error; f = fopen(tmpname, "w"); if (!f) goto error; if (fmt == MNT_FMT_MTAB || fmt == MNT_FMT_FSTAB) line_fn = fprintf_mtab_fs; mnt_reset_iter(&itr, MNT_ITER_FORWARD); while(mnt_tab_next_fs(tb, &itr, &fs) == 0) line_fn(f, fs); 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: DBG(UPDATE, mnt_debug("%s: update from tab %p failed", filename, tb)); if (f) fclose(f); return errno ? -errno : -1; } static int set_fs_root(mnt_update *upd, mnt_fs *fs) { char *root = NULL, *mnt = NULL; const char *fstype; char *optstr; mnt_tab *tb = NULL; if (upd->mountflags & MS_REMOUNT) return 0; optstr = (char *) mnt_fs_get_optstr(fs); fstype = mnt_fs_get_fstype(fs); /* * bind-mount -- get fs-root and source device for the source filesystem */ if (upd->mountflags & MS_BIND) { const char *src, *src_root; mnt_fs *src_fs; src = mnt_fs_get_srcpath(fs); if (!src) goto err; mnt = mnt_get_mountpoint(src); if (!mnt) goto err; root = mnt_get_fs_root(src, mnt); tb = mnt_new_tab_from_file(_PATH_PROC_MOUNTINFO); if (!tb) goto dflt; src_fs = mnt_tab_find_target(tb, mnt, MNT_ITER_BACKWARD); if (!src_fs) goto dflt; /* set device name and fs */ src = mnt_fs_get_srcpath(src_fs); mnt_fs_set_source(fs, src); mnt_fs_set_fstype(fs, mnt_fs_get_fstype(src_fs)); /* on btrfs the subvolume is used as fs-root in * /proc/self/mountinfo, so we have get the original subvolume * name from src_fs and prepend the subvolume name to the * fs-root path */ src_root = mnt_fs_get_root(src_fs); if (src_root && !startswith(root, src_root)) { size_t sz = strlen(root) + strlen(src_root) + 1; char *tmp = malloc(sz); if (!tmp) goto err; snprintf(tmp, sz, "%s%s", src_root, root); free(root); root = tmp; } } /* * btrfs-subvolume mount -- get subvolume name and use it as a root-fs path */ else if (fstype && !strcmp(fstype, "btrfs")) { char *vol = NULL, *p; size_t sz, volsz = 0; if (mnt_optstr_get_option(optstr, "subvol", &vol, &volsz)) goto dflt; sz = volsz; if (*vol != '/') sz++; root = malloc(sz + 1); if (!root) goto err; p = root; if (*vol != '/') *p++ = '/'; memcpy(p, vol, volsz); *(root + sz) = '\0'; } dflt: mnt_free_tab(tb); if (!root) root = strdup("/"); if (!root) goto err; fs->root = root; free(mnt); return 0; err: free(root); free(mnt); return -1; } /** * mnt_update_is_pointless: * @upd: update * * This function returns 1 if the previous mnt_prepare_update() call returned 1 * too. * * Returns: status of the update. */ int mnt_update_is_pointless(mnt_update *upd) { return upd ? upd->pointless : 0; } /** * mnt_prepare_update: * @upd: update * * Prepares internal data for mtab update: * - set filename if mnt_update_set_filename() wasn't called * - set file format if mnt_update_set_format() wasn't called * - bitwise-OR mountflags from mount options * - for /var/run/mount/mountinfo: * * evaluate if the update is necessary * * set fs root and devname for bind mount and btrfs subvolumes * * removes unnecessary mount options * - allocate update_lock if necessary * * This function has to be always called before mount(2). The mnt_update_file() * should not be called if mnt_prepare_update() returns non-zero value. * * Returns: 0 on success, 1 if update is unnecessary, negative in case of error */ int mnt_prepare_update(mnt_update *upd) { char *u = NULL; const char *o = NULL; int rc = 0; assert(upd); assert(upd->fs); if (!upd || !upd->fs) return -EINVAL; DBG(UPDATE, mnt_debug_h(upd, "prepare update (target=%s, source=%s, optstr=%s)", mnt_fs_get_target(upd->fs), mnt_fs_get_source(upd->fs), mnt_fs_get_optstr(upd->fs))); if (!upd->filename) { const char *p = mnt_get_writable_mtab_path(); if (!p) { if (errno) { rc = -errno; goto err; /* EACCES? */ } goto nothing; /* no mtab */ } upd->filename = strdup(p); if (!upd->filename) { rc = -ENOMEM; goto err; } } DBG(UPDATE, mnt_debug_h(upd, "filename: %s", upd->filename)); if (!upd->format) { if (endswith(upd->filename, "mountinfo")) upd->format = MNT_FMT_MOUNTINFO; else if (endswith(upd->filename, "fstab")) upd->format = MNT_FMT_FSTAB; else upd->format = MNT_FMT_MTAB; } DBG(UPDATE, mnt_debug_h(upd, "format: %s", upd->format == MNT_FMT_MOUNTINFO ? "mountinfo" : upd->format == MNT_FMT_FSTAB ? "fstab" : "mtab")); /* TODO: cannonicalize source and target paths on mnt->fs */ if (upd->format != MNT_FMT_FSTAB) { unsigned long fl = 0; o = mnt_fs_get_optstr(upd->fs); if (o && !mnt_optstr_get_mountflags(o, &fl)) upd->mountflags |= fl; } /* umount */ if (upd->action == MNT_ACT_UMOUNT) goto done; /* * A) classic /etc/mtab or /etc/fstab update */ if (upd->format != MNT_FMT_MOUNTINFO) goto done; /* * B) /var/run/mount/mountinfo * - remove all non-userspace mount options */ if (upd->mountflags & MS_REMOUNT) { rc = mnt_split_optstr(o, &u, NULL, NULL, MNT_NOMTAB, 0); if (rc) goto err; rc = __mnt_fs_set_optstr_ptr(upd->fs, u, FALSE); if (rc) goto err; u = NULL; } else { if (!o) goto nothing; /* no options */ rc = mnt_split_optstr(o, &u, NULL, NULL, MNT_NOMTAB, 0); if (rc) goto err; if (!u) goto nothing; /* no userpsace options */ rc = set_fs_root(upd, upd->fs); if (rc) goto err; rc = __mnt_fs_set_optstr_ptr(upd->fs, u, FALSE); if (rc) goto err; u = NULL; } done: if (!upd->nolock && !upd->lc) { upd->lc = mnt_new_lock(upd->filename, 0); if (!upd->lc) { rc = -ENOMEM; goto err; } } DBG(UPDATE, mnt_debug_h(upd, "prepare update: success")); return 0; err: free(u); DBG(UPDATE, mnt_debug_h(upd, "prepare update: failed")); return rc; nothing: upd->pointless = TRUE; DBG(UPDATE, mnt_debug_h(upd, "prepare update: pointless")); return 1; } static int add_entry(mnt_update *upd) { FILE *f; int rc = -1; assert(upd); DBG(UPDATE, mnt_debug_h(upd, "add entry")); if (upd->lc) mnt_lock_file(upd->lc); f = fopen(upd->filename, "a+"); if (f) { if (upd->format == MNT_FMT_MOUNTINFO) rc = fprintf_mountinfo_fs(f, upd->fs); else rc = fprintf_mtab_fs(f, upd->fs); fclose(f); } if (upd->lc) mnt_unlock_file(upd->lc); return rc; } static int remove_entry(mnt_update *upd) { const char *target; mnt_tab *tb = NULL; mnt_fs *fs = NULL; int rc = -1; assert(upd); assert(upd->filename); target = mnt_fs_get_target(upd->fs); assert(target); DBG(UPDATE, mnt_debug_h(upd, "remove entry (target %s)", target)); if (upd->lc) mnt_lock_file(upd->lc); tb = mnt_new_tab_from_file(upd->filename); if (!tb) goto done; fs = mnt_tab_find_target(tb, target, MNT_ITER_BACKWARD); if (!fs) { rc = 0; /* no error if the file does not contain the target */ goto done; } mnt_tab_remove_fs(tb, fs); if (!update_file(upd->filename, upd->format, tb)) rc = 0; done: if (upd->lc) mnt_unlock_file(upd->lc); mnt_free_tab(tb); mnt_free_fs(fs); return rc; } static int modify_target(mnt_update *upd) { mnt_tab *tb = NULL; mnt_fs *fs = NULL; int rc = -1; assert(upd); assert(upd->old_target); assert(upd->filename); assert(mnt_fs_get_target(upd->fs)); if (!upd->old_target) return -1; DBG(UPDATE, mnt_debug_h(upd, "modify target (%s->%s)", upd->old_target, mnt_fs_get_target(upd->fs))); if (upd->lc) mnt_lock_file(upd->lc); tb = mnt_new_tab_from_file(upd->filename); if (!tb) goto done; fs = mnt_tab_find_target(tb, upd->old_target, MNT_ITER_BACKWARD); if (!fs) { rc = 0; /* no error if the file does not contain the target */ goto done; } mnt_fs_set_target(fs, mnt_fs_get_target(upd->fs)); if (!update_file(upd->filename, upd->format, tb)) rc = 0; done: if (upd->lc) mnt_unlock_file(upd->lc); mnt_free_tab(tb); return rc; } static int modify_options(mnt_update *upd) { mnt_tab *tb = NULL; mnt_fs *fs = NULL, *rem_fs = NULL; int rc = -1; const char *target = mnt_fs_get_target(upd->fs); assert(target); assert(upd->filename); DBG(UPDATE, mnt_debug_h(upd, "modify options (target %s)", target)); if (upd->lc) mnt_lock_file(upd->lc); tb = mnt_new_tab_from_file(upd->filename); if (!tb) goto done; fs = mnt_tab_find_target(tb, target, MNT_ITER_BACKWARD); if (!fs) { rc = 0; /* no error if the file does not contain the target */ goto done; } if (upd->format == MNT_FMT_MOUNTINFO && !mnt_fs_get_optstr(upd->fs)) { mnt_tab_remove_fs(tb, fs); rem_fs = fs; } else rc = __mnt_fs_set_optstr(fs, mnt_fs_get_optstr(upd->fs), FALSE); if (!update_file(upd->filename, upd->format, tb)) rc = 0; done: if (upd->lc) mnt_unlock_file(upd->lc); mnt_free_tab(tb); mnt_free_fs(rem_fs); return rc; } /** * mnt_update_file: * @upd: update * * Updates the update file. The behavior of this function is undefined if * mnt_prepare_update() has not been called. The request to update file will * be ignored for pointless updates (see mnt_update_is_pointless()). * * Returns: 0 on success, -1 in case of error. */ int mnt_update_file(mnt_update *upd) { assert(upd); assert(upd->filename); assert(upd->format); assert(upd->fs); if (!upd || !upd->fs) return -1; if (upd->pointless) { DBG(UPDATE, mnt_debug_h(upd, "ingnore update requiest (pointless)")); return 0; } DBG(UPDATE, mnt_debug_h(upd, "update (target %s)", mnt_fs_get_target(upd->fs))); /* * umount */ if (upd->action == MNT_ACT_UMOUNT) return remove_entry(upd); /* * mount */ if (upd->action == MNT_ACT_MOUNT) { if (upd->mountflags & MS_REMOUNT) return modify_options(upd); if (upd->mountflags & MS_MOVE) return modify_target(upd); return add_entry(upd); /* mount */ } return -1; } #ifdef TEST_PROGRAM #include mnt_lock *lock; static void lock_fallback(void) { if (lock) mnt_unlock_file(lock); } static int update(mnt_update *upd) { int rc; /* * Note that mount(2) syscal should be called *after* * mnt_prepare_update() and *before* mnt_update_file() */ rc = mnt_prepare_update(upd); if (!rc) { /* setup lock fallback */ int rc; lock = mnt_update_get_lock(upd); atexit(lock_fallback); rc = mnt_update_file(upd); lock = NULL; return rc; } if (rc == 1) return 0; fprintf(stderr, "update: failed to prepare update\n"); return -1; } int test_add(struct mtest *ts, int argc, char *argv[]) { mnt_fs *fs = mnt_new_fs(); mnt_update *upd; int rc = -1; if (argc < 5 || !fs) return -1; mnt_fs_set_source(fs, argv[1]); mnt_fs_set_target(fs, argv[2]); mnt_fs_set_fstype(fs, argv[3]); mnt_fs_set_optstr(fs, argv[4]); upd = mnt_new_update(MNT_ACT_MOUNT, 0, fs); if (!upd) return -1; rc = update(upd); mnt_free_update(upd); mnt_free_fs(fs); return rc; } int test_add_fstab(struct mtest *ts, int argc, char *argv[]) { mnt_fs *fs = mnt_new_fs(); mnt_update *upd; int rc = -1; if (argc < 7 || !fs) return -1; mnt_fs_set_source(fs, argv[1]); mnt_fs_set_target(fs, argv[2]); mnt_fs_set_fstype(fs, argv[3]); mnt_fs_set_optstr(fs, argv[4]); mnt_fs_set_freq(fs, atoi(argv[5])); mnt_fs_set_passno(fs, atoi(argv[6])); /* this is tricky -- to add to fstab use "MNT_ACT_MOUNT" */ upd = mnt_new_update(MNT_ACT_MOUNT, 0, fs); if (!upd) return -1; mnt_update_disable_lock(upd, TRUE); /* lock is unnecessary */ mnt_update_set_filename(upd, mnt_get_fstab_path()); mnt_update_set_format(upd, MNT_FMT_FSTAB); rc = update(upd); mnt_free_update(upd); mnt_free_fs(fs); return rc; } int test_remove(struct mtest *ts, int argc, char *argv[]) { mnt_fs *fs = mnt_new_fs(); mnt_update *upd; int rc = -1; if (argc < 2 || !fs) return -1; mnt_fs_set_target(fs, argv[1]); upd = mnt_new_update(MNT_ACT_UMOUNT, 0, fs); if (!upd) return -1; rc = update(upd); mnt_free_update(upd); mnt_free_fs(fs); return rc; } int test_move(struct mtest *ts, int argc, char *argv[]) { mnt_fs *fs = mnt_new_fs(); mnt_update *upd; int rc = -1; if (argc < 3 || !fs) return -1; mnt_fs_set_target(fs, argv[2]); upd = mnt_new_update(MNT_ACT_MOUNT, MS_MOVE, fs); if (!upd) return -1; mnt_update_set_old_target(upd, argv[1]); rc = update(upd); mnt_free_update(upd); mnt_free_fs(fs); return rc; } int test_remount(struct mtest *ts, int argc, char *argv[]) { mnt_fs *fs = mnt_new_fs(); mnt_update *upd; int rc = -1; if (argc < 3 || !fs) return -1; mnt_fs_set_target(fs, argv[1]); mnt_fs_set_optstr(fs, argv[2]); upd = mnt_new_update(MNT_ACT_MOUNT, MS_REMOUNT, fs); if (!upd) return -1; rc = update(upd); mnt_free_update(upd); mnt_free_fs(fs); return rc; } int main(int argc, char *argv[]) { struct mtest tss[] = { { "--add", test_add, " add line to mtab" }, { "--remove", test_remove, " MS_REMOUNT mtab change" }, { "--move", test_move, " MS_MOVE mtab change" }, { "--remount",test_remount, " MS_REMOUNT mtab change" }, { "--add-fstab", test_add_fstab, " add line to fstab" }, { NULL } }; return mnt_run_test(tss, argc, argv); } #endif /* TEST_PROGRAM */