]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libmount: support "recursive" argument for VFS attributes
authorKarel Zak <kzak@redhat.com>
Wed, 15 Feb 2023 13:30:58 +0000 (14:30 +0100)
committerKarel Zak <kzak@redhat.com>
Wed, 15 Feb 2023 13:30:58 +0000 (14:30 +0100)
 mount  -o bind,ro=recursive,nosuid /foo /bar

sets all sub-mount to read-only, but only /bar will be nosuid.

Addresses: https://github.com/util-linux/util-linux/issues/1501
Signed-off-by: Karel Zak <kzak@redhat.com>
libmount/src/hook_mount.c
libmount/src/mountP.h
libmount/src/optlist.c
sys-utils/mount.8.adoc

index 958d5956cae4e3a33c90f76bb5c40a2554f45a97..09a1f3f32507baef63f47697ff54d375c464ac75 100644 (file)
@@ -134,9 +134,9 @@ static int configure_superblock(struct libmnt_context *cxt,
                const struct libmnt_optmap *ent = mnt_opt_get_mapent(opt);
 
                if (ent && mnt_opt_get_map(opt) == cxt->map_linux &&
-                   ent->id == MS_RDONLY)
-                       ;
-               else if (!name || mnt_opt_get_map(opt) || mnt_opt_is_external(opt))
+                   ent->id == MS_RDONLY) {
+                       value = NULL;
+               else if (!name || mnt_opt_get_map(opt) || mnt_opt_is_external(opt))
                        continue;
 
                DBG(HOOK, ul_debugobj(hs, "  fsconfig(name=%s,value=%s)", name, value));
@@ -303,18 +303,14 @@ static int hook_reconfigure_mount(struct libmnt_context *cxt,
        return rc;
 }
 
-static int hook_set_vfsflags(struct libmnt_context *cxt,
+static int set_vfsflags(struct libmnt_context *cxt,
                        const struct libmnt_hookset *hs,
-                       void *data __attribute__((__unused__)))
+                       uint64_t set, uint64_t clr, int recursive)
 {
        struct libmnt_sysapi *api;
-       struct libmnt_optlist *ol;
        struct mount_attr attr = { .attr_clr = 0 };
        unsigned int callflags = AT_EMPTY_PATH;
-       uint64_t set = 0, clr = 0;
-       int rc = 0;
-
-       DBG(HOOK, ul_debugobj(hs, "setting VFS flags"));
+       int rc;
 
        api = get_sysapi(cxt);
        assert(api);
@@ -324,19 +320,11 @@ static int hook_set_vfsflags(struct libmnt_context *cxt,
        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;
+                       return rc;
                rc = 0;
        }
 
-       ol = mnt_context_get_optlist(cxt);
-       if (!ol)
-               return -ENOMEM;
-
-       rc = mnt_optlist_get_attrs(ol, &set, &clr);
-       if (rc)
-               return rc;
-
-       if (mnt_optlist_is_recursive(ol))
+       if (recursive)
                callflags |= AT_RECURSIVE;
 
        if (set & (MOUNT_ATTR_RELATIME | MOUNT_ATTR_NOATIME | MOUNT_ATTR_STRICTATIME))
@@ -347,15 +335,45 @@ static int hook_set_vfsflags(struct libmnt_context *cxt,
        attr.attr_set = set;
        attr.attr_clr = clr;
 
+       errno = 0;
        rc = mount_setattr(api->fd_tree, "", callflags, &attr, sizeof(attr));
        set_syscall_status(cxt, "move_setattr", rc == 0);
 
        if (rc && errno == EINVAL)
                return -MNT_ERR_APPLYFLAGS;
-done:
+
        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;
+
+       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__)))
@@ -544,7 +562,7 @@ static int hook_prepare(struct libmnt_context *cxt,
 
        /* MOUNT_ATTR_* flags for mount_setattr() */
        if (!rc)
-               rc = mnt_optlist_get_attrs(ol, &set, &clr);
+               rc = mnt_optlist_get_attrs(ol, &set, &clr, 0);
 
        /* open_tree() or fsopen() */
        if (!rc)
index 438ccfa7efc720a36ca10e2762e5841299a77c23..0d341c65a6a9e9794cb2f7692ad3f0b27cdb9c61 100644 (file)
@@ -544,9 +544,16 @@ enum {
        __MNT_OL_FLTR_COUNT     /* keep it last */
 };
 
+
 extern int mnt_optlist_get_flags(struct libmnt_optlist *ls, unsigned long *flags,
                           const struct libmnt_optmap *map, unsigned int what);
-extern int mnt_optlist_get_attrs(struct libmnt_optlist *ls, uint64_t *set, uint64_t *clr);
+
+/* recursive status for mnt_optlist_get_attrs() */
+#define MNT_OL_REC     1
+#define MNT_OL_NOREC   2
+
+extern int mnt_optlist_get_attrs(struct libmnt_optlist *ls, uint64_t *set, uint64_t *clr, int rec);
+
 extern int mnt_optlist_get_optstr(struct libmnt_optlist *ol, const char **optstr,
                         const struct libmnt_optmap *map, unsigned int what);
 extern int mnt_optlist_strdup_optstr(struct libmnt_optlist *ls, char **optstr,
index d0243d9039546a62d92c643aa367a344914ff701..acdf47150add3d4452ece888026621f900a64a15 100644 (file)
@@ -44,6 +44,8 @@ struct libmnt_opt {
        enum libmnt_optsrc      src;
 
        unsigned int external : 1,      /* visible for external helpers only */
+                    recursive : 1,     /* recursive flag */
+                    is_linux : 1,      /* defined in ls->linux_map (VFS attr) */
                     quoted : 1;        /* name="value" */
 };
 
@@ -206,7 +208,6 @@ int mnt_optlist_remove_opt(struct libmnt_optlist *ls, struct libmnt_opt *opt)
 
                if (opt->ent->id & MS_REC)
                        ls->is_recursive = 0;
-
        }
 
        optlist_cleanup_cache(ls, opt->map);
@@ -360,6 +361,42 @@ int mnt_optlist_merge_opts(struct libmnt_optlist *ls)
        return 0;
 }
 
