From: Karel Zak Date: Wed, 25 May 2022 12:01:52 +0000 (+0200) Subject: libmount: reimplement X-mount.subdir= by hooks X-Git-Tag: v2.39-rc1~343 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b0a165dc76c024614a930325877a25555d4ff819;p=thirdparty%2Futil-linux.git libmount: reimplement X-mount.subdir= by hooks * "prepare-target hook" checks for X-mount.subdir= and define pre-mount hook * "mount-pre hook" unshares temporary directory, redirect the next mount(2) to this temporary directory * "mount-post hook" binds subdirectory to the final target, umounts temporary directory Signed-off-by: Karel Zak --- diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am index 1e7ec24d0d..90cc4ac266 100644 --- a/libmount/src/Makemodule.am +++ b/libmount/src/Makemodule.am @@ -34,6 +34,7 @@ libmount_la_SOURCES += \ libmount/src/context_umount.c \ libmount/src/hook_mount_legacy.c \ libmount/src/hook_mkdir.c \ + libmount/src/hook_subdir.c \ libmount/src/monitor.c if HAVE_BTRFS diff --git a/libmount/src/context.c b/libmount/src/context.c index 840549d86e..2c909b19e0 100644 --- a/libmount/src/context.c +++ b/libmount/src/context.c @@ -158,7 +158,6 @@ int mnt_reset_context(struct libmnt_context *cxt) free(cxt->helper); free(cxt->orig_user); - free(cxt->subdir); cxt->tgt_owner = (uid_t) -1; cxt->tgt_group = (gid_t) -1; @@ -173,7 +172,6 @@ int mnt_reset_context(struct libmnt_context *cxt) cxt->user_mountflags = 0; cxt->orig_mountflags = 0; cxt->mountdata = NULL; - cxt->subdir = NULL; cxt->flags = MNT_FL_DEFAULT; cxt->noautofs = 1; cxt->is_propagation_only = 0; @@ -299,8 +297,6 @@ struct libmnt_context *mnt_copy_context(struct libmnt_context *o) goto failed; if (strdup_between_structs(n, o, orig_user)) goto failed; - if (strdup_between_structs(n, o, subdir)) - goto failed; n->mountflags = o->mountflags; n->mountdata = o->mountdata; diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c index 990cc11f82..e176607551 100644 --- a/libmount/src/context_mount.c +++ b/libmount/src/context_mount.c @@ -572,29 +572,6 @@ static int exec_helper(struct libmnt_context *cxt) return rc; } -static int do_mount_subdir(struct libmnt_context *cxt, - const char *root, - const char *subdir, - const char *target) -{ - char *src = NULL; - int rc = 0; - - if (asprintf(&src, "%s/%s", root, subdir) < 0) - return -ENOMEM; - - DBG(CXT, ul_debugobj(cxt, "mount subdir %s to %s", src, target)); - if (mount(src, target, NULL, MS_BIND | MS_REC, NULL) != 0) - rc = -MNT_ERR_APPLYFLAGS; - - DBG(CXT, ul_debugobj(cxt, "umount old root %s", root)); - if (umount(root) != 0) - rc = -MNT_ERR_APPLYFLAGS; - - free(src); - return rc; -} - /* * The default is to use fstype from cxt->fs, this could be overwritten by * @try_type argument. If @try_type is specified then mount with MS_SILENT. @@ -605,8 +582,8 @@ static int do_mount_subdir(struct libmnt_context *cxt, */ static int do_mount(struct libmnt_context *cxt, const char *try_type) { - int rc = 0, old_ns_fd = -1; - char *org_target = NULL, *org_type = NULL; + int rc = 0; + char *org_type = NULL; assert(cxt); assert(cxt->fs); @@ -642,19 +619,6 @@ static int do_mount(struct libmnt_context *cxt, const char *try_type) } - /* create unhared temporary target (TODO: use MOUNT_PRE hook) */ - if (cxt->subdir) { - org_target = strdup(mnt_fs_get_target(cxt->fs)); - if (!org_target) { - rc = -ENOMEM; - goto done; - } - rc = mnt_tmptgt_unshare(&old_ns_fd); - if (rc) - goto done; - mnt_fs_set_target(cxt->fs, MNT_PATH_TMPTGT); - } - /* * mount(2) or others syscalls */ @@ -664,24 +628,9 @@ static int do_mount(struct libmnt_context *cxt, const char *try_type) if (!rc) rc = mnt_context_call_hooks(cxt, MNT_STAGE_MOUNT_POST); - if (org_target) - __mnt_fs_set_target_ptr(cxt->fs, org_target); if (org_type && rc != 0) __mnt_fs_set_fstype_ptr(cxt->fs, org_type); - org_target = org_type = NULL; - - /* - * bind subdir to the real target, umount temporary target - * (TODO: use MOUNT_POST hook) - */ - if (rc == 0 && cxt->subdir) { - rc = do_mount_subdir(cxt, MNT_PATH_TMPTGT, cxt->subdir, - mnt_fs_get_target(cxt->fs)); - if (rc) - goto done; - mnt_tmptgt_cleanup(old_ns_fd); - old_ns_fd = -1; - } + org_type = NULL; if (rc == 0 && try_type && cxt->update) { struct libmnt_fs *fs = mnt_update_get_fs(cxt->update); @@ -690,11 +639,8 @@ static int do_mount(struct libmnt_context *cxt, const char *try_type) } done: - if (old_ns_fd >= 0) - mnt_tmptgt_cleanup(old_ns_fd); if (try_type) cxt->mountflags &= ~MS_SILENT; - free(org_target); free(org_type); return rc; } @@ -853,39 +799,6 @@ static int parse_ownership_mode(struct libmnt_context *cxt) return 0; } -static int is_subdir_required(struct libmnt_context *cxt, int *rc) -{ - char *dir; - size_t sz; - - assert(cxt); - assert(rc); - - *rc = 0; - - if (!cxt->fs - || !cxt->fs->user_optstr - || mnt_optstr_get_option(cxt->fs->user_optstr, - "X-mount.subdir", &dir, &sz) != 0) - return 0; - - if (dir && *dir == '"') - dir++, sz-=2; - - if (!dir || sz < 1) { - DBG(CXT, ul_debug("failed to parse X-mount.subdir '%s'", dir)); - *rc = -MNT_ERR_MOUNTOPT; - } else { - cxt->subdir = strndup(dir, sz); - if (!cxt->subdir) - *rc = -ENOMEM; - - DBG(CXT, ul_debug("subdir %s wanted", dir)); - } - - return *rc == 0; -} - static int prepare_target(struct libmnt_context *cxt) { const char *tgt, *prefix; @@ -929,9 +842,6 @@ static int prepare_target(struct libmnt_context *cxt) if (!ns_old) return -MNT_ERR_NAMESPACE; - /* X-mount.mkdir target */ - rc = mnt_context_call_hooks(cxt, MNT_STAGE_PREP_TARGET); - /* canonicalize the path */ if (rc == 0) { struct libmnt_cache *cache = mnt_context_get_cache(cxt); @@ -943,15 +853,7 @@ static int prepare_target(struct libmnt_context *cxt) } } - /* X-mount.subdir= target */ - if (rc == 0 - && cxt->action == MNT_ACT_MOUNT - && (cxt->user_mountflags & MNT_MS_XFSTABCOMM) - && is_subdir_required(cxt, &rc)) { - - DBG(CXT, ul_debugobj(cxt, "subdir %s required", cxt->subdir)); - } - + rc = mnt_context_call_hooks(cxt, MNT_STAGE_PREP_TARGET); if (!mnt_context_switch_ns(cxt, ns_old)) return -MNT_ERR_NAMESPACE; diff --git a/libmount/src/hook_subdir.c b/libmount/src/hook_subdir.c new file mode 100644 index 0000000000..7a069d0950 --- /dev/null +++ b/libmount/src/hook_subdir.c @@ -0,0 +1,316 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2022 Karel Zak + * + * libmount is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * + * This is X-mount.subdir= implementation. The code uses global hookset data + * rather than per-callback (hook) data. + */ + +#include + +#include "mountP.h" +#include "fileutils.h" + +static int hook_prepare_target(struct libmnt_context *cxt, const struct libmnt_hookset *hs, void *data); +static int tmptgt_cleanup(int old_ns_fd); + +struct hookset_data { + char *subdir; + char *org_target; + int old_ns_fd; +}; + +static void free_hookset_data( struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct hookset_data *hsd = mnt_context_get_hookset_data(cxt, hs); + + if (!hsd) + return; + if (hsd->old_ns_fd >= 0) + tmptgt_cleanup(hsd->old_ns_fd); + + free(hsd->org_target); + free(hsd->subdir); + free(hsd); + + mnt_context_set_hookset_data(cxt, hs, NULL); +} + +/* global data, used by all callbacks */ +static struct hookset_data *new_hookset_data( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct hookset_data *hsd = calloc(1, sizeof(struct hookset_data)); + + if (hsd && mnt_context_set_hookset_data(cxt, hs, hsd) != 0) { + /* probably ENOMEM problem */ + free(hsd); + hsd = NULL; + } + return hsd; +} + +/* initiallize this module */ +static int hookset_init(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + DBG(HOOK, ul_debugobj(hs, "init '%s'", hs->name)); + + return mnt_context_append_hook(cxt, hs, + MNT_STAGE_PREP_TARGET, NULL, hook_prepare_target); +} + +/* de-initiallize this module */ +static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name)); + + /* remove all our hooks */ + while (mnt_context_remove_hook(cxt, hs, 0, NULL) == 0); + + /* free and remove global hookset data */ + free_hookset_data(cxt, hs); + + return 0; +} + +/* + * Initialize MNT_PATH_TMPTGT; mkdir, create a new namespace and + * mark (bind mount) the directory as private. + */ +static int tmptgt_unshare(int *old_ns_fd) +{ +#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES + int rc = 0, fd = -1; + + assert(old_ns_fd); + + *old_ns_fd = -1; + + /* remember the current namespace */ + fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + if (fd < 0) + goto fail; + + /* create new namespace */ + if (unshare(CLONE_NEWNS) != 0) + goto fail; + + /* create directory */ + rc = ul_mkdir_p(MNT_PATH_TMPTGT, S_IRWXU); + if (rc) + goto fail; + + /* try to set top-level directory as private, this is possible if + * MNT_RUNTIME_TOPDIR (/run) is a separated filesystem. */ + if (mount("none", MNT_RUNTIME_TOPDIR, NULL, MS_PRIVATE, NULL) != 0) { + + /* failed; create a mountpoint from MNT_PATH_TMPTGT */ + if (mount(MNT_PATH_TMPTGT, MNT_PATH_TMPTGT, "none", MS_BIND, NULL) != 0) + goto fail; + if (mount("none", MNT_PATH_TMPTGT, NULL, MS_PRIVATE, NULL) != 0) + goto fail; + } + + DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshared")); + *old_ns_fd = fd; + return 0; +fail: + if (rc == 0) + rc = errno ? -errno : -EINVAL; + + tmptgt_cleanup(fd); + DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshare failed")); + return rc; +#else + return -ENOSYS; +#endif +} + +/* + * Clean up MNT_PATH_TMPTGT; umount and switch back to old namespace + */ +static int tmptgt_cleanup(int old_ns_fd) +{ +#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES + umount(MNT_PATH_TMPTGT); + + if (old_ns_fd >= 0) { + setns(old_ns_fd, CLONE_NEWNS); + close(old_ns_fd); + } + + DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " cleanup done")); + return 0; +#else + return -ENOSYS; +#endif +} + +static int do_mount_subdir(const char *root, + const char *subdir, + const char *target) +{ + char *src = NULL; + int rc = 0; + + if (asprintf(&src, "%s/%s", root, subdir) < 0) + return -ENOMEM; + + DBG(HOOK, ul_debug("mount subdir %s to %s", src, target)); + if (mount(src, target, NULL, MS_BIND | MS_REC, NULL) != 0) + rc = -MNT_ERR_APPLYFLAGS; + + DBG(HOOK, ul_debug("umount old root %s", root)); + if (umount(root) != 0) + rc = -MNT_ERR_APPLYFLAGS; + + free(src); + return rc; +} + + +static int hook_mount_post( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct hookset_data *hsd; + int rc = 0; + + hsd = mnt_context_get_hookset_data(cxt, hs); + if (!hsd || !hsd->subdir) + return 0; + + /* reset to the original mountpoint */ + mnt_fs_set_target(cxt->fs, hsd->org_target); + + /* bind subdir to the real target, umount temporary target */ + rc = do_mount_subdir(MNT_PATH_TMPTGT, + hsd->subdir, + mnt_fs_get_target(cxt->fs)); + if (rc) + return rc; + + tmptgt_cleanup(hsd->old_ns_fd); + hsd->old_ns_fd = -1; + + return rc; +} + +static int hook_mount_pre( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct hookset_data *hsd; + int rc = 0; + + hsd = mnt_context_get_hookset_data(cxt, hs); + if (!hsd) + return 0; + + /* create unhared temporary target */ + hsd->org_target = strdup(mnt_fs_get_target(cxt->fs)); + if (!hsd->org_target) + rc = -ENOMEM; + if (!rc) + rc = tmptgt_unshare(&hsd->old_ns_fd); + if (!rc) + mnt_fs_set_target(cxt->fs, MNT_PATH_TMPTGT); + if (!rc) + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT_POST, + NULL, hook_mount_post); + return rc; +} + + + +static int is_subdir_required(struct libmnt_context *cxt, int *rc, char **subdir) +{ + char *dir = NULL; + size_t sz; + + assert(cxt); + assert(rc); + + *rc = 0; + + if (!cxt->fs + || !cxt->fs->user_optstr + || mnt_optstr_get_option(cxt->fs->user_optstr, + "X-mount.subdir", &dir, &sz) != 0) + return 0; + + if (dir && *dir == '"') + dir++, sz-=2; + + if (!dir || sz < 1) { + DBG(HOOK, ul_debug("failed to parse X-mount.subdir '%s'", dir)); + *rc = -MNT_ERR_MOUNTOPT; + } else { + *subdir = strndup(dir, sz); + if (!*subdir) + *rc = -ENOMEM; + } + + return *rc == 0; +} + +/* this is the initial callback used to check mount options and define next + * actions if necessary */ +static int hook_prepare_target( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + const char *tgt; + char *subdir = NULL; + int rc = 0; + + assert(cxt); + + tgt = mnt_fs_get_target(cxt->fs); + if (!tgt) + return 0; + + /* X-mount.subdir= target */ + if (cxt->action == MNT_ACT_MOUNT + && (cxt->user_mountflags & MNT_MS_XFSTABCOMM) + && is_subdir_required(cxt, &rc, &subdir)) { + + /* create a global data */ + struct hookset_data *hsd = new_hookset_data(cxt, hs); + + if (!hsd) + return -ENOMEM; + + hsd->subdir = subdir; + + DBG(HOOK, ul_debugobj(hs, "subdir %s wanted", subdir)); + + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT_PRE, + NULL, hook_mount_pre); + } + + return rc; +} + + +const struct libmnt_hookset hookset_subdir = +{ + .name = "__subdir", + .init = hookset_init, + .deinit = hookset_deinit +}; diff --git a/libmount/src/hooks.c b/libmount/src/hooks.c index 5e6327c6e5..13a3185993 100644 --- a/libmount/src/hooks.c +++ b/libmount/src/hooks.c @@ -8,12 +8,14 @@ * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. - */ - -/** - * SECTION: hookset - * @title: Mount context hooks - * @short_description: extensions to mount process + * + * + * The "hookset" is set of callbacks (hooks) that implement some functionality. + * It supports two kinds of data: + * + * - global data : accessible for all callbacks, independent on defined hooks + * + * - per-hook data : usually used by the callback function */ #include "mountP.h" @@ -23,6 +25,7 @@ static const struct libmnt_hookset *hooksets[] = { #ifdef __linux__ &hookset_mkdir, + &hookset_subdir, &hookset_mount_legacy #endif }; @@ -151,6 +154,7 @@ int mnt_context_set_hookset_data(struct libmnt_context *cxt, /* deallocate old data */ if (data == NULL) { if (hd) { + DBG(CXT, ul_debugobj(cxt, " free '%s' data", hs->name)); list_del(&hd->datas); free(hd); } @@ -163,7 +167,7 @@ int mnt_context_set_hookset_data(struct libmnt_context *cxt, if (!hd) return -ENOMEM; - DBG(CXT, ul_debugobj(cxt, "alloc '%s' data", hs->name)); + DBG(CXT, ul_debugobj(cxt, " alloc '%s' data", hs->name)); INIT_LIST_HEAD(&hd->datas); hd->hookset = hs; list_add_tail(&hd->datas, &cxt->hooksets_datas); diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h index 29dcba82fa..80f02af761 100644 --- a/libmount/src/mountP.h +++ b/libmount/src/mountP.h @@ -302,6 +302,7 @@ struct libmnt_hookset { /* built-in hooks */ extern const struct libmnt_hookset hookset_mount_legacy; extern const struct libmnt_hookset hookset_mkdir; +extern const struct libmnt_hookset hookset_subdir; extern int mnt_context_init_hooksets(struct libmnt_context *cxt); @@ -353,8 +354,6 @@ struct libmnt_context char *fstype_pattern; /* for mnt_match_fstype() */ char *optstr_pattern; /* for mnt_match_options() */ - char *subdir; /* X-mount.subdir= */ - uid_t tgt_owner; /* X-mount.owner= */ gid_t tgt_group; /* X-mount.group= */ mode_t tgt_mode; /* X-mount.mode= */ diff --git a/libmount/src/utils.c b/libmount/src/utils.c index 5693cf8adf..26c5aef7b1 100644 --- a/libmount/src/utils.c +++ b/libmount/src/utils.c @@ -1240,79 +1240,6 @@ done: return 1; } -/* - * Initialize MNT_PATH_TMPTGT; mkdir, create a new namespace and - * mark (bind mount) the directory as private. - */ -int mnt_tmptgt_unshare(int *old_ns_fd) -{ -#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES - int rc = 0, fd = -1; - - assert(old_ns_fd); - - *old_ns_fd = -1; - - /* remember the current namespace */ - fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); - if (fd < 0) - goto fail; - - /* create new namespace */ - if (unshare(CLONE_NEWNS) != 0) - goto fail; - - /* create directory */ - rc = ul_mkdir_p(MNT_PATH_TMPTGT, S_IRWXU); - if (rc) - goto fail; - - /* try to set top-level directory as private, this is possible if - * MNT_RUNTIME_TOPDIR (/run) is a separated filesystem. */ - if (mount("none", MNT_RUNTIME_TOPDIR, NULL, MS_PRIVATE, NULL) != 0) { - - /* failed; create a mountpoint from MNT_PATH_TMPTGT */ - if (mount(MNT_PATH_TMPTGT, MNT_PATH_TMPTGT, "none", MS_BIND, NULL) != 0) - goto fail; - if (mount("none", MNT_PATH_TMPTGT, NULL, MS_PRIVATE, NULL) != 0) - goto fail; - } - - DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshared")); - *old_ns_fd = fd; - return 0; -fail: - if (rc == 0) - rc = errno ? -errno : -EINVAL; - - mnt_tmptgt_cleanup(fd); - DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshare failed")); - return rc; -#else - return -ENOSYS; -#endif -} - -/* - * Clean up MNT_PATH_TMPTGT; umount and switch back to old namespace - */ -int mnt_tmptgt_cleanup(int old_ns_fd) -{ -#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES - umount(MNT_PATH_TMPTGT); - - if (old_ns_fd >= 0) { - setns(old_ns_fd, CLONE_NEWNS); - close(old_ns_fd); - } - - DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " cleanup done")); - return 0; -#else - return -ENOSYS; -#endif -} - #ifdef TEST_PROGRAM static int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[]) {