/* 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 fsconfig/fsopen based mount. * * Please, see the comment in libmount/src/hooks.c to understand how hooks work. * * Operations: functions and STAGE, all is prepared in hook_prepare(): * * mount: * - fsopen PRE * - fsmount MOUNT * - mount_setattr MOUNT (VFS flags) * - mount_move POST * - mount_setattr POST (propagation) * * remount: * - open_tree PRE * - fsconfig MOUNT (FS reconfigure) * - mount_setattr MOUNT (VFS flags) * - mount_setattr POST (propagation) * * propagation-only: * - open_tree PRE * - mount_setattr POST (propagation) * * move: * - open_tree PRE * - mount_move POST * * bind: * - open_tree PRE (clone) * - mount_setattr MOUNT (VFS flags) * - mount_move POST */ #include "mountP.h" #include "fileutils.h" /* statx() fallback */ #include "strutils.h" #include "mount-api-utils.h" #include "linux_version.h" #include #ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT #define get_sysapi(_cxt) mnt_context_get_sysapi(_cxt) static void close_sysapi_fds(struct libmnt_sysapi *api) { if (api->fd_fs >= 0) close(api->fd_fs); if (api->fd_tree >= 0) close(api->fd_tree); api->fd_tree = api->fd_fs = -1; } /* * This hookset uses 'struct libmnt_sysapi' (mountP.h) as hookset data. */ static void free_hookset_data( struct libmnt_context *cxt, const struct libmnt_hookset *hs) { struct libmnt_sysapi *api = mnt_context_get_hookset_data(cxt, hs); if (!api) return; close_sysapi_fds(api); free(api); mnt_context_set_hookset_data(cxt, hs, NULL); } /* global data, used by all callbacks */ static struct libmnt_sysapi *new_hookset_data( struct libmnt_context *cxt, const struct libmnt_hookset *hs) { struct libmnt_sysapi *api = calloc(1, sizeof(struct libmnt_sysapi)); if (!api) return NULL; api->fd_fs = api->fd_tree = -1; if (mnt_context_set_hookset_data(cxt, hs, api) != 0) { /* probably ENOMEM problem */ free(api); api = NULL; } return api; } /* 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; } static inline int fsconfig_set_value( struct libmnt_context *cxt, const struct libmnt_hookset *hs, int fd, const char *name, const char *value) { int rc; char *p = NULL; if (value && strstr(value, "\\,")) { p = strdup(value); if (!p) return -EINVAL; strrem(p, '\\'); value = p; } DBG(HOOK, ul_debugobj(hs, " fsconfig(name=\"%s\" value=\"%s\")", name, value ? : "")); if (value) { rc = fsconfig(fd, FSCONFIG_SET_STRING, name, value, 0); free(p); } else rc = fsconfig(fd, FSCONFIG_SET_FLAG, name, NULL, 0); set_syscall_status(cxt, "fsconfig", rc == 0); return rc; } static int configure_superblock(struct libmnt_context *cxt, const struct libmnt_hookset *hs, int fd, int force_rwro) { struct libmnt_optlist *ol; struct libmnt_iter itr; struct libmnt_opt *opt; int rc = 0, has_rwro = 0; DBG(HOOK, ul_debugobj(hs, " config FS")); ol = mnt_context_get_optlist(cxt); if (!ol) return -ENOMEM; mnt_reset_iter(&itr, MNT_ITER_FORWARD); while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) { const char *name = mnt_opt_get_name(opt); const char *value = mnt_opt_get_value(opt); const struct libmnt_optmap *ent = mnt_opt_get_mapent(opt); const int is_linux = ent && mnt_opt_get_map(opt) == cxt->map_linux; if (is_linux && ent->id == MS_RDONLY) { /* Use ro/rw for superblock (for backward compatibility) */ value = NULL; has_rwro = 1; } else if (is_linux && ent->mask & MNT_SUPERBLOCK) { /* Use some old MS_* (VFS) flags as superblock flags */ ; } else if (!name || mnt_opt_get_map(opt) || mnt_opt_is_external(opt)) /* Ignore VFS flags, userspace and external options */ continue; rc = fsconfig_set_value(cxt, hs, fd, name, value); if (rc != 0) goto done; } if (force_rwro && !has_rwro) rc = fsconfig_set_value(cxt, hs, fd, "rw", NULL); done: DBG(HOOK, ul_debugobj(hs, " config done [rc=%d]", rc)); return rc != 0 && errno ? -errno : rc; } static int open_fs_configuration_context(struct libmnt_context *cxt, struct libmnt_sysapi *api, const char *type) { DBG(HOOK, ul_debug(" new FS '%s'", type)); if (!type) return -EINVAL; DBG(HOOK, ul_debug(" fsopen(%s)", type)); api->fd_fs = fsopen(type, FSOPEN_CLOEXEC); set_syscall_status(cxt, "fsopen", api->fd_fs >= 0); if (api->fd_fs < 0) return -errno; api->is_new_fs = 1; return api->fd_fs; } static int open_mount_tree(struct libmnt_context *cxt, const char *path, unsigned long mflg) { unsigned long oflg = OPEN_TREE_CLOEXEC; int rc = 0, fd = -1; if (mflg == (unsigned long) -1) { rc = mnt_optlist_get_flags(cxt->optlist, &mflg, cxt->map_linux, 0); if (rc) return rc; } if (!path) { path = mnt_fs_get_target(cxt->fs); if (!path) return -EINVAL; } /* Classic -oremount,bind,ro is not bind operation, it's just * VFS flags update only */ if ((mflg & MS_BIND) && !(mflg & MS_REMOUNT)) { oflg |= OPEN_TREE_CLONE; if (mnt_optlist_is_rbind(cxt->optlist)) oflg |= AT_RECURSIVE; } if (cxt->force_clone) oflg |= OPEN_TREE_CLONE; DBG(HOOK, ul_debug("open_tree(path=%s%s%s)", path, oflg & OPEN_TREE_CLONE ? " clone" : "", oflg & AT_RECURSIVE ? " recursive" : "")); fd = open_tree(AT_FDCWD, path, oflg); set_syscall_status(cxt, "open_tree", fd >= 0); return fd; } static int hook_create_mount(struct libmnt_context *cxt, const struct libmnt_hookset *hs, void *data __attribute__((__unused__))) { struct libmnt_sysapi *api; const char *src; int rc = 0; assert(cxt); if (mnt_context_helper_executed(cxt)) return 0; assert(cxt->fs); api = get_sysapi(cxt); assert(api); if (api->fd_fs < 0) { const char *type = mnt_fs_get_fstype(cxt->fs); rc = open_fs_configuration_context(cxt, api, type); if (rc < 0) { rc = api->fd_fs; goto done; } } src = mnt_fs_get_srcpath(cxt->fs); if (!src) return -EINVAL; DBG(HOOK, ul_debugobj(hs, "init FS")); rc = fsconfig(api->fd_fs, FSCONFIG_SET_STRING, "source", src, 0); set_syscall_status(cxt, "fsconfig", rc == 0); if (!rc) rc = configure_superblock(cxt, hs, api->fd_fs, 0); if (!rc) { DBG(HOOK, ul_debugobj(hs, "create FS")); rc = fsconfig(api->fd_fs, FSCONFIG_CMD_CREATE, NULL, NULL, 0); set_syscall_status(cxt, "fsconfig", rc == 0); } if (!rc) { api->fd_tree = fsmount(api->fd_fs, FSMOUNT_CLOEXEC, 0); set_syscall_status(cxt, "fsmount", api->fd_tree >= 0); if (api->fd_tree < 0) rc = -errno; } if (rc) /* cleanup after fail (libmount may only try the FS type) */ close_sysapi_fds(api); #if defined(HAVE_STATX) && defined(HAVE_STRUCT_STATX) && defined(HAVE_STRUCT_STATX_STX_MNT_ID) if (!rc && cxt->fs) { struct statx st; rc = statx(api->fd_tree, "", AT_EMPTY_PATH, STATX_MNT_ID, &st); if (rc == 0) { cxt->fs->id = (int) st.stx_mnt_id; if (cxt->update) { struct libmnt_fs *fs = mnt_update_get_fs(cxt->update); if (fs) fs->id = cxt->fs->id; } } } #endif done: DBG(HOOK, ul_debugobj(hs, "create FS done [rc=%d, id=%d]", rc, cxt->fs ? cxt->fs->id : -1)); return rc; } static int hook_reconfigure_mount(struct libmnt_context *cxt, const struct libmnt_hookset *hs, void *data __attribute__((__unused__))) { struct libmnt_sysapi *api; int rc = 0; assert(cxt); if (mnt_context_helper_executed(cxt)) return 0; api = get_sysapi(cxt); assert(api); assert(api->fd_tree >= 0); if (api->fd_fs < 0) { api->fd_fs = fspick(api->fd_tree, "", FSPICK_EMPTY_PATH | FSPICK_NO_AUTOMOUNT); set_syscall_status(cxt, "fspick", api->fd_fs >= 0); if (api->fd_fs < 0) return -errno; } rc = configure_superblock(cxt, hs, api->fd_fs, 1); if (!rc) { DBG(HOOK, ul_debugobj(hs, "re-configurate FS")); rc = fsconfig(api->fd_fs, FSCONFIG_CMD_RECONFIGURE, NULL, NULL, 0); set_syscall_status(cxt, "fsconfig", rc == 0); } DBG(HOOK, ul_debugobj(hs, "reconf FS done [rc=%d]", rc)); return rc; } static int set_vfsflags(struct libmnt_context *cxt, const struct libmnt_hookset *hs, uint64_t set, uint64_t clr, int recursive) { struct libmnt_sysapi *api; struct mount_attr attr = { .attr_clr = 0 }; unsigned int callflags = AT_EMPTY_PATH; int rc; api = get_sysapi(cxt); assert(api); /* fallback only; necessary when init_sysapi() during preparation * cannot open the tree -- for example when we call /sbin/mount. */ if (api->fd_tree < 0 && mnt_fs_get_target(cxt->fs)) { rc = api->fd_tree = open_mount_tree(cxt, NULL, (unsigned long) -1); if (rc < 0) return rc; rc = 0; } if (recursive) callflags |= AT_RECURSIVE; DBG(HOOK, ul_debugobj(hs, "mount_setattr(set=0x%08" PRIx64" clr=0x%08" PRIx64")", set, clr)); attr.attr_set = set; attr.attr_clr = clr; errno = 0; rc = mount_setattr(api->fd_tree, "", callflags, &attr, sizeof(attr)); set_syscall_status(cxt, "mount_setattr", rc == 0); if (rc && errno == EINVAL) return -MNT_ERR_APPLYFLAGS; return rc == 0 ? 0 : -errno; } static int hook_set_vfsflags(struct libmnt_context *cxt, const struct libmnt_hookset *hs, void *data __attribute__((__unused__))) { struct libmnt_optlist *ol; uint64_t set = 0, clr = 0; int rc = 0; if (mnt_context_helper_executed(cxt)) return 0; DBG(HOOK, ul_debugobj(hs, "setting VFS flags")); ol = mnt_context_get_optlist(cxt); if (!ol) return -ENOMEM; /* normal flags */ rc = mnt_optlist_get_attrs(ol, &set, &clr, MNT_OL_NOREC); if (!rc && (set || clr)) rc = set_vfsflags(cxt, hs, set, clr, 0); /* recursive flags */ set = clr = 0; if (!rc) rc = mnt_optlist_get_attrs(ol, &set, &clr, MNT_OL_REC); if (!rc && (set || clr)) rc = set_vfsflags(cxt, hs, set, clr, 1); return rc; } static int hook_set_propagation(struct libmnt_context *cxt, const struct libmnt_hookset *hs, void *data __attribute__((__unused__))) { struct libmnt_sysapi *api; struct libmnt_optlist *ol; struct libmnt_iter itr; struct libmnt_opt *opt; int rc = 0; DBG(HOOK, ul_debugobj(hs, "setting propagation")); ol = mnt_context_get_optlist(cxt); if (!ol) return -ENOMEM; api = get_sysapi(cxt); assert(api); /* fallback only; necessary when init_sysapi() during preparation * cannot open the tree -- for example when we call /sbin/mount. */ if (api->fd_tree < 0 && mnt_fs_get_target(cxt->fs)) { rc = api->fd_tree = open_mount_tree(cxt, NULL, (unsigned long) -1); if (rc < 0) goto done; rc = 0; } mnt_reset_iter(&itr, MNT_ITER_FORWARD); while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) { const struct libmnt_optmap *map = mnt_opt_get_map(opt); const struct libmnt_optmap *ent = mnt_opt_get_mapent(opt); struct mount_attr attr = { .attr_clr = 0 }; unsigned int flgs = AT_EMPTY_PATH; if (cxt->map_linux != map) continue; if (mnt_opt_is_external(opt)) continue; if (!ent || !ent->id || !(ent->id & MS_PROPAGATION)) continue; attr.propagation = ent->id & MS_PROPAGATION; if (ent->id & MS_REC) flgs |= AT_RECURSIVE; DBG(HOOK, ul_debugobj(hs, "mount_setattr(propagation=0x%08" PRIx64")", (uint64_t) attr.propagation)); rc = mount_setattr(api->fd_tree, "", flgs, &attr, sizeof(attr)); set_syscall_status(cxt, "mount_setattr", rc == 0); if (rc && errno == EINVAL) return -MNT_ERR_APPLYFLAGS; if (rc != 0) break; } done: return rc == 0 ? 0 : -errno; } static int hook_attach_target(struct libmnt_context *cxt, const struct libmnt_hookset *hs, void *data __attribute__((__unused__))) { struct libmnt_sysapi *api; const char *target; int rc = 0; if (mnt_context_helper_executed(cxt)) return 0; target = mnt_fs_get_target(cxt->fs); if (!target) return -EINVAL; api = get_sysapi(cxt); assert(api); assert(api->fd_tree >= 0); DBG(HOOK, ul_debugobj(hs, "move_mount(to=%s)", target)); /* umount old target if we created a clone */ if (cxt->force_clone && !api->is_new_fs && !mnt_optlist_is_bind(cxt->optlist)) { DBG(HOOK, ul_debugobj(hs, "remove expired target")); umount2(target, MNT_DETACH); } rc = move_mount(api->fd_tree, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH); set_syscall_status(cxt, "move_mount", rc == 0); return rc == 0 ? 0 : -errno; } static inline int fsopen_is_supported(void) { int dummy, rc = 1; errno = 0; dummy = fsopen(NULL, FSOPEN_CLOEXEC); if (errno == ENOSYS) rc = 0; if (dummy >= 0) close(dummy); return rc; } static inline int mount_setattr_is_supported(void) { int rc; errno = 0; rc = mount_setattr(-1, NULL, 0, NULL, 0); return !(rc == -1 && errno == ENOSYS); } /* * open_tree() and fsopen() */ static int init_sysapi(struct libmnt_context *cxt, const struct libmnt_hookset *hs, unsigned long flags) { struct libmnt_sysapi *api; const char *path = NULL; assert(cxt); assert(hs); DBG(HOOK, ul_debugobj(hs, "initialize API fds")); /* A) tree based operation -- the tree is mount point */ if ((flags & MS_REMOUNT) || mnt_context_propagation_only(cxt)) { DBG(HOOK, ul_debugobj(hs, " REMOUNT/propagation")); path = mnt_fs_get_target(cxt->fs); if (!path) return -EINVAL; /* B) tree based operation -- the tree is mount source */ } else if ((flags & MS_BIND) || (flags & MS_MOVE)) { DBG(HOOK, ul_debugobj(hs, " BIND/MOVE")); path = mnt_fs_get_srcpath(cxt->fs); if (!path) return -EINVAL; } api = new_hookset_data(cxt, hs); if (!api) return -ENOMEM; if (path) { api->fd_tree = open_mount_tree(cxt, path, flags); if (api->fd_tree < 0) goto fail; /* C) FS based operation * * Note, fstype is optinal and may be specified later if mount by * list of FS types (mount -t foo,bar,ext4). In this case fsopen() * is called later in hook_create_mount(). */ } else { const char *type = mnt_fs_get_fstype(cxt->fs); int rc = 0; /* fsopen() to create a superblock */ if (cxt->helper == NULL && type && !strchr(type, ',')) rc = open_fs_configuration_context(cxt, api, type); /* dummy fsopen() to test if API is available */ else if (!fsopen_is_supported()) { errno = ENOSYS; rc = -errno; set_syscall_status(cxt, "fsopen", rc == 0); } if (rc < 0) goto fail; } return 0; fail: DBG(HOOK, ul_debugobj(hs, "init fs/tree failed [errno=%d %m]", errno)); return -errno; } static int force_classic_mount(struct libmnt_context *cxt) { const char *env = getenv("LIBMOUNT_FORCE_MOUNT2"); if (env) { if (strcmp(env, "always") == 0) return 1; if (strcmp(env, "never") == 0) return 0; } /* "auto" (default) -- try to be smart */ /* For external /sbin/mount. helpers we use the new API only for * propagation setting. In this case, the usability of mount_setattr() * will be verified later */ if (cxt->helper) return 0; /* * The current kernel btrfs driver does not completely implement * fsconfig() as it does not work with selinux stuff. * * Don't use the new mount API in this situation. Let's hope this issue * is temporary. */ { const char *type = mnt_fs_get_fstype(cxt->fs); if (type && strcmp(type, "btrfs") == 0 && cxt->has_selinux_opt) return 1; } return 0; } /* * Analyze library context and register hook to call mount-like syscalls. * * Note that this function interprets classic MS_* flags by new Linux mount FD * based API. * * Returns: 0 on success, <0 on error, >0 on recover-able error */ static int hook_prepare(struct libmnt_context *cxt, const struct libmnt_hookset *hs, void *data __attribute__((__unused__))) { struct libmnt_optlist *ol; unsigned long flags = 0; uint64_t set = 0, clr = 0; int rc = 0; assert(cxt); assert(hs == &hookset_mount); if (force_classic_mount(cxt)) { DBG(HOOK, ul_debugobj(hs, "new API disabled")); return 0; } DBG(HOOK, ul_debugobj(hs, "prepare mount")); ol = mnt_context_get_optlist(cxt); if (!ol) return -ENOMEM; /* classic MS_* flags (include oprations like MS_REMOUNT, etc) */ rc = mnt_optlist_get_flags(ol, &flags, cxt->map_linux, 0); /* MOUNT_ATTR_* flags for mount_setattr() */ if (!rc) rc = mnt_optlist_get_attrs(ol, &set, &clr, 0); /* open_tree() or fsopen() */ if (!rc) { rc = init_sysapi(cxt, hs, flags); if (rc && cxt->syscall_status == -ENOSYS) goto enosys; } /* check mutually exclusive operations */ if (!rc && (flags & MS_BIND) && (flags & MS_MOVE)) return -EINVAL; if (!rc && (flags & MS_MOVE) && (flags & MS_REMOUNT)) return -EINVAL; /* classic remount (note -oremount,bind,ro is not superblock reconfiguration) */ if (!rc && cxt->helper == NULL && (flags & MS_REMOUNT) && !(flags & MS_BIND)) rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT, NULL, hook_reconfigure_mount); /* create a new FS instance */ else if (!rc && cxt->helper == NULL && !(flags & MS_BIND) && !(flags & MS_MOVE) && !(flags & MS_REMOUNT) && !mnt_context_propagation_only(cxt)) rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT, NULL, hook_create_mount); /* call mount_setattr() */ if (!rc && cxt->helper == NULL && (set != 0 || clr != 0 || (flags & MS_REMOUNT))) { /* * mount_setattr() supported, but not usable for remount * https://github.com/torvalds/linux/commit/dd8b477f9a3d8edb136207acb3652e1a34a661b7 */ if (get_linux_version() < KERNEL_VERSION(5, 14, 0)) goto enosys; if (!mount_setattr_is_supported()) goto enosys; rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT, NULL, hook_set_vfsflags); } /* call move_mount() to attach target */ if (!rc && cxt->helper == NULL && (cxt->force_clone || (!(flags & MS_REMOUNT) && !mnt_context_propagation_only(cxt)))) rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT_POST, NULL, hook_attach_target); /* set propagation (has to be attached to VFS) */ if (!rc && mnt_optlist_get_propagation(ol)) { if (!mount_setattr_is_supported()) goto enosys; rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT_POST, NULL, hook_set_propagation); } DBG(HOOK, ul_debugobj(hs, "prepare mount done [rc=%d]", rc)); return rc; enosys: /* we need to recover from this error, so hook_mount_legacy.c * can try to continue */ DBG(HOOK, ul_debugobj(hs, "failed to init new API")); reset_syscall_status(cxt); hookset_deinit(cxt, hs); return 1; } const struct libmnt_hookset hookset_mount = { .name = "__mount", .firststage = MNT_STAGE_PREP, .firstcall = hook_prepare, .deinit = hookset_deinit }; #endif /* USE_LIBMOUNT_MOUNTFD_SUPPORT */