+#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT
+static inline uint64_t flag_to_attr(unsigned long flag)
+{
+       switch (flag) {
+       case MS_RDONLY:
+               return MOUNT_ATTR_RDONLY;
+       case MS_NOSUID:
+               return MOUNT_ATTR_NOSUID;
+       case MS_NOEXEC:
+               return MOUNT_ATTR_NOEXEC;
+       case MS_NODIRATIME:
+               return MOUNT_ATTR_NODIRATIME;
+       case MS_RELATIME:
+               return MOUNT_ATTR_RELATIME;
+       case MS_NOATIME:
+               return MOUNT_ATTR_NOATIME;
+       case MS_STRICTATIME:
+               return MOUNT_ATTR_STRICTATIME;
+       case MS_NOSYMFOLLOW:
+               return MOUNT_ATTR_NOSYMFOLLOW;
+       }
+       return 0;
+}
+
+/*
+ * Is the @opt relevant for mount_setattr() ?
+ */
+static inline int is_vfs_opt(struct libmnt_opt *opt)
+{
+       if (!opt->map || !opt->ent || !opt->ent->id || !opt->is_linux)
+               return 0;
+
+       return flag_to_attr(opt->ent->id) == 0 ? 0 : 1;
+}
+#endif
+
 static struct libmnt_opt *optlist_new_opt(struct libmnt_optlist *ls,
                        const char *name, size_t namesz,
                        const char *value, size_t valsz,
@@ -401,6 +438,8 @@ static struct libmnt_opt *optlist_new_opt(struct libmnt_optlist *ls,
 
        /* shortcuts */
        if (map && ent && map == ls->linux_map) {
+               opt->is_linux = 1;
+
                if (ent->id & MS_PROPAGATION)
                        ls->propagation |= ent->id;
                else if (opt->ent->id == MS_REMOUNT)
@@ -416,10 +455,16 @@ static struct libmnt_opt *optlist_new_opt(struct libmnt_optlist *ls,
                else if (opt->ent->id == MS_SILENT)
                        ls->is_silent = 1;
 
-               if (opt->ent->id & MS_REC)
+               if (opt->ent->id & MS_REC) {
                        ls->is_recursive = 1;
+                       opt->recursive = 1;
+               }
        }
-
+#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT
+       if (!opt->recursive && opt->value
+           && is_vfs_opt(opt) && strcmp(opt->value, "recursive") == 0)
+               opt->recursive = 1;
+#endif
        if (ent && map) {
                DBG(OPTLIST, ul_debugobj(ls, " added %s [id=0x%08x map=%p]",
                                opt->name, ent->id, map));
@@ -752,37 +797,13 @@ int mnt_optlist_get_flags(struct libmnt_optlist *ls, unsigned long *flags,
        return 0;
 }
 
-#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT
-static inline uint64_t flag_to_attr(unsigned long flag)
-{
-       switch (flag) {
-       case MS_RDONLY:
-               return MOUNT_ATTR_RDONLY;
-       case MS_NOSUID:
-               return MOUNT_ATTR_NOSUID;
-       case MS_NOEXEC:
-               return MOUNT_ATTR_NOEXEC;
-       case MS_NODIRATIME:
-               return MOUNT_ATTR_NODIRATIME;
-       case MS_RELATIME:
-               return MOUNT_ATTR_RELATIME;
-       case MS_NOATIME:
-               return MOUNT_ATTR_NOATIME;
-       case MS_STRICTATIME:
-               return MOUNT_ATTR_STRICTATIME;
-       case MS_NOSYMFOLLOW:
-               return MOUNT_ATTR_NOSYMFOLLOW;
-       }
-       return 0;
-}
-#endif
 
 /*
  * Like mnt_optlist_get_flags() for VFS flags, but converts classic MS_* flags to
  * new MOUNT_ATTR_*
  */
 #ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT
-int mnt_optlist_get_attrs(struct libmnt_optlist *ls, uint64_t *set, uint64_t *clr)
+int mnt_optlist_get_attrs(struct libmnt_optlist *ls, uint64_t *set, uint64_t *clr, int rec)
 {
        struct libmnt_iter itr;
        struct libmnt_opt *opt;
@@ -800,6 +821,12 @@ int mnt_optlist_get_attrs(struct libmnt_optlist *ls, uint64_t *set, uint64_t *cl
                        continue;
                if (!opt->ent || !opt->ent->id)
                        continue;
+
+               if (rec == MNT_OL_REC && !opt->recursive)
+                       continue;
+               if (rec == MNT_OL_NOREC && opt->recursive)
+                       continue;
+
                if (!is_wanted_opt(opt, ls->linux_map, MNT_OL_FLTR_DFLT))
                        continue;
                x = flag_to_attr( opt->ent->id );
@@ -823,7 +850,8 @@ int mnt_optlist_get_attrs(struct libmnt_optlist *ls, uint64_t *set, uint64_t *cl
 #else
 int mnt_optlist_get_attrs(struct libmnt_optlist *ls __attribute__((__unused__)),
                          uint64_t *set __attribute__((__unused__)),
-                         uint64_t *clr __attribute__((__unused__)))
+                         uint64_t *clr __attribute__((__unused__)),
+                         int mask)
 {
        return 0;
 }
@@ -1057,7 +1085,14 @@ const struct libmnt_optmap *mnt_opt_get_mapent(struct libmnt_opt *opt)
 
 int mnt_opt_set_value(struct libmnt_opt *opt, const char *str)
 {
-       return strdup_to_struct_member(opt, value, str);
+       int rc;
+
+       opt->recursive = 0;
+       rc = strdup_to_struct_member(opt, value, str);
+
+       if (rc == 0 && str && strcmp(str, "recursive") == 0)
+               opt->recursive = 1;
+       return rc;
 }
 
 int mnt_opt_set_u64value(struct libmnt_opt *opt, uint64_t num)
index 50c10be2b102429cf15ed64bacd13e4bc8603ecd..a701a12e1c4ff7f8b35832f3551ff8a3bf896b74 100644 (file)
@@ -234,7 +234,7 @@ ____
 
 Note that a read-only bind will create a read-only mountpoint (VFS entry), but the original filesystem superblock will still be writable, meaning that the _olddir_ will be writable, but the _newdir_ will be read-only.
 
-It's also possible to change nosuid, nodev, noexec, noatime, nodiratime, relatime and nosymfollow VFS entry flags via a "remount,bind" operation. The other flags (for example filesystem-specific flags) are silently ignored. It's impossible to change mount options recursively (for example with *-o rbind,ro*).
+It's also possible to change nosuid, nodev, noexec, noatime, nodiratime, relatime and nosymfollow VFS entry flags via a "remount,bind" operation. The other flags (for example filesystem-specific flags) are silently ignored. The classic mount(2) system call does not allow to change mount options recursively (for example with *-o rbind,ro*). The recursive semantic is possible with a new mount_setattr(2) kernel system call and it's supported since libmount from util-linux v2.39 by a new experimental "recursive" option argument (e.g. *-o rbind,ro=recursive*). For more details see the *FILESYSTEM-INDEPENDENT MOUNT OPTIONS* section for more details.
 
 Since util-linux 2.31, *mount* ignores the *bind* flag from _/etc/fstab_ on a *remount* operation (if *-o remount* is specified on command line). This is necessary to fully control mount options on remount by command line. In previous versions the bind flag has been always applied and it was impossible to re-define mount options without interaction with the bind semantic. This *mount* behavior does not affect situations when "remount,bind" is specified in the _/etc/fstab_ file.
 
@@ -454,6 +454,34 @@ Some of these options are only useful when they appear in the _/etc/fstab_ file.
 
 Some of these options could be enabled or disabled by default in the system kernel. To check the current setting see the options in _/proc/mounts_. Note that filesystems also have per-filesystem specific default mount options (see for example *tune2fs -l* output for ext__N__ filesystems).
 
+The options *nosuid*, *noexec*, *nodiratime*, *relatime*, *noatime*, *strictatime*, and *nosymfollow* are interpreted only by the abstract VFS kernel layer and applied to the mountpoint node rather than to the filesystem itself. Try:  
+____
+
+ findmnt -o TARGET,VFS-OPTIONS,FS-OPTIONS
+____
+
+to get a complete overview of filesystems and VFS options.
+
+The read-only setting (*ro* or *rw*) is interpreted by VFS and the filesystem
+and depends on how the option is specified on the mount(8) command line. The
+default is to interpret it on the filesystem level. The operation "-o bind,remount,ro"
+is applied only to the VFS mountpoint, and operation "-o remount,ro" is applied to
+VFS and filesystem superblock. This semantic allows create a read-only
+mountpoint but keeps the filesystem writable from another mountpoint.
+
+Since v2.39 libmount can use a new kernel mount interface to set the VFS
+options recursive. For backward compatibility, this feature is not enabled by
+default, although recursive operation (e.g. rbind) has been requested. The new
+option argument "recursive" could be specified, for example:
+____
+
+ mount -orbind,ro=recursive,noexec=recursive,nosuid /foo /bar
+____
+
+recursively binds filesystems from /foo to /bar, /bar, and all submounts will
+be read-only and noexec, but only /bar itself will be "nosuid". The "recursive"
+optional argument for VFS mount options is an EXPERIMENTAL feature. 
+
 The following options apply to any filesystem that is being mounted (but not every filesystem actually honors them - e.g., the *sync* option today has an effect only for ext2, ext3, ext4, fat, vfat, ufs and xfs):
 
 *async*::