From 8241fb005f41a9450739f00c7396ca1e8cde69af Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Thu, 19 May 2022 13:55:43 +0200 Subject: [PATCH] libmount: implement hooks for a legacy mount(2) * replaces context "addmounts" functionality with more generic hooks * "hooksets" handles complex functionality by a set of hooks. During initialization (or later), the hookset can define arbitrary hook function(s). The library will call the functions from a specified place ("stage"). Now supported stages are prepare-options, pre-mount, mount, and post-mount. This solution looks complex at first glance, but it will help keep all extensions separated from core library code (IDs mapping, X-mount.chown/chmod, X-mount.subdir, etc.). It will also be possible to support multiple implementations for the same functionality (classic mount(2) vs. new fsmount(2)) without #ifdefs storms etc. Maybe later we can also use hooksets for external library modules (like verity support). * __legacy-mount hookset implements support for the classic mount(2) syscall. Supported hooks: prepare-options - analyzes the current setting (libmnt_context) and setup other hooks when necessary mount - calls mount(2) for standard "/dev to /mnt" use-cases post-mount (two possible hooks): - calls mount(2) to modify propagation flags - calls mount(2) to implement bind-remount (mount --bind -oro) Signed-off-by: Karel Zak --- libmount/meson.build | 2 + libmount/src/Makemodule.am | 2 + libmount/src/context.c | 73 ++++--- libmount/src/context_mount.c | 331 ++++++------------------------- libmount/src/fs.c | 7 + libmount/src/hook_mount_legacy.c | 308 ++++++++++++++++++++++++++++ libmount/src/hooks.c | 291 +++++++++++++++++++++++++++ libmount/src/init.c | 1 + libmount/src/mountP.h | 76 ++++++- 9 files changed, 780 insertions(+), 311 deletions(-) create mode 100644 libmount/src/hook_mount_legacy.c create mode 100644 libmount/src/hooks.c diff --git a/libmount/meson.build b/libmount/meson.build index d454e9f9a2..21c84ac95d 100644 --- a/libmount/meson.build +++ b/libmount/meson.build @@ -17,6 +17,7 @@ configure_file( lib_mount_sources = ''' src/mountP.h src/cache.c + src/hooks.c src/fs.c src/init.c src/iter.c @@ -42,6 +43,7 @@ if LINUX src/context_veritydev.c src/context_mount.c src/context_umount.c + src/hook_mount_legacy.c src/monitor.c '''.split() endif diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am index c2579b0acc..2174eeb4c9 100644 --- a/libmount/src/Makemodule.am +++ b/libmount/src/Makemodule.am @@ -11,6 +11,7 @@ libmount_la_SOURCES = \ libmount/src/mountP.h \ libmount/src/cache.c \ libmount/src/fs.c \ + libmount/src/hooks.c \ libmount/src/init.c \ libmount/src/iter.c \ libmount/src/lock.c \ @@ -31,6 +32,7 @@ libmount_la_SOURCES += \ libmount/src/context_veritydev.c \ libmount/src/context_mount.c \ libmount/src/context_umount.c \ + libmount/src/hook_mount_legacy.c \ libmount/src/monitor.c if HAVE_BTRFS diff --git a/libmount/src/context.c b/libmount/src/context.c index a6d78953cd..2178627a7f 100644 --- a/libmount/src/context.c +++ b/libmount/src/context.c @@ -61,8 +61,6 @@ struct libmnt_context *mnt_new_context(void) cxt->tgt_group = (gid_t) -1; cxt->tgt_mode = (mode_t) -1; - INIT_LIST_HEAD(&cxt->addmounts); - ruid = getuid(); euid = geteuid(); @@ -74,6 +72,9 @@ struct libmnt_context *mnt_new_context(void) cxt->ns_tgt.fd = -1; cxt->ns_cur = &cxt->ns_orig; + INIT_LIST_HEAD(&cxt->hooksets_hooks); + INIT_LIST_HEAD(&cxt->hooksets_datas); + /* if we're really root and aren't running setuid */ cxt->restricted = (uid_t) 0 == ruid && ruid == euid ? 0 : 1; @@ -112,6 +113,7 @@ void mnt_free_context(struct libmnt_context *cxt) mnt_free_update(cxt->update); mnt_context_set_target_ns(cxt, NULL); + mnt_context_deinit_hooksets(cxt); free(cxt->children); @@ -170,20 +172,15 @@ int mnt_reset_context(struct libmnt_context *cxt) cxt->orig_user = NULL; cxt->mountflags = 0; cxt->user_mountflags = 0; + cxt->orig_mountflags = 0; cxt->mountdata = NULL; cxt->subdir = NULL; cxt->flags = MNT_FL_DEFAULT; cxt->noautofs = 1; - - /* free additional mounts list */ - while (!list_empty(&cxt->addmounts)) { - struct libmnt_addmount *ad = list_entry(cxt->addmounts.next, - struct libmnt_addmount, - mounts); - mnt_free_addmount(ad); - } + cxt->is_propagation_only = 0; mnt_context_reset_status(cxt); + mnt_context_deinit_hooksets(cxt); if (cxt->table_fltrcb) mnt_context_set_tabfilter(cxt, NULL, NULL); @@ -308,6 +305,7 @@ struct libmnt_context *mnt_copy_context(struct libmnt_context *o) n->mountflags = o->mountflags; n->mountdata = o->mountdata; + n->is_propagation_only = o->is_propagation_only; mnt_context_reset_status(n); @@ -1700,28 +1698,32 @@ int mnt_context_set_mflags(struct libmnt_context *cxt, unsigned long flags) int mnt_context_get_mflags(struct libmnt_context *cxt, unsigned long *flags) { int rc = 0; - struct list_head *p; if (!cxt || !flags) return -EINVAL; *flags = 0; - if (!(cxt->flags & MNT_FL_MOUNTFLAGS_MERGED) && cxt->fs) { - const char *o = mnt_fs_get_options(cxt->fs); - if (o) - rc = mnt_optstr_get_flags(o, flags, - mnt_get_builtin_optmap(MNT_LINUX_MAP)); - } - - list_for_each(p, &cxt->addmounts) { - struct libmnt_addmount *ad = - list_entry(p, struct libmnt_addmount, mounts); - *flags |= ad->mountflags; - } + if (cxt->flags & MNT_FL_MOUNTFLAGS_MERGED) + /* + * Mount options already merged to the flags + */ + *flags |= cxt->orig_mountflags; + else { + /* + * Mount options not yet processed by library, generate the + * flags on-the fly + */ + if (cxt->fs) { + const char *o = mnt_fs_get_options(cxt->fs); - if (!rc) + if (o) + rc = mnt_optstr_get_flags(o, flags, + mnt_get_builtin_optmap(MNT_LINUX_MAP)); + } + /* Add flags defined by mnt_context_set_mflags */ *flags |= cxt->mountflags; + } return rc; } @@ -2254,6 +2256,10 @@ int mnt_context_prepare_helper(struct libmnt_context *cxt, const char *name, return rc; } +/* + * Create cxt->mountflags from options and already defined flags. Note that the + * flags may be later modified, the original is stored in cxt->orig_mountflags. + */ int mnt_context_merge_mflags(struct libmnt_context *cxt) { unsigned long fl = 0; @@ -2266,7 +2272,7 @@ int mnt_context_merge_mflags(struct libmnt_context *cxt) rc = mnt_context_get_mflags(cxt, &fl); if (rc) return rc; - cxt->mountflags = fl; + cxt->orig_mountflags = cxt->mountflags = fl; fl = 0; rc = mnt_context_get_user_mflags(cxt, &fl); @@ -2278,6 +2284,14 @@ int mnt_context_merge_mflags(struct libmnt_context *cxt) cxt->mountflags, cxt->user_mountflags)); cxt->flags |= MNT_FL_MOUNTFLAGS_MERGED; + + if (cxt->mountflags & MS_PROPAGATION) { + unsigned long rest = cxt->mountflags & ~MS_PROPAGATION; + + if (rest == 0 || rest == MS_SILENT) + cxt->is_propagation_only = 1; + } + return 0; } @@ -2667,13 +2681,12 @@ int mnt_context_propagation_only(struct libmnt_context *cxt) if (cxt->action != MNT_ACT_MOUNT) return 0; - /* has to be called after context_mount.c: fix_opts() */ - assert((cxt->flags & MNT_FL_MOUNTOPTS_FIXED)); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); - /* all propagation mounts are in cxt->addmount */ - return !list_empty(&cxt->addmounts) - && (cxt->mountflags == 0 || cxt->mountflags == MS_SILENT) + return cxt->is_propagation_only + && cxt->mountdata == NULL && cxt->fs + && cxt->fs->fs_optstr == NULL && (!cxt->fs->fstype || strcmp(cxt->fs->fstype, "none") == 0) && (!cxt->fs->source || strcmp(cxt->fs->source, "none") == 0); } diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c index 3eaa12f0f3..6e8464a337 100644 --- a/libmount/src/context_mount.c +++ b/libmount/src/context_mount.c @@ -28,140 +28,6 @@ #include "mountP.h" #include "strutils.h" -/* - * Kernel supports only one MS_PROPAGATION flag change by one mount(2) syscall, - * to bypass this restriction we call mount(2) per flag. It's really not a perfect - * solution, but it's the same like to execute multiple mount(8) commands. - * - * We use cxt->addmounts (additional mounts) list to keep order of the requested - * flags changes. - */ -struct libmnt_addmount *mnt_new_addmount(void) -{ - struct libmnt_addmount *ad = calloc(1, sizeof(*ad)); - if (!ad) - return NULL; - - INIT_LIST_HEAD(&ad->mounts); - return ad; -} - -void mnt_free_addmount(struct libmnt_addmount *ad) -{ - if (!ad) - return; - list_del(&ad->mounts); - free(ad); -} - -static int mnt_context_append_additional_mount(struct libmnt_context *cxt, - struct libmnt_addmount *ad) -{ - assert(cxt); - assert(ad); - - if (!list_empty(&ad->mounts)) - return -EINVAL; - - DBG(CXT, ul_debugobj(cxt, - "mount: add additional flag: 0x%08lx", - ad->mountflags)); - - list_add_tail(&ad->mounts, &cxt->addmounts); - return 0; -} - -/* - * add additional mount(2) syscall requests when necessary to set propagation flags - * after regular mount(2). - */ -static int init_propagation(struct libmnt_context *cxt) -{ - char *name; - char *opts = (char *) mnt_fs_get_vfs_options(cxt->fs); - size_t namesz; - struct libmnt_optmap const *maps[1]; - int rec_count = 0; - - if (!opts) - return 0; - - DBG(CXT, ul_debugobj(cxt, "mount: initialize additional propagation mounts")); - - maps[0] = mnt_get_builtin_optmap(MNT_LINUX_MAP); - - while (!mnt_optstr_next_option(&opts, &name, &namesz, NULL, NULL)) { - const struct libmnt_optmap *ent; - struct libmnt_addmount *ad; - int rc; - - if (!mnt_optmap_get_entry(maps, 1, name, namesz, &ent) || !ent) - continue; - - DBG(CXT, ul_debugobj(cxt, " checking %s", ent->name)); - - /* Note that MS_REC may be used for more flags, so we have to keep - * track about number of recursive options to keep the MS_REC in the - * mountflags if necessary. - */ - if (ent->id & MS_REC) - rec_count++; - - if (!(ent->id & MS_PROPAGATION)) - continue; - - ad = mnt_new_addmount(); - if (!ad) - return -ENOMEM; - - ad->mountflags = ent->id; - DBG(CXT, ul_debugobj(cxt, " adding extra mount(2) call for %s", ent->name)); - rc = mnt_context_append_additional_mount(cxt, ad); - if (rc) - return rc; - - DBG(CXT, ul_debugobj(cxt, " removing %s from primary mount(2) call", ent->name)); - cxt->mountflags &= ~ent->id; - - if (ent->id & MS_REC) - rec_count--; - } - - if (rec_count) - cxt->mountflags |= MS_REC; - - return 0; -} - -/* - * add additional mount(2) syscall request to implement "bind,", the first regular - * mount(2) is the "bind" operation, the second is "remount,bind," call. - */ -static int init_bind_remount(struct libmnt_context *cxt) -{ - struct libmnt_addmount *ad; - int rc; - - assert(cxt); - assert(cxt->mountflags & MS_BIND); - assert(!(cxt->mountflags & MS_REMOUNT)); - - DBG(CXT, ul_debugobj(cxt, "mount: initialize additional ro,bind mount")); - - ad = mnt_new_addmount(); - if (!ad) - return -ENOMEM; - - ad->mountflags = cxt->mountflags; - ad->mountflags |= (MS_REMOUNT | MS_BIND); - - rc = mnt_context_append_additional_mount(cxt, ad); - if (rc) - return rc; - - return 0; -} - #if defined(HAVE_LIBSELINUX) || defined(HAVE_SMACK) struct libmnt_optname { const char *name; @@ -272,18 +138,7 @@ static int fix_optstr(struct libmnt_context *cxt) free(fs->user_optstr); fs->user_optstr = NULL; } - if (cxt->mountflags & MS_PROPAGATION) { - rc = init_propagation(cxt); - if (rc) - return rc; - } - if ((cxt->mountflags & MS_BIND) - && (cxt->mountflags & MNT_BIND_SETTABLE) - && !(cxt->mountflags & MS_REMOUNT)) { - rc = init_bind_remount(cxt); - if (rc) - return rc; - } + next = fs->fs_optstr; @@ -353,7 +208,6 @@ static int fix_optstr(struct libmnt_context *cxt) goto done; } - if (!rc && mnt_context_is_restricted(cxt) && (cxt->user_mountflags & MNT_MS_USER)) { ns_old = mnt_context_switch_origin_ns(cxt); if (!ns_old) @@ -365,6 +219,8 @@ static int fix_optstr(struct libmnt_context *cxt) return -MNT_ERR_NAMESPACE; } + mnt_context_call_hooks(cxt, MNT_STAGE_PREP_OPTIONS); + /* refresh merged optstr */ free(fs->optstr); fs->optstr = NULL; @@ -716,43 +572,6 @@ static int exec_helper(struct libmnt_context *cxt) return rc; } -static int do_mount_additional(struct libmnt_context *cxt, - const char *target, - unsigned long flags, - int *syserr) -{ - struct list_head *p; - - assert(cxt); - assert(target); - - if (syserr) - *syserr = 0; - - list_for_each(p, &cxt->addmounts) { - int rc; - struct libmnt_addmount *ad = - list_entry(p, struct libmnt_addmount, mounts); - - DBG(CXT, ul_debugobj(cxt, "mount(2) changing flag: 0x%08lx %s", - ad->mountflags, - ad->mountflags & MS_REC ? " (recursive)" : "")); - - rc = mount("none", target, NULL, - ad->mountflags | (flags & MS_SILENT), NULL); - if (rc) { - if (syserr) - *syserr = -errno; - DBG(CXT, ul_debugobj(cxt, - "mount(2) failed [errno=%d %m]", - errno)); - return rc; - } - } - - return 0; -} - static int do_mount_subdir(struct libmnt_context *cxt, const char *root, const char *subdir, @@ -787,8 +606,7 @@ 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; - const char *src, *target, *type; - unsigned long flags; + char *org_target = NULL, *org_type = NULL; assert(cxt); assert(cxt->fs); @@ -800,108 +618,72 @@ static int do_mount(struct libmnt_context *cxt, const char *try_type) return rc; } - flags = cxt->mountflags; - src = mnt_fs_get_srcpath(cxt->fs); - target = mnt_fs_get_target(cxt->fs); - if (cxt->helper) { rc = exec_helper(cxt); if (mnt_context_helper_executed(cxt) && mnt_context_get_helper_status(cxt) == 0 - && !list_empty(&cxt->addmounts) - && do_mount_additional(cxt, target, flags, NULL)) - + && mnt_context_call_hooks(cxt, MNT_STAGE_MOUNT_POST)) return -MNT_ERR_APPLYFLAGS; + return rc; } - if (!target) - return -EINVAL; - if (!src) { - /* unnecessary, should be already resolved in - * mnt_context_prepare_srcpath(), but to be sure... */ - DBG(CXT, ul_debugobj(cxt, "WARNING: source is NULL -- using \"none\"!")); - src = "none"; + if (try_type) { + cxt->mountflags |= MS_SILENT; + if (mnt_fs_get_fstype(cxt->fs)) { + org_type = strdup(mnt_fs_get_fstype(cxt->fs)); + if (!org_type) { + rc = -ENOMEM; + goto done; + } + } + mnt_fs_set_fstype(cxt->fs, try_type); } - type = try_type ? : mnt_fs_get_fstype(cxt->fs); - - if (try_type) - flags |= MS_SILENT; - - - if (mnt_context_is_fake(cxt)) { - /* - * fake - */ - cxt->syscall_status = 0; - - DBG(CXT, ul_debugobj(cxt, "FAKE mount(2) " - "[source=%s, target=%s, type=%s," - " mountflags=0x%08lx, mountdata=%s]", - src, target, type, - flags, cxt->mountdata ? "yes" : "")); - - } else if (mnt_context_propagation_only(cxt)) { - /* - * propagation flags *only* - */ - if (do_mount_additional(cxt, target, flags, &cxt->syscall_status)) - return -MNT_ERR_APPLYFLAGS; - } else { - /* - * regular mount - */ - /* create unhared temporary target */ - if (cxt->subdir) { - rc = mnt_tmptgt_unshare(&old_ns_fd); - if (rc) - return rc; - target = MNT_PATH_TMPTGT; - } - DBG(CXT, ul_debugobj(cxt, "mount(2) " - "[source=%s, target=%s, type=%s," - " mountflags=0x%08lx, mountdata=%s]", - src, target, type, - flags, cxt->mountdata ? "yes" : "")); - - if (mount(src, target, type, flags, cxt->mountdata)) { - cxt->syscall_status = -errno; - DBG(CXT, ul_debugobj(cxt, "mount(2) failed [errno=%d %m]", - -cxt->syscall_status)); - rc = -cxt->syscall_status; + /* 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; } - DBG(CXT, ul_debugobj(cxt, " mount(2) success")); - cxt->syscall_status = 0; + rc = mnt_tmptgt_unshare(&old_ns_fd); + if (rc) + goto done; + mnt_fs_set_target(cxt->fs, MNT_PATH_TMPTGT); + } - /* - * additional mounts for extra propagation flags - */ - if (!list_empty(&cxt->addmounts) - && do_mount_additional(cxt, target, flags, NULL)) { + /* + * mount(2) or others syscalls + */ + rc = mnt_context_call_hooks(cxt, MNT_STAGE_MOUNT_PRE); + if (!rc) + rc = mnt_context_call_hooks(cxt, MNT_STAGE_MOUNT); + if (!rc) + rc = mnt_context_call_hooks(cxt, MNT_STAGE_MOUNT_POST); - /* TODO: call umount? */ - rc = -MNT_ERR_APPLYFLAGS; - goto done; - } + 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 - */ - if (cxt->subdir) { - target = mnt_fs_get_target(cxt->fs); - rc = do_mount_subdir(cxt, MNT_PATH_TMPTGT, cxt->subdir, target); - if (rc) - goto done; - mnt_tmptgt_cleanup(old_ns_fd); - old_ns_fd = -1; - } + /* + * 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; } - if (try_type && cxt->update) { + if (rc == 0 && try_type && cxt->update) { struct libmnt_fs *fs = mnt_update_get_fs(cxt->update); if (fs) rc = mnt_fs_set_fstype(fs, try_type); @@ -910,7 +692,10 @@ 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; } @@ -1104,6 +889,8 @@ int mnt_context_prepare_mount(struct libmnt_context *cxt) rc = mnt_context_merge_mflags(cxt); if (!rc) rc = evaluate_permissions(cxt); + if (!rc) + rc = mnt_context_init_hooksets(cxt); if (!rc) rc = fix_optstr(cxt); if (!rc) @@ -1397,8 +1184,12 @@ again: goto again; } } + + mnt_context_deinit_hooksets(cxt); + if (!mnt_context_switch_ns(cxt, ns_old)) return -MNT_ERR_NAMESPACE; + return rc; } diff --git a/libmount/src/fs.c b/libmount/src/fs.c index 180cfc862d..46a22c75a7 100644 --- a/libmount/src/fs.c +++ b/libmount/src/fs.c @@ -552,6 +552,13 @@ int mnt_fs_set_target(struct libmnt_fs *fs, const char *tgt) return strdup_to_struct_member(fs, target, tgt); } +int __mnt_fs_set_target_ptr(struct libmnt_fs *fs, char *tgt) +{ + free(fs->target); + fs->target = tgt; + return 0; +} + static int mnt_fs_get_flags(struct libmnt_fs *fs) { return fs ? fs->flags : 0; diff --git a/libmount/src/hook_mount_legacy.c b/libmount/src/hook_mount_legacy.c new file mode 100644 index 0000000000..a82b682d6b --- /dev/null +++ b/libmount/src/hook_mount_legacy.c @@ -0,0 +1,308 @@ +/* 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. + */ + +#include "mountP.h" + +static int hook_prepare(struct libmnt_context *cxt, const struct libmnt_hookset *hs, void *data); + +struct hook_data { + unsigned long mountflags; +}; + +static int hookset_init(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ +#ifdef UL_HAVE_MOUNT_API + /* do nothing when __builtin-mount succesfully registred */ + if (mnt_context_has_hook(cxt, &hookset_mount, 0, NULL)) + return 0; +#endif + + DBG(HOOK, ul_debugobj(hs, "init '%s'", hs->name)); + + /* add very basic callback */ + return mnt_context_append_hook(cxt, hs, + MNT_STAGE_PREP_OPTIONS, NULL, hook_prepare); +} + +static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + void *data = NULL; + + DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name)); + + /* remove all our hooks and free hook data */ + while (mnt_context_remove_hook(cxt, hs, 0, &data) == 0) { + if (data) + free(data); + data = NULL; + } + + return 0; +} + +static struct hook_data *new_hook_data(void) +{ + return calloc(1, sizeof(struct hook_data)); +} + +/* call mount(2) for propagation flags */ +static int hook_propagation(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data) +{ + int rc; + struct hook_data *hd = (struct hook_data *) data; + + assert(hd); + assert(cxt); + assert(cxt->fs); + + DBG(HOOK, ul_debugobj(hs, " calling mount(2) for propagation: 0x%08lx %s", + hd->mountflags, + hd->mountflags & MS_REC ? " (recursive)" : "")); + + if (mnt_context_is_fake(cxt)) { + DBG(CXT, ul_debugobj(cxt, " FAKE (-f)")); + cxt->syscall_status = 0; + return 0; + } + + /* + * hd->mountflags are propagation flags as set in prepare_propagation() + * + * cxt->mountflags are global mount flags, may be modified after + * preparation stage (for example when libmount blindly tries FS type then + * it uses MS_SILENT) + */ + rc = mount("none", mnt_fs_get_target(cxt->fs), NULL, + hd->mountflags | (cxt->mountflags & MS_SILENT), NULL); + + if (rc) { + /* Update global syscall status if only this function called */ + if (mnt_context_propagation_only(cxt)) + cxt->syscall_status = -errno; + + DBG(HOOK, ul_debugobj(hs, " mount(2) failed [errno=%d %m]", errno)); + rc = -MNT_ERR_APPLYFLAGS; + } + return rc; +} + +/* + * add additional mount(2) syscalls to set propagation flags after regular mount(2). + */ +static int prepare_propagation(struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + char *name; + char *opts; + size_t namesz; + struct libmnt_optmap const *maps[1]; + int rec_count = 0; + + assert(cxt); + assert(cxt->fs); + + opts = (char *) mnt_fs_get_vfs_options(cxt->fs); + if (!opts) + return 0; + + maps[0] = mnt_get_builtin_optmap(MNT_LINUX_MAP); + + while (!mnt_optstr_next_option(&opts, &name, &namesz, NULL, NULL)) { + struct hook_data *data; + const struct libmnt_optmap *ent; + int rc; + + if (!mnt_optmap_get_entry(maps, 1, name, namesz, &ent) || !ent) + continue; + + /* Note that MS_REC may be used for more flags, so we have to keep + * track about number of recursive options to keep the MS_REC in the + * mountflags if necessary. + */ + if (ent->id & MS_REC) + rec_count++; + + if (!(ent->id & MS_PROPAGATION)) + continue; + + data = new_hook_data(); + if (!data) + return -ENOMEM; + data->mountflags = ent->id; + + DBG(HOOK, ul_debugobj(hs, " adding mount(2) call for %s", ent->name)); + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT_POST, + data, + hook_propagation); + if (rc) + return rc; + + DBG(HOOK, ul_debugobj(hs, " removing '%s' flag from primary mount(2)", ent->name)); + cxt->mountflags &= ~ent->id; + + if (ent->id & MS_REC) + rec_count--; + } + + if (rec_count) + cxt->mountflags |= MS_REC; + + return 0; +} + +/* call mount(2) for bind,remount */ +static int hook_bindremount(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, void *data) +{ + int rc; + struct hook_data *hd = (struct hook_data *) data; + + DBG(HOOK, ul_debugobj(hs, " mount(2) for bind-remount: 0x%08lx %s", + hd->mountflags, + hd->mountflags & MS_REC ? " (recursive)" : "")); + + if (mnt_context_is_fake(cxt)) { + DBG(CXT, ul_debugobj(cxt, " FAKE (-f)")); + cxt->syscall_status = 0; + return 0; + } + + /* for the flags see comment in hook_propagation() */ + rc = mount("none", mnt_fs_get_target(cxt->fs), NULL, + hd->mountflags | (cxt->mountflags & MS_SILENT), NULL); + + if (rc) + DBG(HOOK, ul_debugobj(hs, " mount(2) failed" + " [rc=%d errno=%d %m]", rc, errno)); + return rc; +} + +/* + * add additional mount(2) syscall request to implement "bind,", the first regular + * mount(2) is the "bind" operation, the second is "remount,bind," call. + */ +static int prepare_bindremount(struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct hook_data *data; + int rc; + + assert(cxt); + assert(cxt->mountflags & MS_BIND); + assert(!(cxt->mountflags & MS_REMOUNT)); + + DBG(HOOK, ul_debugobj(hs, " adding mount(2) call for bint-remount")); + + data = new_hook_data(); + if (!data) + return -ENOMEM; + + data->mountflags = cxt->mountflags; + data->mountflags |= (MS_REMOUNT | MS_BIND); + + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT_POST, data, hook_bindremount); + return rc; +} + + + + +/* call mount(2) for regular FS mount, mount flags and options are read from + * library context struct. There are no private hook data. + */ +static int hook_mount(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + int rc = 0; + const char *src, *target, *type; + + src = mnt_fs_get_srcpath(cxt->fs); + target = mnt_fs_get_target(cxt->fs); + type = mnt_fs_get_fstype(cxt->fs); + + if (!target) + return -EINVAL; + if (!src) + src = "none"; + + DBG(HOOK, ul_debugobj(hs, " mount(2) " + "[source=%s, target=%s, type=%s," + " mountflags=0x%08lx, mountdata=%s]", + src, target, type, + cxt->mountflags, cxt->mountdata ? "yes" : "")); + + if (mnt_context_is_fake(cxt)) { + DBG(HOOK, ul_debugobj(hs, " FAKE (-f)")); + cxt->syscall_status = 0; + return 0; + } + + if (mount(src, target, type, cxt->mountflags, cxt->mountdata)) { + cxt->syscall_status = -errno; + DBG(HOOK, ul_debugobj(hs, " mount(2) failed [errno=%d %m]", + -cxt->syscall_status)); + rc = -cxt->syscall_status; + return rc; + } + + cxt->syscall_status = 0; + return rc; +} + +/* + * analyze library context and register hooks to call one or more mount(2) syscalls + */ +static int hook_prepare(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + int rc = 0; + + assert(cxt); + assert(hs == &hookset_mount_legacy); + + /* add extra mount(2) calls for each propagation flag */ + if (cxt->mountflags & MS_PROPAGATION) { + rc = prepare_propagation(cxt, hs); + if (rc) + return rc; + } + + /* add extra mount(2) call to implement "bind,remount," */ + if ((cxt->mountflags & MS_BIND) + && (cxt->mountflags & MNT_BIND_SETTABLE) + && !(cxt->mountflags & MS_REMOUNT)) { + rc = prepare_bindremount(cxt, hs); + if (rc) + return rc; + } + + /* append regual FS mount(2) */ + if (!mnt_context_propagation_only(cxt)) + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT, NULL, hook_mount); + + return rc; +} + + +const struct libmnt_hookset hookset_mount_legacy = +{ + .name = "__legacy-mount", + .init = hookset_init, + .deinit = hookset_deinit +}; diff --git a/libmount/src/hooks.c b/libmount/src/hooks.c new file mode 100644 index 0000000000..e5307bd329 --- /dev/null +++ b/libmount/src/hooks.c @@ -0,0 +1,291 @@ +/* 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. + */ + +/** + * SECTION: hookset + * @title: Mount context hooks + * @short_description: extensions to mount process + */ + +#include "mountP.h" + +/* built-in hooksets */ +static const struct libmnt_hookset *hooksets[] = { +#ifdef __linux__ + &hookset_mount_legacy +#endif +}; + +/* hooksets data */ +struct hookset_data { + const struct libmnt_hookset *hookset; + void *data; + + struct list_head datas; +}; + +/* individial callback */ +struct hookset_hook { + const struct libmnt_hookset *hookset; + int stage; + void *data; + + int (*func)(struct libmnt_context *, const struct libmnt_hookset *, void *); + + struct list_head hooks; +}; + +static const char *stagenames[] = { + /* prepare */ + [MNT_STAGE_PREP_SOURCE] = "prep-source", + [MNT_STAGE_PREP_TARGET] = "prep-target", + [MNT_STAGE_PREP_OPTIONS] = "prep-options", + /* mount */ + [MNT_STAGE_MOUNT_PRE] = "pre-mount", + [MNT_STAGE_MOUNT] = "mount", + [MNT_STAGE_MOUNT_POST] = "post-mount", + /* post */ + [MNT_STAGE_POST] = "post", +}; + +int mnt_context_init_hooksets(struct libmnt_context *cxt) +{ + size_t i; + int rc = 0; + + assert(cxt); + + for (i = 0; i < ARRAY_SIZE(hooksets); i++) { + const struct libmnt_hookset *hs = hooksets[i]; + + rc = hs->init(cxt, hs); + if (rc < 0) + break; + } + + if (rc < 0) + mnt_context_deinit_hooksets(cxt); + return rc; +} + +int mnt_context_deinit_hooksets(struct libmnt_context *cxt) +{ + size_t i; + int rc = 0; + + assert(cxt); + + if (list_empty(&cxt->hooksets_hooks)) + return 0; + + for (i = 0; i < ARRAY_SIZE(hooksets); i++) { + const struct libmnt_hookset *hs = hooksets[i]; + + rc += hs->deinit(cxt, hs); + } + + assert(list_empty(&cxt->hooksets_datas)); + assert(list_empty(&cxt->hooksets_hooks)); + + INIT_LIST_HEAD(&cxt->hooksets_datas); + INIT_LIST_HEAD(&cxt->hooksets_hooks); + + return rc; +} + +const struct libmnt_hookset *mnt_context_get_hookset( + struct libmnt_context *cxt, const char *name) +{ + size_t i; + + assert(cxt); + assert(name); + + for (i = 0; i < ARRAY_SIZE(hooksets); i++) { + const struct libmnt_hookset *hs = hooksets[i]; + + if (strcmp(name, hs->name) == 0) + return hs; + } + + return NULL; +} + +static struct hookset_data *get_hookset_data( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct list_head *p; + + assert(cxt); + assert(hs); + + list_for_each(p, &cxt->hooksets_datas) { + struct hookset_data *x = list_entry(p, struct hookset_data, datas); + + if (x->hookset == hs) + return x; + } + return 0; +} + +int mnt_context_set_hookset_data(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data) +{ + struct hookset_data *hd = NULL; + + hd = get_hookset_data(cxt, hs); + + /* deallocate old data */ + if (data == NULL) { + if (hd) { + list_del(&hd->datas); + free(hd); + } + return 0; + } + + /* create and append new data */ + if (!hd) { + hd = calloc(1, sizeof(*hd)); + if (!hd) + return -ENOMEM; + + 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); + + } + hd->data = data; + return 0; +} + +void *mnt_context_get_hookset_data(struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct hookset_data *hd = get_hookset_data(cxt, hs); + + return hd ? hd->data : NULL; +} + +int mnt_context_append_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data, + int (*func)(struct libmnt_context *, + const struct libmnt_hookset *, + void *)) +{ + struct hookset_hook *hook; + + assert(cxt); + assert(hs); + assert(stage); + + hook = calloc(1, sizeof(*hook)); + if (!hook) + return -ENOMEM; + + DBG(CXT, ul_debugobj(cxt, " appending %s hook from %s", + stagenames[stage], hs->name)); + + INIT_LIST_HEAD(&hook->hooks); + + hook->hookset = hs; + hook->data = data; + hook->func = func; + hook->stage = stage; + + list_add_tail(&hook->hooks, &cxt->hooksets_hooks); + return 0; +} + +static struct hookset_hook *get_hookset_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data) +{ + struct list_head *p, *next; + + assert(cxt); + + list_for_each_safe(p, next, &cxt->hooksets_hooks) { + struct hookset_hook *x = list_entry(p, struct hookset_hook, hooks); + + if (hs && x->hookset != hs) + continue; + if (stage && x->stage != stage) + continue; + if (data && x->data != data) + continue; + return x; + } + + return NULL; +} + +int mnt_context_remove_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void **data) +{ + struct hookset_hook *hook; + + assert(cxt); + + hook = get_hookset_hook(cxt, hs, stage, NULL); + if (hook) { + DBG(CXT, ul_debugobj(cxt, " removing %s hook from %s", + stagenames[hook->stage], hook->hookset->name)); + + if (data) + *data = hook->data; + + list_del(&hook->hooks); + free(hook); + return 0; + } + + return 1; +} + +int mnt_context_has_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data) +{ + return get_hookset_hook(cxt, hs, stage, data) ? 1 : 0; +} + +int mnt_context_call_hooks(struct libmnt_context *cxt, int stage) +{ + struct list_head *p, *next; + + list_for_each_safe(p, next, &cxt->hooksets_hooks) { + int rc; + struct hookset_hook *x = list_entry(p, struct hookset_hook, hooks); + + if (x->stage != stage) + continue; + + DBG(CXT, ul_debugobj(cxt, "calling %s hook from %s", + stagenames[x->stage], x->hookset->name)); + + rc = x->func(cxt, x->hookset, x->data); + if (rc < 0) + return rc; + } + + return 0; +} diff --git a/libmount/src/init.c b/libmount/src/init.c index 2410fcc3a2..d7f46b7a2d 100644 --- a/libmount/src/init.c +++ b/libmount/src/init.c @@ -29,6 +29,7 @@ UL_DEBUG_DEFINE_MASKNAMES(libmount) = { "diff", MNT_DEBUG_DIFF, "mountinfo changes tracking" }, { "fs", MNT_DEBUG_FS, "FS abstraction" }, { "help", MNT_DEBUG_HELP, "this help" }, + { "hook", MNT_DEBUG_HOOK, "hooks functionality" }, { "locks", MNT_DEBUG_LOCKS, "mtab and utab locking" }, { "loop", MNT_DEBUG_LOOP, "loop devices routines" }, { "options", MNT_DEBUG_OPTIONS, "mount options parsing" }, diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h index 6f8f03a331..98e40d23a1 100644 --- a/libmount/src/mountP.h +++ b/libmount/src/mountP.h @@ -47,6 +47,7 @@ #define MNT_DEBUG_BTRFS (1 << 12) #define MNT_DEBUG_LOOP (1 << 13) #define MNT_DEBUG_VERITY (1 << 14) +#define MNT_DEBUG_HOOK (1 << 15) #define MNT_DEBUG_ALL 0xFFFF @@ -275,14 +276,66 @@ enum { }; /* - * Additional mounts + * Context hooks + * + * TODO: this will be public one day when libmount will support modules for + * stuff like veritydev.c. */ -struct libmnt_addmount { - unsigned long mountflags; +enum { + MNT_STAGE_PREP_SOURCE = 1, + MNT_STAGE_PREP_TARGET, + MNT_STAGE_PREP_OPTIONS, + + MNT_STAGE_MOUNT_PRE = 100, + MNT_STAGE_MOUNT, + MNT_STAGE_MOUNT_POST, + + MNT_STAGE_POST = 200 +}; - struct list_head mounts; +struct libmnt_hookset { + const char *name; /* hook set name */ + int (*init)(struct libmnt_context *, const struct libmnt_hookset *); /* initialization function */ + int (*deinit)(struct libmnt_context *, const struct libmnt_hookset *); /* cleanup function */ }; +/* built-in hooks */ +extern const struct libmnt_hookset hookset_mount_legacy; + + +extern int mnt_context_init_hooksets(struct libmnt_context *cxt); +extern int mnt_context_deinit_hooksets(struct libmnt_context *cxt); +extern const struct libmnt_hookset *mnt_context_get_hookset(struct libmnt_context *cxt, const char *name); + +extern int mnt_context_set_hookset_data(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data); + +extern void *mnt_context_get_hookset_data(struct libmnt_context *cxt, + const struct libmnt_hookset *hs); + +extern int mnt_context_has_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data); + +extern int mnt_context_append_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data, + int (*func)(struct libmnt_context *, + const struct libmnt_hookset *, + void *)); + +extern int mnt_context_remove_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void **data); +extern int mnt_context_call_hooks(struct libmnt_context *cxt, int stage); + +/* + * Namespace + */ struct libmnt_ns { int fd; /* file descriptor of namespace, -1 when inactive */ struct libmnt_cache *cache; /* paths cache associated with NS */ @@ -328,8 +381,7 @@ struct libmnt_context const void *mountdata; /* final mount(2) data, string or binary data */ unsigned long user_mountflags; /* MNT_MS_* (loop=, user=, ...) */ - - struct list_head addmounts; /* additional mounts */ + unsigned long orig_mountflags; /* original flags (see mnt_context_merge_mflags()) */ struct libmnt_cache *cache; /* paths cache */ struct libmnt_lock *lock; /* utab lock */ @@ -354,7 +406,6 @@ struct libmnt_context int nchildren; /* number of children */ pid_t pid; /* 0=parent; PID=child */ - int syscall_status; /* 1: not called yet, 0: success, <0: -errno */ struct libmnt_ns ns_orig; /* original namespace */ @@ -362,7 +413,11 @@ struct libmnt_context struct libmnt_ns *ns_cur; /* pointer to current namespace */ unsigned int enabled_textdomain : 1; /* bindtextdomain() called */ - unsigned int noautofs : 1; /* ignore autofs mounts */ + unsigned int noautofs : 1; /* ignore autofs mounts */ + unsigned int is_propagation_only : 1; + + struct list_head hooksets_datas; /* global hooksets data */ + struct list_head hooksets_hooks; /* global hooksets data */ }; /* flags */ @@ -421,6 +476,8 @@ extern int __mnt_fs_set_source_ptr(struct libmnt_fs *fs, char *source) __attribute__((nonnull(1))); extern int __mnt_fs_set_fstype_ptr(struct libmnt_fs *fs, char *fstype) __attribute__((nonnull(1))); +extern int __mnt_fs_set_target_ptr(struct libmnt_fs *fs, char *tgt) + __attribute__((nonnull(1))); /* context.c */ extern struct libmnt_context *mnt_copy_context(struct libmnt_context *o); @@ -450,9 +507,6 @@ extern int mnt_context_is_loopdev(struct libmnt_context *cxt) extern int mnt_context_propagation_only(struct libmnt_context *cxt) __attribute__((nonnull)); -extern struct libmnt_addmount *mnt_new_addmount(void); -extern void mnt_free_addmount(struct libmnt_addmount *ad); - extern int mnt_context_setup_loopdev(struct libmnt_context *cxt); extern int mnt_context_delete_loopdev(struct libmnt_context *cxt); extern int mnt_context_clear_loopdev(struct libmnt_context *cxt); -- 2.47.2