| /* |
| * Copyright (C) 2010 Karel Zak <kzak@redhat.com> |
| * |
| * This file may be redistributed under the terms of the |
| * GNU Lesser General Public License. |
| */ |
| |
| /** |
| * SECTION: update |
| * @title: Tables update |
| * @short_description: userspace mount information management |
| * |
| * The struct libmnt_update provides an abstraction to manage mount options in |
| * userspace independently of system configuration. This low-level API works on |
| * systems both with and without /etc/mtab. On systems without the regular /etc/mtab |
| * file, the userspace mount options (e.g. user=) are stored in the /run/mount/utab |
| * file. |
| * |
| * It's recommended to use high-level struct libmnt_context API. |
| */ |
| #include <sys/file.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| |
| #include "mountP.h" |
| #include "mangle.h" |
| #include "pathnames.h" |
| |
| struct libmnt_update { |
| char *target; |
| struct libmnt_fs *fs; |
| char *filename; |
| unsigned long mountflags; |
| int userspace_only; |
| int ready; |
| |
| struct libmnt_table *mountinfo; |
| }; |
| |
| static int set_fs_root(struct libmnt_update *upd, struct libmnt_fs *fs, unsigned long mountflags); |
| static int utab_new_entry(struct libmnt_update *upd, struct libmnt_fs *fs, unsigned long mountflags); |
| |
| /** |
| * mnt_new_update: |
| * |
| * Returns: newly allocated update handler |
| */ |
| struct libmnt_update *mnt_new_update(void) |
| { |
| struct libmnt_update *upd; |
| |
| upd = calloc(1, sizeof(*upd)); |
| if (!upd) |
| return NULL; |
| |
| DBG(UPDATE, ul_debugobj(upd, "allocate")); |
| return upd; |
| } |
| |
| /** |
| * mnt_free_update: |
| * @upd: update |
| * |
| * Deallocates struct libmnt_update handler. |
| */ |
| void mnt_free_update(struct libmnt_update *upd) |
| { |
| if (!upd) |
| return; |
| |
| DBG(UPDATE, ul_debugobj(upd, "free")); |
| |
| mnt_unref_fs(upd->fs); |
| mnt_unref_table(upd->mountinfo); |
| free(upd->target); |
| free(upd->filename); |
| free(upd); |
| } |
| |
| /* |
| * Returns 0 on success, <0 in case of error. |
| */ |
| int mnt_update_set_filename(struct libmnt_update *upd, const char *filename, |
| int userspace_only) |
| { |
| const char *path = NULL; |
| int rw = 0; |
| |
| if (!upd) |
| return -EINVAL; |
| |
| /* filename explicitly defined */ |
| if (filename) { |
| char *p = strdup(filename); |
| if (!p) |
| return -ENOMEM; |
| |
| upd->userspace_only = userspace_only; |
| free(upd->filename); |
| upd->filename = p; |
| } |
| |
| if (upd->filename) |
| return 0; |
| |
| /* detect tab filename -- /etc/mtab or /run/mount/utab |
| */ |
| #ifdef USE_LIBMOUNT_SUPPORT_MTAB |
| mnt_has_regular_mtab(&path, &rw); |
| #endif |
| if (!rw) { |
| path = NULL; |
| mnt_has_regular_utab(&path, &rw); |
| if (!rw) |
| return -EACCES; |
| upd->userspace_only = TRUE; |
| } |
| upd->filename = strdup(path); |
| if (!upd->filename) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| /** |
| * mnt_update_get_filename: |
| * @upd: update |
| * |
| * This function returns the file name (e.g. /etc/mtab) of the up-dated file. |
| * |
| * Returns: pointer to filename that will be updated or NULL in case of error. |
| */ |
| const char *mnt_update_get_filename(struct libmnt_update *upd) |
| { |
| return upd ? upd->filename : NULL; |
| } |
| |
| /** |
| * mnt_update_is_ready: |
| * @upd: update handler |
| * |
| * Returns: 1 if entry described by @upd is successfully prepared and will be |
| * written to the mtab/utab file. |
| */ |
| int mnt_update_is_ready(struct libmnt_update *upd) |
| { |
| return upd ? upd->ready : FALSE; |
| } |
| |
| /** |
| * mnt_update_set_fs: |
| * @upd: update handler |
| * @mountflags: MS_* flags |
| * @target: umount target, must be NULL for mount |
| * @fs: mount filesystem description, must be NULL for umount |
| * |
| * Returns: <0 in case on error, 0 on success, 1 if update is unnecessary. |
| */ |
| int mnt_update_set_fs(struct libmnt_update *upd, unsigned long mountflags, |
| const char *target, struct libmnt_fs *fs) |
| { |
| int rc; |
| |
| if (!upd) |
| return -EINVAL; |
| if ((mountflags & MS_MOVE) && (!fs || !mnt_fs_get_srcpath(fs))) |
| return -EINVAL; |
| if (target && fs) |
| return -EINVAL; |
| |
| DBG(UPDATE, ul_debugobj(upd, |
| "resetting FS [target=%s, flags=0x%08lx]", |
| target, mountflags)); |
| if (fs) { |
| DBG(UPDATE, ul_debugobj(upd, "FS template:")); |
| DBG(UPDATE, mnt_fs_print_debug(fs, stderr)); |
| } |
| |
| mnt_unref_fs(upd->fs); |
| free(upd->target); |
| upd->ready = FALSE; |
| upd->fs = NULL; |
| upd->target = NULL; |
| upd->mountflags = 0; |
| |
| if (mountflags & MS_PROPAGATION) |
| return 1; |
| |
| upd->mountflags = mountflags; |
| |
| rc = mnt_update_set_filename(upd, NULL, 0); |
| if (rc) { |
| DBG(UPDATE, ul_debugobj(upd, "no writable file available [rc=%d]", rc)); |
| return rc; /* error or no file available (rc = 1) */ |
| } |
| if (target) { |
| upd->target = strdup(target); |
| if (!upd->target) |
| return -ENOMEM; |
| |
| } else if (fs) { |
| if (upd->userspace_only && !(mountflags & MS_MOVE)) { |
| rc = utab_new_entry(upd, fs, mountflags); |
| if (rc) |
| return rc; |
| } else { |
| upd->fs = mnt_copy_mtab_fs(fs); |
| if (!upd->fs) |
| return -ENOMEM; |
| |
| } |
| } |
| |
| |
| DBG(UPDATE, ul_debugobj(upd, "ready")); |
| upd->ready = TRUE; |
| return 0; |
| } |
| |
| /** |
| * mnt_update_get_fs: |
| * @upd: update |
| * |
| * Returns: update filesystem entry or NULL |
| */ |
| struct libmnt_fs *mnt_update_get_fs(struct libmnt_update *upd) |
| { |
| return upd ? upd->fs : NULL; |
| } |
| |
| /** |
| * mnt_update_get_mflags: |
| * @upd: update |
| * |
| * Returns: mount flags as was set by mnt_update_set_fs() |
| */ |
| unsigned long mnt_update_get_mflags(struct libmnt_update *upd) |
| { |
| return upd ? upd->mountflags : 0; |
| } |
| |
| /** |
| * mnt_update_force_rdonly: |
| * @upd: update |
| * @rdonly: is read-only? |
| * |
| * Returns: 0 on success and negative number in case of error. |
| */ |
| int mnt_update_force_rdonly(struct libmnt_update *upd, int rdonly) |
| { |
| int rc = 0; |
| |
| if (!upd || !upd->fs) |
| return -EINVAL; |
| |
| if (rdonly && (upd->mountflags & MS_RDONLY)) |
| return 0; |
| if (!rdonly && !(upd->mountflags & MS_RDONLY)) |
| return 0; |
| |
| if (!upd->userspace_only) { |
| /* /etc/mtab -- we care about VFS options there */ |
| const char *o = mnt_fs_get_options(upd->fs); |
| char *n = o ? strdup(o) : NULL; |
| |
| if (n) |
| mnt_optstr_remove_option(&n, rdonly ? "rw" : "ro"); |
| if (!mnt_optstr_prepend_option(&n, rdonly ? "ro" : "rw", NULL)) |
| rc = mnt_fs_set_options(upd->fs, n); |
| |
| free(n); |
| } |
| |
| if (rdonly) |
| upd->mountflags &= ~MS_RDONLY; |
| else |
| upd->mountflags |= MS_RDONLY; |
| |
| return rc; |
| } |
| |
| |
| /* |
| * Allocates an utab entry (upd->fs) for mount/remount. This function should be |
| * called *before* mount(2) syscall. The @fs is used as a read-only template. |
| * |
| * Returns: 0 on success, negative number on error, 1 if utab's update is |
| * unnecessary. |
| */ |
| static int utab_new_entry(struct libmnt_update *upd, struct libmnt_fs *fs, |
| unsigned long mountflags) |
| { |
| int rc = 0; |
| const char *o, *a; |
| char *u = NULL; |
| |
| assert(fs); |
| assert(upd); |
| assert(upd->fs == NULL); |
| assert(!(mountflags & MS_MOVE)); |
| |
| DBG(UPDATE, ul_debug("prepare utab entry")); |
| |
| o = mnt_fs_get_user_options(fs); |
| a = mnt_fs_get_attributes(fs); |
| upd->fs = NULL; |
| |
| if (o) { |
| /* remove non-mtab options */ |
| rc = mnt_optstr_get_options(o, &u, |
| mnt_get_builtin_optmap(MNT_USERSPACE_MAP), |
| MNT_NOMTAB); |
| if (rc) |
| goto err; |
| } |
| |
| if (!u && !a) { |
| DBG(UPDATE, ul_debug("utab entry unnecessary (no options)")); |
| return 1; |
| } |
| |
| /* allocate the entry */ |
| upd->fs = mnt_copy_fs(NULL, fs); |
| if (!upd->fs) { |
| rc = -ENOMEM; |
| goto err; |
| } |
| |
| rc = mnt_fs_set_options(upd->fs, u); |
| if (rc) |
| goto err; |
| rc = mnt_fs_set_attributes(upd->fs, a); |
| if (rc) |
| goto err; |
| |
| if (!(mountflags & MS_REMOUNT)) { |
| rc = set_fs_root(upd, fs, mountflags); |
| if (rc) |
| goto err; |
| } |
| |
| free(u); |
| DBG(UPDATE, ul_debug("utab entry OK")); |
| return 0; |
| err: |
| free(u); |
| mnt_unref_fs(upd->fs); |
| upd->fs = NULL; |
| return rc; |
| } |
| |
| /* |
| * Sets fs-root and fs-type to @upd->fs according to the @fs template and |
| * @mountfalgs. For MS_BIND mountflag it reads information about the source |
| * filesystem from /proc/self/mountinfo. |
| */ |
| static int set_fs_root(struct libmnt_update *upd, struct libmnt_fs *fs, |
| unsigned long mountflags) |
| { |
| struct libmnt_fs *src_fs; |
| char *fsroot = NULL; |
| const char *src, *fstype; |
| int rc = 0; |
| |
| DBG(UPDATE, ul_debug("setting FS root")); |
| |
| assert(upd); |
| assert(upd->fs); |
| assert(fs); |
| |
| fstype = mnt_fs_get_fstype(fs); |
| |
| if (mountflags & MS_BIND) { |
| if (!upd->mountinfo) |
| upd->mountinfo = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO); |
| src = mnt_fs_get_srcpath(fs); |
| if (src) { |
| rc = mnt_fs_set_bindsrc(upd->fs, src); |
| if (rc) |
| goto err; |
| } |
| |
| } else if (fstype && (strcmp(fstype, "btrfs") == 0 || strcmp(fstype, "auto") == 0)) { |
| if (!upd->mountinfo) |
| upd->mountinfo = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO); |
| } |
| |
| src_fs = mnt_table_get_fs_root(upd->mountinfo, fs, |
| mountflags, &fsroot); |
| if (src_fs) { |
| src = mnt_fs_get_srcpath(src_fs); |
| rc = mnt_fs_set_source(upd->fs, src); |
| if (rc) |
| goto err; |
| |
| mnt_fs_set_fstype(upd->fs, mnt_fs_get_fstype(src_fs)); |
| } |
| |
| upd->fs->root = fsroot; |
| return 0; |
| err: |
| free(fsroot); |
| return rc; |
| } |
| |
| /* mtab and fstab update -- returns zero on success |
| */ |
| static int fprintf_mtab_fs(FILE *f, struct libmnt_fs *fs) |
| { |
| const char *o, *src, *fstype, *comm; |
| char *m1, *m2, *m3, *m4; |
| int rc; |
| |
| assert(fs); |
| assert(f); |
| |
| comm = mnt_fs_get_comment(fs); |
| src = mnt_fs_get_source(fs); |
| fstype = mnt_fs_get_fstype(fs); |
| o = mnt_fs_get_options(fs); |
| |
| m1 = src ? mangle(src) : "none"; |
| m2 = mangle(mnt_fs_get_target(fs)); |
| m3 = fstype ? mangle(fstype) : "none"; |
| m4 = o ? mangle(o) : "rw"; |
| |
| if (m1 && m2 && m3 && m4) { |
| if (comm) |
| fputs(comm, f); |
| rc = fprintf(f, "%s %s %s %s %d %d\n", |
| m1, m2, m3, m4, |
| mnt_fs_get_freq(fs), |
| mnt_fs_get_passno(fs)); |
| if (rc > 0) |
| rc = 0; |
| } else |
| rc = -ENOMEM; |
| |
| if (src) |
| free(m1); |
| free(m2); |
| if (fstype) |
| free(m3); |
| if (o) |
| free(m4); |
| |
| return rc; |
| } |
| |
| static int fprintf_utab_fs(FILE *f, struct libmnt_fs *fs) |
| { |
| char *p; |
| int rc = 0; |
| |
| if (!fs || !f) |
| return -EINVAL; |
| |
| p = mangle(mnt_fs_get_source(fs)); |
| if (p) { |
| rc = fprintf(f, "SRC=%s ", p); |
| free(p); |
| } |
| if (rc >= 0) { |
| p = mangle(mnt_fs_get_target(fs)); |
| if (p) { |
| rc = fprintf(f, "TARGET=%s ", p); |
| free(p); |
| } |
| } |
| if (rc >= 0) { |
| p = mangle(mnt_fs_get_root(fs)); |
| if (p) { |
| rc = fprintf(f, "ROOT=%s ", p); |
| free(p); |
| } |
| } |
| if (rc >= 0) { |
| p = mangle(mnt_fs_get_bindsrc(fs)); |
| if (p) { |
| rc = fprintf(f, "BINDSRC=%s ", p); |
| free(p); |
| } |
| } |
| if (rc >= 0) { |
| p = mangle(mnt_fs_get_attributes(fs)); |
| if (p) { |
| rc = fprintf(f, "ATTRS=%s ", p); |
| free(p); |
| } |
| } |
| if (rc >= 0) { |
| p = mangle(mnt_fs_get_user_options(fs)); |
| if (p) { |
| rc = fprintf(f, "OPTS=%s", p); |
| free(p); |
| } |
| } |
| if (rc >= 0) |
| rc = fprintf(f, "\n"); |
| |
| if (rc > 0) |
| rc = 0; /* success */ |
| return rc; |
| } |
| |
| static int update_table(struct libmnt_update *upd, struct libmnt_table *tb) |
| { |
| FILE *f; |
| int rc, fd; |
| char *uq = NULL; |
| |
| if (!tb || !upd->filename) |
| return -EINVAL; |
| |
| DBG(UPDATE, ul_debugobj(upd, "%s: updating", upd->filename)); |
| |
| fd = mnt_open_uniq_filename(upd->filename, &uq); |
| if (fd < 0) |
| return fd; /* error */ |
| |
| f = fdopen(fd, "w" UL_CLOEXECSTR); |
| if (f) { |
| struct stat st; |
| struct libmnt_iter itr; |
| struct libmnt_fs *fs; |
| |
| mnt_reset_iter(&itr, MNT_ITER_FORWARD); |
| |
| if (tb->comms && mnt_table_get_intro_comment(tb)) |
| fputs(mnt_table_get_intro_comment(tb), f); |
| |
| while(mnt_table_next_fs(tb, &itr, &fs) == 0) { |
| if (upd->userspace_only) |
| rc = fprintf_utab_fs(f, fs); |
| else |
| rc = fprintf_mtab_fs(f, fs); |
| if (rc) { |
| DBG(UPDATE, ul_debugobj(upd, |
| "%s: write entry failed: %m", uq)); |
| goto leave; |
| } |
| } |
| if (tb->comms && mnt_table_get_trailing_comment(tb)) |
| fputs(mnt_table_get_trailing_comment(tb), f); |
| |
| if (fflush(f) != 0) { |
| rc = -errno; |
| DBG(UPDATE, ul_debugobj(upd, "%s: fflush failed: %m", uq)); |
| goto leave; |
| } |
| |
| rc = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) ? -errno : 0; |
| |
| if (!rc && stat(upd->filename, &st) == 0) |
| /* Copy uid/gid from the present file before renaming. */ |
| rc = fchown(fd, st.st_uid, st.st_gid) ? -errno : 0; |
| |
| fclose(f); |
| f = NULL; |
| |
| if (!rc) |
| rc = rename(uq, upd->filename) ? -errno : 0; |
| } else { |
| rc = -errno; |
| close(fd); |
| } |
| |
| leave: |
| if (f) |
| fclose(f); |
| |
| unlink(uq); /* be paranoid */ |
| free(uq); |
| DBG(UPDATE, ul_debugobj(upd, "%s: done [rc=%d]", upd->filename, rc)); |
| return rc; |
| } |
| |
| /** |
| * mnt_table_write_file |
| * @tb: parsed file (e.g. fstab) |
| * @file: target |
| * |
| * This function writes @tb to @file. |
| * |
| * Returns: 0 on success, negative number on error. |
| */ |
| int mnt_table_write_file(struct libmnt_table *tb, FILE *file) |
| { |
| int rc = 0; |
| struct libmnt_iter itr; |
| struct libmnt_fs *fs; |
| |
| if (tb->comms && mnt_table_get_intro_comment(tb)) |
| fputs(mnt_table_get_intro_comment(tb), file); |
| |
| mnt_reset_iter(&itr, MNT_ITER_FORWARD); |
| while(mnt_table_next_fs(tb, &itr, &fs) == 0) { |
| rc = fprintf_mtab_fs(file, fs); |
| if (rc) |
| return rc; |
| } |
| if (tb->comms && mnt_table_get_trailing_comment(tb)) |
| fputs(mnt_table_get_trailing_comment(tb), file); |
| |
| if (fflush(file) != 0) |
| rc = -errno; |
| |
| DBG(TAB, ul_debugobj(tb, "write file done [rc=%d]", rc)); |
| return rc; |
| } |
| |
| /** |
| * mnt_table_replace_file |
| * @tb: parsed file (e.g. fstab) |
| * @filename: target |
| * |
| * This function replaces @file by the new content from @tb. |
| * |
| * Returns: 0 on success, negative number on error. |
| */ |
| int mnt_table_replace_file(struct libmnt_table *tb, const char *filename) |
| { |
| int fd, rc = 0; |
| FILE *f; |
| char *uq = NULL; |
| |
| DBG(TAB, ul_debugobj(tb, "%s: replacing", filename)); |
| |
| fd = mnt_open_uniq_filename(filename, &uq); |
| if (fd < 0) |
| return fd; /* error */ |
| |
| f = fdopen(fd, "w" UL_CLOEXECSTR); |
| if (f) { |
| struct stat st; |
| |
| mnt_table_write_file(tb, f); |
| |
| if (fflush(f) != 0) { |
| rc = -errno; |
| DBG(UPDATE, ul_debug("%s: fflush failed: %m", uq)); |
| goto leave; |
| } |
| |
| rc = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) ? -errno : 0; |
| |
| if (!rc && stat(filename, &st) == 0) |
| /* Copy uid/gid from the present file before renaming. */ |
| rc = fchown(fd, st.st_uid, st.st_gid) ? -errno : 0; |
| |
| fclose(f); |
| f = NULL; |
| |
| if (!rc) |
| rc = rename(uq, filename) ? -errno : 0; |
| } else { |
| rc = -errno; |
| close(fd); |
| } |
| |
| leave: |
| if (f) |
| fclose(f); |
| unlink(uq); |
| free(uq); |
| |
| DBG(TAB, ul_debugobj(tb, "replace done [rc=%d]", rc)); |
| return rc; |
| } |
| |
| static int add_file_entry(struct libmnt_table *tb, struct libmnt_update *upd) |
| { |
| struct libmnt_fs *fs; |
| |
| assert(upd); |
| |
| fs = mnt_copy_fs(NULL, upd->fs); |
| if (!fs) |
| return -ENOMEM; |
| |
| mnt_table_add_fs(tb, fs); |
| mnt_unref_fs(fs); |
| |
| return update_table(upd, tb); |
| } |
| |
| static int update_add_entry(struct libmnt_update *upd, struct libmnt_lock *lc) |
| { |
| struct libmnt_table *tb; |
| int rc = 0; |
| |
| assert(upd); |
| assert(upd->fs); |
| |
| DBG(UPDATE, ul_debugobj(upd, "%s: add entry", upd->filename)); |
| |
| if (lc) |
| rc = mnt_lock_file(lc); |
| if (rc) |
| return -MNT_ERR_LOCK; |
| |
| tb = __mnt_new_table_from_file(upd->filename, |
| upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1); |
| if (tb) |
| rc = add_file_entry(tb, upd); |
| if (lc) |
| mnt_unlock_file(lc); |
| |
| mnt_unref_table(tb); |
| return rc; |
| } |
| |
| static int update_remove_entry(struct libmnt_update *upd, struct libmnt_lock *lc) |
| { |
| struct libmnt_table *tb; |
| int rc = 0; |
| |
| assert(upd); |
| assert(upd->target); |
| |
| DBG(UPDATE, ul_debugobj(upd, "%s: remove entry", upd->filename)); |
| |
| if (lc) |
| rc = mnt_lock_file(lc); |
| if (rc) |
| return -MNT_ERR_LOCK; |
| |
| tb = __mnt_new_table_from_file(upd->filename, |
| upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1); |
| if (tb) { |
| struct libmnt_fs *rem = mnt_table_find_target(tb, upd->target, MNT_ITER_BACKWARD); |
| if (rem) { |
| mnt_table_remove_fs(tb, rem); |
| rc = update_table(upd, tb); |
| } |
| } |
| if (lc) |
| mnt_unlock_file(lc); |
| |
| mnt_unref_table(tb); |
| return rc; |
| } |
| |
| static int update_modify_target(struct libmnt_update *upd, struct libmnt_lock *lc) |
| { |
| struct libmnt_table *tb = NULL; |
| int rc = 0; |
| |
| assert(upd); |
| DBG(UPDATE, ul_debugobj(upd, "%s: modify target", upd->filename)); |
| |
| if (lc) |
| rc = mnt_lock_file(lc); |
| if (rc) |
| return -MNT_ERR_LOCK; |
| |
| tb = __mnt_new_table_from_file(upd->filename, |
| upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1); |
| if (tb) { |
| struct libmnt_fs *cur = mnt_table_find_target(tb, |
| mnt_fs_get_srcpath(upd->fs), MNT_ITER_BACKWARD); |
| if (cur) { |
| rc = mnt_fs_set_target(cur, mnt_fs_get_target(upd->fs)); |
| if (!rc) |
| rc = update_table(upd, tb); |
| } |
| } |
| |
| if (lc) |
| mnt_unlock_file(lc); |
| |
| mnt_unref_table(tb); |
| return rc; |
| } |
| |
| static int update_modify_options(struct libmnt_update *upd, struct libmnt_lock *lc) |
| { |
| struct libmnt_table *tb = NULL; |
| int rc = 0; |
| struct libmnt_fs *fs; |
| |
| assert(upd); |
| assert(upd->fs); |
| |
| DBG(UPDATE, ul_debugobj(upd, "%s: modify options", upd->filename)); |
| |
| fs = upd->fs; |
| |
| if (lc) |
| rc = mnt_lock_file(lc); |
| if (rc) |
| return -MNT_ERR_LOCK; |
| |
| tb = __mnt_new_table_from_file(upd->filename, |
| upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1); |
| if (tb) { |
| struct libmnt_fs *cur = mnt_table_find_target(tb, |
| mnt_fs_get_target(fs), |
| MNT_ITER_BACKWARD); |
| if (cur) { |
| if (upd->userspace_only) |
| rc = mnt_fs_set_attributes(cur, mnt_fs_get_attributes(fs)); |
| if (!rc) |
| rc = mnt_fs_set_options(cur, mnt_fs_get_options(fs)); |
| if (!rc) |
| rc = update_table(upd, tb); |
| } else |
| rc = add_file_entry(tb, upd); /* not found, add new */ |
| } |
| |
| if (lc) |
| mnt_unlock_file(lc); |
| |
| mnt_unref_table(tb); |
| return rc; |
| } |
| |
| /** |
| * mnt_update_table: |
| * @upd: update |
| * @lc: lock or NULL |
| * |
| * High-level API to update /etc/mtab (or private /run/mount/utab file). |
| * |
| * The @lc lock is optional and will be created if necessary. Note that |
| * an automatically created lock blocks all signals. |
| * |
| * See also mnt_lock_block_signals() and mnt_context_get_lock(). |
| * |
| * Returns: 0 on success, negative number on error. |
| */ |
| int mnt_update_table(struct libmnt_update *upd, struct libmnt_lock *lc) |
| { |
| struct libmnt_lock *lc0 = lc; |
| int rc = -EINVAL; |
| |
| if (!upd || !upd->filename) |
| return -EINVAL; |
| if (!upd->ready) |
| return 0; |
| |
| DBG(UPDATE, ul_debugobj(upd, "%s: update tab", upd->filename)); |
| if (upd->fs) { |
| DBG(UPDATE, mnt_fs_print_debug(upd->fs, stderr)); |
| } |
| if (!lc) { |
| lc = mnt_new_lock(upd->filename, 0); |
| if (lc) |
| mnt_lock_block_signals(lc, TRUE); |
| } |
| if (lc && upd->userspace_only) |
| mnt_lock_use_simplelock(lc, TRUE); /* use flock */ |
| |
| if (!upd->fs && upd->target) |
| rc = update_remove_entry(upd, lc); /* umount */ |
| else if (upd->mountflags & MS_MOVE) |
| rc = update_modify_target(upd, lc); /* move */ |
| else if (upd->mountflags & MS_REMOUNT) |
| rc = update_modify_options(upd, lc); /* remount */ |
| else if (upd->fs) |
| rc = update_add_entry(upd, lc); /* mount */ |
| |
| upd->ready = FALSE; |
| DBG(UPDATE, ul_debugobj(upd, "%s: update tab: done [rc=%d]", |
| upd->filename, rc)); |
| if (lc != lc0) |
| mnt_free_lock(lc); |
| return rc; |
| } |
| |
| int mnt_update_already_done(struct libmnt_update *upd, struct libmnt_lock *lc) |
| { |
| struct libmnt_table *tb = NULL; |
| struct libmnt_lock *lc0 = lc; |
| int rc = 0; |
| |
| if (!upd || !upd->filename || (!upd->fs && !upd->target)) |
| return -EINVAL; |
| |
| DBG(UPDATE, ul_debugobj(upd, "%s: checking for previous update", upd->filename)); |
| |
| if (!lc) { |
| lc = mnt_new_lock(upd->filename, 0); |
| if (lc) |
| mnt_lock_block_signals(lc, TRUE); |
| } |
| if (lc && upd->userspace_only) |
| mnt_lock_use_simplelock(lc, TRUE); /* use flock */ |
| if (lc) { |
| rc = mnt_lock_file(lc); |
| if (rc) { |
| rc = -MNT_ERR_LOCK; |
| goto done; |
| } |
| } |
| |
| tb = __mnt_new_table_from_file(upd->filename, |
| upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1); |
| if (lc) |
| mnt_unlock_file(lc); |
| if (!tb) |
| goto done; |
| |
| if (upd->fs) { |
| /* mount */ |
| const char *tgt = mnt_fs_get_target(upd->fs); |
| const char *src = mnt_fs_get_bindsrc(upd->fs) ? |
| mnt_fs_get_bindsrc(upd->fs) : |
| mnt_fs_get_source(upd->fs); |
| |
| if (mnt_table_find_pair(tb, src, tgt, MNT_ITER_BACKWARD)) { |
| DBG(UPDATE, ul_debugobj(upd, "%s: found %s %s", |
| upd->filename, src, tgt)); |
| rc = 1; |
| } |
| } else if (upd->target) { |
| /* umount */ |
| if (!mnt_table_find_target(tb, upd->target, MNT_ITER_BACKWARD)) { |
| DBG(UPDATE, ul_debugobj(upd, "%s: not-found (umounted) %s", |
| upd->filename, upd->target)); |
| rc = 1; |
| } |
| } |
| |
| mnt_unref_table(tb); |
| done: |
| if (lc && lc != lc0) |
| mnt_free_lock(lc); |
| DBG(UPDATE, ul_debugobj(upd, "%s: previous update check done [rc=%d]", |
| upd->filename, rc)); |
| return rc; |
| } |
| |
| |
| #ifdef TEST_PROGRAM |
| |
| static int update(const char *target, struct libmnt_fs *fs, unsigned long mountflags) |
| { |
| int rc; |
| struct libmnt_update *upd; |
| |
| DBG(UPDATE, ul_debug("update test")); |
| |
| upd = mnt_new_update(); |
| if (!upd) |
| return -ENOMEM; |
| |
| rc = mnt_update_set_fs(upd, mountflags, target, fs); |
| if (rc == 1) { |
| /* update is unnecessary */ |
| rc = 0; |
| goto done; |
| } |
| if (rc) { |
| fprintf(stderr, "failed to set FS\n"); |
| goto done; |
| } |
| |
| /* [... mount(2) call should be here...] */ |
| |
| rc = mnt_update_table(upd, NULL); |
| done: |
| mnt_free_update(upd); |
| return rc; |
| } |
| |
| static int test_add(struct libmnt_test *ts, int argc, char *argv[]) |
| { |
| struct libmnt_fs *fs = mnt_new_fs(); |
| int rc; |
| |
| 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_options(fs, argv[4]); |
| |
| rc = update(NULL, fs, 0); |
| mnt_unref_fs(fs); |
| return rc; |
| } |
| |
| static int test_remove(struct libmnt_test *ts, int argc, char *argv[]) |
| { |
| if (argc < 2) |
| return -1; |
| return update(argv[1], NULL, 0); |
| } |
| |
| static int test_move(struct libmnt_test *ts, int argc, char *argv[]) |
| { |
| struct libmnt_fs *fs = mnt_new_fs(); |
| int rc; |
| |
| if (argc < 3) |
| return -1; |
| mnt_fs_set_source(fs, argv[1]); |
| mnt_fs_set_target(fs, argv[2]); |
| |
| rc = update(NULL, fs, MS_MOVE); |
| |
| mnt_unref_fs(fs); |
| return rc; |
| } |
| |
| static int test_remount(struct libmnt_test *ts, int argc, char *argv[]) |
| { |
| struct libmnt_fs *fs = mnt_new_fs(); |
| int rc; |
| |
| if (argc < 3) |
| return -1; |
| mnt_fs_set_target(fs, argv[1]); |
| mnt_fs_set_options(fs, argv[2]); |
| |
| rc = update(NULL, fs, MS_REMOUNT); |
| mnt_unref_fs(fs); |
| return rc; |
| } |
| |
| static int test_replace(struct libmnt_test *ts, int argc, char *argv[]) |
| { |
| struct libmnt_fs *fs = mnt_new_fs(); |
| struct libmnt_table *tb = mnt_new_table(); |
| int rc; |
| |
| if (argc < 3) |
| return -1; |
| |
| mnt_table_enable_comments(tb, TRUE); |
| mnt_table_parse_fstab(tb, NULL); |
| |
| mnt_fs_set_source(fs, argv[1]); |
| mnt_fs_set_target(fs, argv[2]); |
| mnt_fs_append_comment(fs, "# this is new filesystem\n"); |
| |
| mnt_table_add_fs(tb, fs); |
| mnt_unref_fs(fs); |
| |
| rc = mnt_table_replace_file(tb, mnt_get_fstab_path()); |
| mnt_unref_table(tb); |
| return rc; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| struct libmnt_test tss[] = { |
| { "--add", test_add, "<src> <target> <type> <options> add a line to mtab" }, |
| { "--remove", test_remove, "<target> MS_REMOUNT mtab change" }, |
| { "--move", test_move, "<old_target> <target> MS_MOVE mtab change" }, |
| { "--remount",test_remount, "<target> <options> MS_REMOUNT mtab change" }, |
| { "--replace",test_replace, "<src> <target> Add a line to LIBMOUNT_FSTAB and replace the original file" }, |
| { NULL } |
| }; |
| |
| return mnt_run_test(tss, argc, argv); |
| } |
| |
| #endif /* TEST_PROGRAM */ |