]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libmount: reimplement X-mount.subdir= by hooks
authorKarel Zak <kzak@redhat.com>
Wed, 25 May 2022 12:01:52 +0000 (14:01 +0200)
committerKarel Zak <kzak@redhat.com>
Tue, 3 Jan 2023 11:53:12 +0000 (12:53 +0100)
* "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 <kzak@redhat.com>
libmount/src/Makemodule.am
libmount/src/context.c
libmount/src/context_mount.c
libmount/src/hook_subdir.c [new file with mode: 0644]
libmount/src/hooks.c
libmount/src/mountP.h
libmount/src/utils.c

index 1e7ec24d0da043db035159f7d1626d65e1226e84..90cc4ac266371480ce980941c264b4cb6d03b058 100644 (file)
@@ -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
index 840549d86e8bd245a6869ed26dae3b7ad94cc502..2c909b19e0b4625e176f416225434a38fe266e30 100644 (file)
@@ -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;
index 990cc11f82738f839ab34a19257043c5652ead82..e1766075514d364667c21c5d5d73893ce6726529 100644 (file)
@@ -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 (file)
index 0000000..7a069d0
--- /dev/null
@@ -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 <kzak@redhat.com>
+ *
+ * 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 <sched.h>
+
+#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
+};
index 5e6327c6e592bf255a5f9d9fdeefd91fd91811ed..13a31859933f24b8fbb9dc8190c05b5e11045df3 100644 (file)
@@ -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);
index 29dcba82fac13d63fbda798fc9cd669852bb63d5..80f02af761814db47371634c7d16147191b9dfee 100644 (file)
@@ -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= */
index 5693cf8adf3f9cd408e6c595d462852c1cba139b..26c5aef7b1f459478b95e381dfeaf12228e24d55 100644 (file)
@@ -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[])
 {