* [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT
@ 2026-07-15 10:11 Miklos Szeredi
2026-07-15 16:43 ` Giuseppe Scrivano
0 siblings, 1 reply; 2+ messages in thread
From: Miklos Szeredi @ 2026-07-15 10:11 UTC (permalink / raw)
To: Giuseppe Scrivano
Cc: linux-fsdevel, linux-unionfs, linux-api, linux-erofs,
Amir Goldstein, Gao Xiang
This is a prototype patch. Needs to be split up!
1) O_ALT / OPEN_TREE_ALT
These open an alternative namespace rooted at dfd - instead of resolving
the path in the real filesystem, it resolves it in a virtual tree that
exposes metadata about that file.
This is intended to provide an alternative to adding new ioctls:
- provides structured namespace
- allows accessing data through fs ops
Could call this O_META, but O_ALT seems more generic and there could be
uses beyond metadata (e.g. seekable, in-place decompression).
This has been discussed previously:
https://lore.kernel.org/all/CAHk-=wjzLmMRf=QG-n+1HnxWCx4KTQn9+OhVvUSJ=ZCQd6Y1WA@mail.gmail.com/
2/a) s_op.get_options(), sb_add_option()
.get_options() is a generic version of .show_options().
After converting everyting to seq_show_option() the rest is trivial:
- change first arg to "struct sb_opt_ctx *ctx"
- replace "seq_show_option(m" with "sb_add_option(ctx"
2/b) sb_add_option_path()
The last arg is "struct path *". This is for options that designate a
path (e.g. stacked fs layers).
Will allow opening the path through O_ALT. Ignored when retrieving the
string value of the option.
3) MNT_CLONABLE
Allow an internal mount to be cloned. Nsfs and pidfs are already special
cased, move these over to this flag.
Also allow overlayfs layers to be cloned.
4) mnt.mnt_devname -> sb.s_devname
This is historical thing, mnt_devname was just copied when cloning the
mount, sharing the same value for all mounts belonging to a super block.
5) mount options in metafs
Support retrieving mount options through O_ALT opens:
mount/options/OPT/N: Nth instance of OPT
This will be a symlink pointint to the option value. If the option refers
to a path (as per 2/b), following the symlink jumps to the given path.
At this point only overlayfs and erofs are converted.
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
Documentation/filesystems/vfs.rst | 2 +-
fs/Makefile | 2 +
fs/erofs/super.c | 29 +-
fs/fcntl.c | 2 +-
fs/internal.h | 1 +
fs/metafs/Makefile | 6 +
fs/metafs/metafs.c | 572 ++++++++++++++++++++++++++++++
fs/metafs/metafs.h | 15 +
fs/mount.h | 1 -
fs/namei.c | 16 +
fs/namespace.c | 84 +++--
fs/nsfs.c | 1 +
fs/open.c | 5 +-
fs/overlayfs/params.c | 43 ++-
fs/overlayfs/params.h | 2 +-
fs/overlayfs/super.c | 5 +-
fs/pidfs.c | 4 +-
fs/proc_namespace.c | 50 ++-
fs/super.c | 12 +-
include/linux/fcntl.h | 2 +-
include/linux/fs/super.h | 10 +
include/linux/fs/super_types.h | 4 +
include/linux/mount.h | 3 +-
include/linux/namei.h | 3 +-
include/linux/pidfs.h | 1 -
include/uapi/asm-generic/fcntl.h | 4 +
include/uapi/linux/mount.h | 1 +
scripts/gdb/linux/proc.py | 5 +-
28 files changed, 774 insertions(+), 111 deletions(-)
create mode 100644 fs/metafs/Makefile
create mode 100644 fs/metafs/metafs.c
create mode 100644 fs/metafs/metafs.h
diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
index 7c753148af88..3dc431b6cdbb 100644
--- a/Documentation/filesystems/vfs.rst
+++ b/Documentation/filesystems/vfs.rst
@@ -314,7 +314,7 @@ or bottom half).
``show_devname``
Optional. Called by the VFS to show device name for
/proc/<pid>/{mounts,mountinfo,mountstats}. If not provided then
- '(struct mount).mnt_devname' will be used.
+ '(struct super_block).s_devname' will be used.
``show_path``
Optional. Called by the VFS (for /proc/<pid>/mountinfo) to show
diff --git a/fs/Makefile b/fs/Makefile
index 89a8a9d207d1..87a4c23d5a08 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -58,6 +58,8 @@ obj-$(CONFIG_CONFIGFS_FS) += configfs/
obj-y += devpts/
obj-$(CONFIG_DLM) += dlm/
+
+obj-y += metafs/
# Do not add any filesystems before this line
obj-$(CONFIG_NETFS_SUPPORT) += netfs/
diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index 86fa5c6a0c70..257517c28e1d 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -1028,29 +1028,34 @@ static int erofs_statfs(struct dentry *dentry, struct kstatfs *buf)
return 0;
}
-static int erofs_show_options(struct seq_file *seq, struct dentry *root)
+static int erofs_get_options(struct sb_opt_ctx *ctx, struct dentry *root)
{
struct erofs_sb_info *sbi = EROFS_SB(root->d_sb);
struct erofs_mount_opts *opt = &sbi->opt;
+ if (sbi->dif0.file)
+ sb_add_option_path(ctx, "source", NULL, &sbi->dif0.file->f_path);
+
if (IS_ENABLED(CONFIG_EROFS_FS_XATTR))
- seq_puts(seq, test_opt(opt, XATTR_USER) ?
- ",user_xattr" : ",nouser_xattr");
+ sb_add_option(ctx, test_opt(opt, XATTR_USER) ?
+ "user_xattr" : "nouser_xattr", NULL);
if (IS_ENABLED(CONFIG_EROFS_FS_POSIX_ACL))
- seq_puts(seq, test_opt(opt, POSIX_ACL) ? ",acl" : ",noacl");
+ sb_add_option(ctx, test_opt(opt, POSIX_ACL) ? "acl" : "noacl", NULL);
if (IS_ENABLED(CONFIG_EROFS_FS_ZIP))
- seq_printf(seq, ",cache_strategy=%s",
+ sb_add_option(ctx, "cache_strategy",
erofs_param_cache_strategy[opt->cache_strategy].name);
if (test_opt(opt, DAX_ALWAYS))
- seq_puts(seq, ",dax=always");
+ sb_add_option(ctx, "dax", "always");
if (test_opt(opt, DAX_NEVER))
- seq_puts(seq, ",dax=never");
+ sb_add_option(ctx, "dax", "never");
if (erofs_is_fileio_mode(sbi) && test_opt(opt, DIRECT_IO))
- seq_puts(seq, ",directio");
- if (sbi->dif0.fsoff)
- seq_printf(seq, ",fsoffset=%llu", sbi->dif0.fsoff);
+ sb_add_option(ctx, "directio", NULL);
+ if (sbi->dif0.fsoff) {
+ char *val __free(kfree) = kasprintf(GFP_KERNEL, "%llu", sbi->dif0.fsoff);
+ sb_add_option(ctx, "fsoffset", val);
+ }
if (test_opt(opt, INODE_SHARE))
- seq_puts(seq, ",inode_share");
+ sb_add_option(ctx, "inode_share", NULL);
return 0;
}
@@ -1069,7 +1074,7 @@ const struct super_operations erofs_sops = {
.free_inode = erofs_free_inode,
.evict_inode = erofs_evict_inode,
.statfs = erofs_statfs,
- .show_options = erofs_show_options,
+ .get_options = erofs_get_options,
};
module_init(erofs_module_init);
diff --git a/fs/fcntl.c b/fs/fcntl.c
index c158f082f1da..01b765cfac84 100644
--- a/fs/fcntl.c
+++ b/fs/fcntl.c
@@ -1169,7 +1169,7 @@ static int __init fcntl_init(void)
* Exceptions: O_NONBLOCK is a two bit define on parisc; O_NDELAY
* is defined as O_NONBLOCK on some platforms and not on others.
*/
- BUILD_BUG_ON(22 - 1 /* for O_RDONLY being 0 */ !=
+ BUILD_BUG_ON(23 - 1 /* for O_RDONLY being 0 */ !=
HWEIGHT32(
(VALID_OPEN_FLAGS & ~(O_NONBLOCK | O_NDELAY)) |
__FMODE_EXEC | __O_REGULAR));
diff --git a/fs/internal.h b/fs/internal.h
index 355d93f92208..e73a5c1d5b4e 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -95,6 +95,7 @@ int path_umount(const struct path *path, int flags);
int path_pivot_root(struct path *new, struct path *old);
int show_path(struct seq_file *m, struct dentry *root);
+int mnt_show_options(struct seq_file *seq, struct vfsmount *mnt);
/*
* fs_struct.c
diff --git a/fs/metafs/Makefile b/fs/metafs/Makefile
new file mode 100644
index 000000000000..f855b48d9c2b
--- /dev/null
+++ b/fs/metafs/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for meta filesystem
+#
+
+obj-y := metafs.o
diff --git a/fs/metafs/metafs.c b/fs/metafs/metafs.c
new file mode 100644
index 000000000000..15ec0b6064da
--- /dev/null
+++ b/fs/metafs/metafs.c
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * metafs - virtual filesystem for metadata access
+ *
+ * This provides a way to access metadata about files and filesystems
+ * through O_ALT file descriptors.
+ */
+
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/magic.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/slab.h>
+#include <linux/dcache.h>
+#include <linux/pseudo_fs.h>
+#include <linux/fs/super.h>
+#include "metafs.h"
+
+/*
+ * metafs superblock and mount
+ */
+static struct vfsmount *metafs_mnt __ro_after_init;
+
+static const struct super_operations metafs_ops = {
+ .statfs = simple_statfs,
+ .drop_inode = inode_just_drop,
+};
+
+static int metafs_init_fs_context(struct fs_context *fc)
+{
+ struct pseudo_fs_context *ctx = init_pseudo(fc, 0x4d455441); /* 'META' */
+ if (!ctx)
+ return -ENOMEM;
+ ctx->ops = &metafs_ops;
+ return 0;
+}
+
+static struct file_system_type metafs_type = {
+ .name = "metafs",
+ .init_fs_context = metafs_init_fs_context,
+ .kill_sb = kill_anon_super,
+};
+
+/*
+ * Mount option collection structures
+ */
+enum metafs_dentry_type {
+ METAFS_ROOT,
+ METAFS_OPTION_VALUES,
+ METAFS_SYMLINK,
+};
+
+struct metafs_option_entry {
+ char *value;
+ struct path path;
+};
+
+struct metafs_dentry_info {
+ enum metafs_dentry_type type;
+ union {
+ struct {
+ struct file *base;
+ } root;
+ struct {
+ struct metafs_option_entry *entries;
+ int count;
+ } opt_values;
+ struct metafs_option_entry symlink;
+ };
+};
+
+struct metafs_collect_ctx {
+ struct sb_opt_ctx ctx;
+ const char *target_name;
+ struct super_block *sb;
+ struct metafs_option_entry *entries;
+ int count;
+ int size;
+ int err;
+ bool is_source;
+};
+
+/*
+ * Collect specific mount option values from the base filesystem
+ */
+static void metafs_collect_option(struct sb_opt_ctx *ctx, const char *name,
+ const char *value, const struct path *path)
+{
+ struct metafs_collect_ctx *collect_ctx = ctx->data;
+ struct metafs_option_entry *entry;
+
+ if (strcmp(name, collect_ctx->target_name) != 0)
+ return;
+
+ if (!value && collect_ctx->is_source)
+ value = collect_ctx->sb->s_devname;
+
+ if (collect_ctx->count >= collect_ctx->size) {
+ int new_size = collect_ctx->size ? collect_ctx->size * 2 : 4;
+ struct metafs_option_entry *new_entries;
+
+ new_entries = krealloc_array(collect_ctx->entries, new_size, sizeof(*new_entries),
+ GFP_KERNEL);
+ if (!new_entries) {
+ collect_ctx->err = -ENOMEM;
+ return;
+ }
+ collect_ctx->entries = new_entries;
+ collect_ctx->size = new_size;
+ }
+
+ entry = &collect_ctx->entries[collect_ctx->count];
+ entry->value = value ? kstrdup(value, GFP_KERNEL) : NULL;
+ if (!entry->value && value) {
+ collect_ctx->err = -ENOMEM;
+ return;
+ }
+ if (path) {
+ entry->path = *path;
+ path_get(&entry->path);
+ } else {
+ memset(&entry->path, 0, sizeof(entry->path));
+ }
+ collect_ctx->count++;
+}
+
+static void metafs_free_entries(struct metafs_option_entry *entries, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ kfree(entries[i].value);
+ if (entries[i].path.mnt)
+ path_put(&entries[i].path);
+ }
+ kfree(entries);
+}
+
+static int metafs_get_option_values(struct file *base, const char *option_name,
+ struct metafs_option_entry **entries_out,
+ int *count_out)
+{
+ const struct path *path = &base->f_path;
+ struct super_block *sb = path->mnt->mnt_sb;
+ struct metafs_collect_ctx collect_ctx = {
+ .ctx.data = &collect_ctx,
+ .ctx.actor = metafs_collect_option,
+ .target_name = option_name,
+ .sb = sb,
+ .is_source = !strcmp(option_name, "source"),
+ };
+ int ret;
+
+ if (sb->s_op->get_options) {
+ ret = sb->s_op->get_options(&collect_ctx.ctx, path->mnt->mnt_root);
+ if (ret || collect_ctx.err) {
+ metafs_free_entries(collect_ctx.entries, collect_ctx.count);
+ return ret ?: collect_ctx.err;
+ }
+ }
+
+ /* special case: add "source" option if filesystem didn't add one*/
+ if (!collect_ctx.count && collect_ctx.is_source) {
+ struct metafs_option_entry *ent __free(kfree) = kzalloc_obj(*ent);
+
+ if (!ent)
+ return -ENOMEM;
+
+ ent->value = kstrdup(sb->s_devname, GFP_KERNEL);
+ if (!ent->value)
+ return -ENOMEM;
+
+ collect_ctx.entries = no_free_ptr(ent);
+ collect_ctx.count = 1;
+ }
+
+ *entries_out = collect_ctx.entries;
+ *count_out = collect_ctx.count;
+ return 0;
+}
+
+/*
+ * metafs symlink operations
+ */
+static const char *metafs_get_link(struct dentry *dentry, struct inode *inode,
+ struct delayed_call *done)
+{
+ struct metafs_dentry_info *info;
+
+ if (!dentry)
+ return ERR_PTR(-ECHILD);
+
+ info = dentry->d_fsdata;
+ if (info->symlink.path.mnt) {
+ int error;
+
+ path_get(&info->symlink.path);
+ error = nd_jump_link(&info->symlink.path);
+ return ERR_PTR(error);
+ }
+
+ return ERR_PTR(-ELOOP);
+}
+
+static int metafs_readlink(struct dentry *dentry, char __user *buffer,
+ int buflen)
+{
+ struct metafs_dentry_info *info = dentry->d_fsdata;
+ const char *value = info->symlink.value;
+
+ if (!value)
+ value = "";
+
+ return readlink_copy(buffer, buflen, value, strlen(value));
+}
+
+static const struct inode_operations metafs_symlink_iops = {
+ .get_link = metafs_get_link,
+ .readlink = metafs_readlink,
+};
+
+/*
+ * Dentry operations - cleanup
+ */
+static void metafs_d_release(struct dentry *dentry)
+{
+ struct metafs_dentry_info *info = dentry->d_fsdata;
+
+ if (!info)
+ return;
+
+ switch (info->type) {
+ case METAFS_ROOT:
+ fput(info->root.base);
+ break;
+ case METAFS_OPTION_VALUES:
+ metafs_free_entries(info->opt_values.entries,
+ info->opt_values.count);
+ break;
+ case METAFS_SYMLINK:
+ kfree(info->symlink.value);
+ if (info->symlink.path.mnt)
+ path_put(&info->symlink.path);
+ break;
+ default:
+ WARN_ON(1);
+ }
+ kfree(info);
+}
+
+static const struct dentry_operations metafs_dentry_ops = {
+ .d_release = metafs_d_release,
+};
+
+/*
+ * metafs directory operations - forward declarations
+ */
+static struct file *metafs_get_base_file(struct dentry *dentry);
+static struct dentry *metafs_root_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags);
+static struct dentry *metafs_mount_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags);
+static struct dentry *metafs_options_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags);
+static struct dentry *metafs_option_values_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags);
+
+static const struct inode_operations metafs_root_dir_iops = {
+ .lookup = metafs_root_lookup,
+};
+
+static const struct inode_operations metafs_mount_dir_iops = {
+ .lookup = metafs_mount_lookup,
+};
+
+static const struct inode_operations metafs_options_dir_iops = {
+ .lookup = metafs_options_lookup,
+};
+
+static const struct inode_operations metafs_option_values_dir_iops = {
+ .lookup = metafs_option_values_lookup,
+};
+
+/*
+ * Create a new inode in metafs
+ */
+static struct inode *metafs_get_inode(struct super_block *sb, umode_t mode,
+ const struct inode_operations *iops)
+{
+ struct inode *inode = new_inode(sb);
+
+ if (!inode)
+ return NULL;
+
+ inode->i_ino = get_next_ino();
+ inode->i_mode = mode;
+ simple_inode_init_ts(inode);
+
+ if (S_ISDIR(mode)) {
+ inode->i_op = iops;
+ inc_nlink(inode);
+ } else if (S_ISLNK(mode)) {
+ inode->i_op = &metafs_symlink_iops;
+ }
+
+ return inode;
+}
+
+/*
+ * Lookup in root directory (/)
+ * Only "mount" is valid here
+ */
+static struct dentry *metafs_root_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ const char *name = dentry->d_name.name;
+ struct inode *inode;
+
+ if (strcmp(name, "mount") != 0)
+ return ERR_PTR(-ENOENT);
+
+ inode = metafs_get_inode(dir->i_sb, S_IFDIR | 0555, &metafs_mount_dir_iops);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+
+ d_add(dentry, inode);
+ d_mark_dontcache(inode);
+ return NULL;
+}
+
+/*
+ * Lookup in /mount directory
+ * Only "options" is valid here
+ */
+static struct dentry *metafs_mount_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ const char *name = dentry->d_name.name;
+ struct inode *inode;
+
+ if (strcmp(name, "options") != 0)
+ return ERR_PTR(-ENOENT);
+
+ inode = metafs_get_inode(dir->i_sb, S_IFDIR | 0555, &metafs_options_dir_iops);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+
+ d_add(dentry, inode);
+ d_mark_dontcache(inode);
+ return NULL;
+}
+
+/*
+ * Lookup in /mount/options directory
+ * Collect values for the requested option name
+ */
+static struct dentry *metafs_options_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ const char *target_name = dentry->d_name.name;
+ struct file *base;
+ struct metafs_dentry_info *info;
+ struct metafs_option_entry *entries = NULL;
+ int count = 0;
+ struct inode *inode;
+ int ret;
+
+ base = metafs_get_base_file(dentry->d_parent);
+ if (!base)
+ return ERR_PTR(-ENOENT);
+
+ ret = metafs_get_option_values(base, target_name, &entries, &count);
+ if (ret)
+ return ERR_PTR(ret);
+
+ if (count == 0) {
+ kfree(entries);
+ return ERR_PTR(-ENOENT);
+ }
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ metafs_free_entries(entries, count);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ info->type = METAFS_OPTION_VALUES;
+ info->opt_values.entries = entries;
+ info->opt_values.count = count;
+
+ inode = metafs_get_inode(dir->i_sb, S_IFDIR | 0555, &metafs_option_values_dir_iops);
+ if (!inode) {
+ metafs_free_entries(entries, count);
+ kfree(info);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ dentry->d_fsdata = info;
+ d_add(dentry, inode);
+ d_mark_dontcache(inode);
+ return NULL;
+}
+
+/*
+ * Lookup in option-specific directory (e.g., /mount/options/lowerdir/)
+ * Return numbered entries (0, 1, 2, ...) as symlinks
+ */
+static struct dentry *metafs_option_values_lookup(struct inode *dir,
+ struct dentry *dentry,
+ unsigned int flags)
+{
+ const char *target_name = dentry->d_name.name;
+ struct metafs_dentry_info *parent_info, *info;
+ struct metafs_option_entry *entry;
+ struct inode *inode;
+ unsigned long index;
+ int err;
+
+ parent_info = dentry->d_parent->d_fsdata;
+ if (WARN_ON(!parent_info || parent_info->type != METAFS_OPTION_VALUES))
+ return ERR_PTR(-ENOENT);
+
+ if (target_name[0] == '0' && target_name[1])
+ return ERR_PTR(-ENOENT);
+
+ err = kstrtoul(target_name, 10, &index);
+ if (err || index >= parent_info->opt_values.count)
+ return ERR_PTR(-ENOENT);
+
+ entry = &parent_info->opt_values.entries[index];
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return ERR_PTR(-ENOMEM);
+
+ info->type = METAFS_SYMLINK;
+ if (entry->value) {
+ info->symlink.value = kstrdup(entry->value, GFP_KERNEL);
+ if (!info->symlink.value) {
+ kfree(info);
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+ if (entry->path.mnt) {
+ info->symlink.path = entry->path;
+ path_get(&info->symlink.path);
+ }
+
+ inode = metafs_get_inode(dir->i_sb, S_IFLNK | 0777, NULL);
+ if (!inode) {
+ kfree(info->symlink.value);
+ if (info->symlink.path.mnt)
+ path_put(&info->symlink.path);
+ kfree(info);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ dentry->d_fsdata = info;
+ d_add(dentry, inode);
+ d_mark_dontcache(inode);
+ return NULL;
+}
+
+/*
+ * Helper to get base file from any metafs dentry
+ */
+static struct file *metafs_get_base_file(struct dentry *dentry)
+{
+ while (dentry) {
+ struct metafs_dentry_info *info = dentry->d_fsdata;
+
+ if (info && info->type == METAFS_ROOT)
+ return info->root.base;
+ if (dentry == dentry->d_parent)
+ break;
+ dentry = dentry->d_parent;
+ }
+ return NULL;
+}
+
+/*
+ * Create the root directory structure for a metadata open
+ */
+static struct dentry *metafs_create_root(struct file *base)
+{
+ struct inode *inode;
+ struct dentry *root;
+ struct super_block *sb = metafs_mnt->mnt_sb;
+ struct metafs_dentry_info *info;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return ERR_PTR(-ENOMEM);
+
+ info->type = METAFS_ROOT;
+ info->root.base = get_file(base);
+
+ inode = metafs_get_inode(sb, S_IFDIR | 0555, &metafs_root_dir_iops);
+ if (!inode) {
+ fput(info->root.base);
+ kfree(info);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ root = d_alloc_anon(sb);
+ if (!root) {
+ iput(inode);
+ fput(info->root.base);
+ kfree(info);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ /* Store the info in d_fsdata before instantiating */
+ root->d_fsdata = info;
+ d_instantiate(root, inode);
+ d_mark_dontcache(inode);
+
+ return root;
+}
+
+/*
+ * Get metadata root path for a base file.
+ *
+ * This is called from path_init() when O_ALT is used.
+ * It returns a path in the metafs that serves as the root
+ * for the metadata lookup.
+ */
+int metafs_get_root(struct file *base, struct path *path)
+{
+ struct dentry *root;
+
+ if (!base)
+ return -EBADF;
+
+ /* Create the metadata root for this base file */
+ root = metafs_create_root(base);
+ if (IS_ERR(root))
+ return PTR_ERR(root);
+
+ /*
+ * Return the path to the metadata root.
+ * Normal VFS lookup will take over from here.
+ */
+ path->mnt = mntget(metafs_mnt);
+ path->dentry = root;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(metafs_get_root);
+
+static int __init metafs_init(void)
+{
+ int err;
+
+ err = register_filesystem(&metafs_type);
+ if (err)
+ return err;
+
+ metafs_mnt = kern_mount(&metafs_type);
+ if (IS_ERR(metafs_mnt)) {
+ err = PTR_ERR(metafs_mnt);
+ unregister_filesystem(&metafs_type);
+ return err;
+ }
+ set_default_d_op(metafs_mnt->mnt_sb, &metafs_dentry_ops);
+
+ return 0;
+}
+
+fs_initcall(metafs_init);
diff --git a/fs/metafs/metafs.h b/fs/metafs/metafs.h
new file mode 100644
index 000000000000..d257714260a7
--- /dev/null
+++ b/fs/metafs/metafs.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_METAFS_H
+#define _LINUX_METAFS_H
+
+struct path;
+struct file;
+
+/*
+ * Get metadata root path for a base file.
+ * Called from path_init() when O_ALT flag is used.
+ * Returns 0 on success with path filled in, -errno on error.
+ */
+int metafs_get_root(struct file *base, struct path *path);
+
+#endif /* _LINUX_METAFS_H */
diff --git a/fs/mount.h b/fs/mount.h
index 94fcc306d21e..69a953bc112b 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -65,7 +65,6 @@ struct mount {
struct mount * __aligned(1) *mnt_pprev_for_sb;
/* except that LSB of pprev is stolen */
#define WRITE_HOLD 1 /* ... for use by mnt_hold_writers() */
- const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
struct list_head mnt_list;
struct list_head mnt_expire; /* link in fs-specific expiry list */
struct list_head mnt_share; /* circular list of shared mounts */
diff --git a/fs/namei.c b/fs/namei.c
index 19ce43c9a6e6..437a14c432e6 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -45,6 +45,7 @@
#include "internal.h"
#include "mount.h"
+#include "metafs/metafs.h"
/* [Feb-1997 T. Schoebel-Theuer]
* Fundamental changes in the pathname lookup mechanisms (namei)
@@ -2753,6 +2754,21 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
return ERR_PTR(-ENOENT);
}
+ if (unlikely(flags & LOOKUP_ALT)) {
+ if (flags & LOOKUP_RCU)
+ return ERR_PTR(-ECHILD);
+
+ /* Handle O_ALT: jump to metafs root */
+ error = metafs_get_root(fd_file(f), &nd->path);
+ if (error)
+ return ERR_PTR(error);
+
+ nd->inode = nd->path.dentry->d_inode;
+ nd->state |= ND_JUMPED;
+
+ return s;
+ }
+
dentry = fd_file(f)->f_path.dentry;
if (*s && unlikely(!d_can_lookup(dentry)))
diff --git a/fs/namespace.c b/fs/namespace.c
index 3d5cd5bf3b05..62cc71c401a2 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -282,7 +282,7 @@ int mnt_get_count(struct mount *mnt)
#endif
}
-static struct mount *alloc_vfsmnt(const char *name)
+static struct mount *alloc_vfsmnt(void)
{
struct mount *mnt = kmem_cache_zalloc(mnt_cache, GFP_KERNEL);
if (mnt) {
@@ -292,18 +292,10 @@ static struct mount *alloc_vfsmnt(const char *name)
if (err)
goto out_free_cache;
- if (name)
- mnt->mnt_devname = kstrdup_const(name,
- GFP_KERNEL_ACCOUNT);
- else
- mnt->mnt_devname = "none";
- if (!mnt->mnt_devname)
- goto out_free_id;
-
#ifdef CONFIG_SMP
mnt->mnt_pcp = alloc_percpu(struct mnt_pcp);
if (!mnt->mnt_pcp)
- goto out_free_devname;
+ goto out_free_id;
this_cpu_add(mnt->mnt_pcp->mnt_count, 1);
#else
@@ -328,11 +320,9 @@ static struct mount *alloc_vfsmnt(const char *name)
return mnt;
#ifdef CONFIG_SMP
-out_free_devname:
- kfree_const(mnt->mnt_devname);
-#endif
out_free_id:
mnt_free_id(mnt);
+#endif
out_free_cache:
kmem_cache_free(mnt_cache, mnt);
return NULL;
@@ -725,7 +715,6 @@ int sb_prepare_remount_readonly(struct super_block *sb)
static void free_vfsmnt(struct mount *mnt)
{
mnt_idmap_put(mnt_idmap(&mnt->mnt));
- kfree_const(mnt->mnt_devname);
#ifdef CONFIG_SMP
free_percpu(mnt->mnt_pcp);
#endif
@@ -1180,7 +1169,7 @@ struct vfsmount *vfs_create_mount(struct fs_context *fc)
if (!fc->root)
return ERR_PTR(-EINVAL);
- mnt = alloc_vfsmnt(fc->source);
+ mnt = alloc_vfsmnt();
if (!mnt)
return ERR_PTR(-ENOMEM);
@@ -1248,7 +1237,7 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,
struct mount *mnt;
int err;
- mnt = alloc_vfsmnt(old->mnt_devname);
+ mnt = alloc_vfsmnt();
if (!mnt)
return ERR_PTR(-ENOMEM);
@@ -2957,16 +2946,12 @@ static int do_change_type(const struct path *path, int ms_flags)
static inline bool may_copy_tree(const struct path *path)
{
struct mount *mnt = real_mount(path->mnt);
- const struct dentry_operations *d_op;
if (check_mnt(mnt))
return true;
- d_op = path->dentry->d_op;
- if (d_op == &ns_dentry_operations)
- return true;
-
- if (d_op == &pidfs_dentry_operations)
+ /* Make an exception for internal mounts explicitly marked as clonable */
+ if (mnt->mnt_ns == MNT_NS_INTERNAL && mnt->mnt.mnt_flags & MNT_CLONABLE)
return true;
if (!is_mounted(path->mnt))
@@ -3201,7 +3186,7 @@ static struct file *vfs_open_tree(int dfd, const char __user *filename, unsigned
if (flags & ~(AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_RECURSIVE |
AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLONE |
- OPEN_TREE_CLOEXEC | OPEN_TREE_NAMESPACE))
+ OPEN_TREE_CLOEXEC | OPEN_TREE_NAMESPACE | OPEN_TREE_ALT))
return ERR_PTR(-EINVAL);
if ((flags & (AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_NAMESPACE)) ==
@@ -3215,6 +3200,8 @@ static struct file *vfs_open_tree(int dfd, const char __user *filename, unsigned
lookup_flags &= ~LOOKUP_AUTOMOUNT;
if (flags & AT_SYMLINK_NOFOLLOW)
lookup_flags &= ~LOOKUP_FOLLOW;
+ if (flags & OPEN_TREE_ALT)
+ lookup_flags |= LOOKUP_ALT;
/*
* If we create a new mount namespace with the cloned mount tree we
@@ -5387,7 +5374,6 @@ static void statmount_fs_subtype(struct kstatmount *s, struct seq_file *seq)
static int statmount_sb_source(struct kstatmount *s, struct seq_file *seq)
{
struct super_block *sb = s->mnt->mnt_sb;
- struct mount *r = real_mount(s->mnt);
if (sb->s_op->show_devname) {
size_t start = seq->count;
@@ -5405,7 +5391,7 @@ static int statmount_sb_source(struct kstatmount *s, struct seq_file *seq)
seq->count = start;
seq_commit(seq, string_unescape_inplace(seq->buf + start, UNESCAPE_OCTAL));
} else {
- seq_puts(seq, r->mnt_devname);
+ seq_puts(seq, sb->s_devname);
}
return 0;
}
@@ -5416,6 +5402,40 @@ static void statmount_mnt_ns_id(struct kstatmount *s, struct mnt_namespace *ns)
s->sm.mnt_ns_id = ns->ns.ns_id;
}
+void sb_add_option(struct sb_opt_ctx *ctx, const char *name, const char *value)
+{
+ sb_add_option_path(ctx, name, value, NULL);
+}
+
+void sb_add_option_path(struct sb_opt_ctx *ctx, const char *name, const char *value,
+ const struct path *path)
+{
+ ctx->actor(ctx, name, value, path);
+}
+
+static void do_show_option(struct sb_opt_ctx *ctx, const char *name, const char *value,
+ const struct path *path)
+{
+ if (strcmp(name, "source") != 0)
+ seq_show_option(ctx->data, name, value);
+}
+
+int mnt_show_options(struct seq_file *seq, struct vfsmount *mnt)
+{
+ struct super_block *sb = mnt->mnt_sb;
+
+ if (sb->s_op->show_options)
+ return sb->s_op->show_options(seq, mnt->mnt_root);
+
+ if (sb->s_op->get_options) {
+ struct sb_opt_ctx ctx = { .data = seq, .actor = do_show_option };
+
+ return sb->s_op->get_options(&ctx, mnt->mnt_root);
+ }
+
+ return 0;
+}
+
static int statmount_mnt_opts(struct kstatmount *s, struct seq_file *seq)
{
struct vfsmount *mnt = s->mnt;
@@ -5427,11 +5447,9 @@ static int statmount_mnt_opts(struct kstatmount *s, struct seq_file *seq)
if (err)
return err;
- if (sb->s_op->show_options) {
- err = sb->s_op->show_options(seq, mnt->mnt_root);
- if (err)
- return err;
- }
+ err = mnt_show_options(seq, mnt);
+ if (err)
+ return err;
if (unlikely(seq_has_overflowed(seq)))
return -EAGAIN;
@@ -5479,14 +5497,10 @@ static inline int statmount_opt_process(struct seq_file *seq, size_t start)
static int statmount_opt_array(struct kstatmount *s, struct seq_file *seq)
{
struct vfsmount *mnt = s->mnt;
- struct super_block *sb = mnt->mnt_sb;
size_t start = seq->count;
int err;
- if (!sb->s_op->show_options)
- return 0;
-
- err = sb->s_op->show_options(seq, mnt->mnt_root);
+ err = mnt_show_options(seq, mnt);
if (err)
return err;
diff --git a/fs/nsfs.c b/fs/nsfs.c
index c3b6ae76594a..e110130d0760 100644
--- a/fs/nsfs.c
+++ b/fs/nsfs.c
@@ -684,6 +684,7 @@ void __init nsfs_init(void)
if (IS_ERR(nsfs_mnt))
panic("can't set nsfs up\n");
nsfs_mnt->mnt_sb->s_flags &= ~SB_NOUSER;
+ nsfs_mnt->mnt_flags |= MNT_CLONABLE;
nsfs_root_path.mnt = nsfs_mnt;
nsfs_root_path.dentry = nsfs_mnt->mnt_root;
}
diff --git a/fs/open.c b/fs/open.c
index 408925d7bd0b..3a081a9e3062 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1131,7 +1131,8 @@ struct file *kernel_file_open(const struct path *path, int flags,
EXPORT_SYMBOL_GPL(kernel_file_open);
#define WILL_CREATE(flags) (flags & (O_CREAT | __O_TMPFILE))
-#define O_PATH_FLAGS (O_DIRECTORY | O_NOFOLLOW | O_PATH | O_CLOEXEC | O_EMPTYPATH)
+#define O_PATH_FLAGS (O_DIRECTORY | O_NOFOLLOW | O_PATH | O_CLOEXEC | O_EMPTYPATH | \
+ O_ALT)
inline struct open_how build_open_how(int flags, umode_t mode)
{
@@ -1283,6 +1284,8 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
lookup_flags |= LOOKUP_FOLLOW;
if (flags & O_EMPTYPATH)
lookup_flags |= LOOKUP_EMPTY;
+ if (flags & O_ALT)
+ lookup_flags |= LOOKUP_ALT;
if (how->resolve & RESOLVE_NO_XDEV)
lookup_flags |= LOOKUP_NO_XDEV;
diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
index c93fcaa45d4a..c5a9e858e366 100644
--- a/fs/overlayfs/params.c
+++ b/fs/overlayfs/params.c
@@ -1046,10 +1046,11 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
* Prints the mount options for a given superblock.
* Returns zero; does not fail.
*/
-int ovl_show_options(struct seq_file *m, struct dentry *dentry)
+int ovl_get_options(struct sb_opt_ctx *ctx, struct dentry *dentry)
{
struct super_block *sb = dentry->d_sb;
struct ovl_fs *ofs = OVL_FS(sb);
+ struct path upperpath;
size_t nr, nr_merged_lower, nr_lower = 0;
char **lowerdirs = ofs->config.lowerdirs;
@@ -1062,43 +1063,47 @@ int ovl_show_options(struct seq_file *m, struct dentry *dentry)
* with the new lowerdir+ and datadir+ mount options.
*/
if (lowerdirs[0]) {
- seq_show_option(m, "lowerdir", lowerdirs[0]);
+ sb_add_option(ctx, "lowerdir", lowerdirs[0]);
} else {
nr_lower = ofs->numlayer;
nr_merged_lower = nr_lower - ofs->numdatalayer;
}
for (nr = 1; nr < nr_lower; nr++) {
+ struct path lowerpath = {
+ .mnt = ofs->layers[nr].mnt,
+ .dentry = ofs->layers[nr].mnt->mnt_root,
+ };
if (nr < nr_merged_lower)
- seq_show_option(m, "lowerdir+", lowerdirs[nr]);
+ sb_add_option_path(ctx, "lowerdir+", lowerdirs[nr],
+ &lowerpath);
else
- seq_show_option(m, "datadir+", lowerdirs[nr]);
+ sb_add_option_path(ctx, "datadir+", lowerdirs[nr],
+ &lowerpath);
}
if (ofs->config.upperdir) {
- seq_show_option(m, "upperdir", ofs->config.upperdir);
- seq_show_option(m, "workdir", ofs->config.workdir);
+ ovl_path_upper(sb->s_root, &upperpath);
+ sb_add_option_path(ctx, "upperdir", ofs->config.upperdir, &upperpath);
+ sb_add_option(ctx, "workdir", ofs->config.workdir);
}
if (ofs->config.default_permissions)
- seq_puts(m, ",default_permissions");
+ sb_add_option(ctx, "default_permissions", NULL);
if (ofs->config.redirect_mode != ovl_redirect_mode_def())
- seq_printf(m, ",redirect_dir=%s",
- ovl_redirect_mode(&ofs->config));
+ sb_add_option(ctx, "redirect_dir", ovl_redirect_mode(&ofs->config));
if (ofs->config.index != ovl_index_def)
- seq_printf(m, ",index=%s", str_on_off(ofs->config.index));
+ sb_add_option(ctx, "index", str_on_off(ofs->config.index));
if (ofs->config.uuid != ovl_uuid_def())
- seq_printf(m, ",uuid=%s", ovl_uuid_mode(&ofs->config));
+ sb_add_option(ctx, "uuid", ovl_uuid_mode(&ofs->config));
if (ofs->config.nfs_export != ovl_nfs_export_def)
- seq_printf(m, ",nfs_export=%s",
- str_on_off(ofs->config.nfs_export));
+ sb_add_option(ctx, "nfs_export", str_on_off(ofs->config.nfs_export));
if (ofs->config.xino != ovl_xino_def() && !ovl_same_fs(ofs))
- seq_printf(m, ",xino=%s", ovl_xino_mode(&ofs->config));
+ sb_add_option(ctx, "xino", ovl_xino_mode(&ofs->config));
if (ofs->config.metacopy != ovl_metacopy_def)
- seq_printf(m, ",metacopy=%s", str_on_off(ofs->config.metacopy));
+ sb_add_option(ctx, "metacopy", str_on_off(ofs->config.metacopy));
if (ofs->config.fsync_mode != ovl_fsync_mode_def())
- seq_printf(m, ",fsync=%s", ovl_fsync_mode(&ofs->config));
+ sb_add_option(ctx, "fsync", ovl_fsync_mode(&ofs->config));
if (ofs->config.userxattr)
- seq_puts(m, ",userxattr");
+ sb_add_option(ctx, "userxattr", NULL);
if (ofs->config.verity_mode != ovl_verity_mode_def())
- seq_printf(m, ",verity=%s",
- ovl_verity_mode(&ofs->config));
+ sb_add_option(ctx, "verity", ovl_verity_mode(&ofs->config));
return 0;
}
diff --git a/fs/overlayfs/params.h b/fs/overlayfs/params.h
index ffd53cdd8482..2cf9c4f188a9 100644
--- a/fs/overlayfs/params.h
+++ b/fs/overlayfs/params.h
@@ -40,5 +40,5 @@ int ovl_init_fs_context(struct fs_context *fc);
void ovl_free_fs(struct ovl_fs *ofs);
int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
struct ovl_config *config);
-int ovl_show_options(struct seq_file *m, struct dentry *dentry);
+int ovl_get_options(struct sb_opt_ctx *ctx, struct dentry *dentry);
const char *ovl_xino_mode(struct ovl_config *config);
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 60f0b7ceef0a..4b57b2db0852 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -302,7 +302,7 @@ static const struct super_operations ovl_super_operations = {
.put_super = ovl_put_super,
.sync_fs = ovl_sync_fs,
.statfs = ovl_statfs,
- .show_options = ovl_show_options,
+ .get_options = ovl_get_options,
};
#define OVL_WORKDIR_NAME "work"
@@ -529,6 +529,7 @@ static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs,
/* Don't inherit atime flags */
upper_mnt->mnt_flags &= ~(MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME);
+ upper_mnt->mnt_flags |= MNT_CLONABLE;
upper_layer->mnt = upper_mnt;
upper_layer->idx = 0;
upper_layer->fsid = 0;
@@ -1105,7 +1106,7 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
* Make lower layers R/O. That way fchmod/fchown on lower file
* will fail instead of modifying lower fs.
*/
- mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME;
+ mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME | MNT_CLONABLE;
layers[ofs->numlayer].trap = trap;
layers[ofs->numlayer].mnt = mnt;
diff --git a/fs/pidfs.c b/fs/pidfs.c
index aaa609ddab04..3843acd6594d 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -840,7 +840,7 @@ static char *pidfs_dname(struct dentry *dentry, char *buffer, int buflen)
return dynamic_dname(buffer, buflen, "anon_inode:[pidfd]");
}
-const struct dentry_operations pidfs_dentry_operations = {
+static const struct dentry_operations pidfs_dentry_operations = {
.d_dname = pidfs_dname,
.d_prune = stashed_dentry_prune,
};
@@ -1151,6 +1151,8 @@ void __init pidfs_init(void)
if (IS_ERR(pidfs_mnt))
panic("Failed to mount pidfs pseudo filesystem");
+ pidfs_mnt->mnt_flags |= MNT_CLONABLE;
+
pidfs_root_path.mnt = pidfs_mnt;
pidfs_root_path.dentry = pidfs_mnt->mnt_root;
}
diff --git a/fs/proc_namespace.c b/fs/proc_namespace.c
index 5c555db68aa2..94bc4c9aa43f 100644
--- a/fs/proc_namespace.c
+++ b/fs/proc_namespace.c
@@ -98,21 +98,27 @@ static void show_type(struct seq_file *m, struct super_block *sb)
}
}
+static int show_devname(struct seq_file *m, struct vfsmount *mnt)
+{
+ struct super_block *sb = mnt->mnt_sb;
+
+ if (sb->s_op->show_devname)
+ return sb->s_op->show_devname(m, mnt->mnt_root);
+
+ mangle(m, sb->s_devname);
+ return 0;
+}
+
static int show_vfsmnt(struct seq_file *m, struct vfsmount *mnt)
{
struct proc_mounts *p = m->private;
- struct mount *r = real_mount(mnt);
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
struct super_block *sb = mnt_path.dentry->d_sb;
int err;
- if (sb->s_op->show_devname) {
- err = sb->s_op->show_devname(m, mnt_path.dentry);
- if (err)
- goto out;
- } else {
- mangle(m, r->mnt_devname);
- }
+ err = show_devname(m, mnt);
+ if (err)
+ goto out;
seq_putc(m, ' ');
/* mountpoints outside of chroot jail will give SEQ_SKIP on this */
err = seq_path_root(m, &mnt_path, &p->root, " \t\n\\");
@@ -125,8 +131,7 @@ static int show_vfsmnt(struct seq_file *m, struct vfsmount *mnt)
if (err)
goto out;
show_vfsmnt_opts(m, mnt);
- if (sb->s_op->show_options)
- err = sb->s_op->show_options(m, mnt_path.dentry);
+ err = mnt_show_options(m, mnt);
seq_puts(m, " 0 0\n");
out:
return err;
@@ -172,19 +177,14 @@ static int show_mountinfo(struct seq_file *m, struct vfsmount *mnt)
seq_puts(m, " - ");
show_type(m, sb);
seq_putc(m, ' ');
- if (sb->s_op->show_devname) {
- err = sb->s_op->show_devname(m, mnt->mnt_root);
- if (err)
- goto out;
- } else {
- mangle(m, r->mnt_devname);
- }
+ err = show_devname(m, mnt);
+ if (err)
+ goto out;
seq_puts(m, sb_rdonly(sb) ? " ro" : " rw");
err = show_sb_opts(m, sb);
if (err)
goto out;
- if (sb->s_op->show_options)
- err = sb->s_op->show_options(m, mnt->mnt_root);
+ err = mnt_show_options(m, mnt);
seq_putc(m, '\n');
out:
return err;
@@ -193,21 +193,15 @@ static int show_mountinfo(struct seq_file *m, struct vfsmount *mnt)
static int show_vfsstat(struct seq_file *m, struct vfsmount *mnt)
{
struct proc_mounts *p = m->private;
- struct mount *r = real_mount(mnt);
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
struct super_block *sb = mnt_path.dentry->d_sb;
int err;
/* device */
seq_puts(m, "device ");
- if (sb->s_op->show_devname) {
- err = sb->s_op->show_devname(m, mnt_path.dentry);
- if (err)
- goto out;
- } else {
- mangle(m, r->mnt_devname);
- }
-
+ err = show_devname(m, mnt);
+ if (err)
+ goto out;
/* mount point */
seq_puts(m, " mounted on ");
/* mountpoints outside of chroot jail will give SEQ_SKIP on this */
diff --git a/fs/super.c b/fs/super.c
index a8fd61136aaf..e5531cb88f89 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -280,6 +280,7 @@ static void destroy_super_work(struct work_struct *work)
security_sb_free(s);
put_user_ns(s->s_user_ns);
kfree(s->s_subtype);
+ kfree_const(s->s_devname);
for (int i = 0; i < SB_FREEZE_LEVELS; i++)
percpu_free_rwsem(&s->s_writers.rw_sem[i]);
kfree(s);
@@ -315,7 +316,7 @@ static void destroy_unused_super(struct super_block *s)
* returns a pointer new superblock or %NULL if allocation had failed.
*/
static struct super_block *alloc_super(struct file_system_type *type, int flags,
- struct user_namespace *user_ns)
+ struct user_namespace *user_ns, const char *name)
{
struct super_block *s = kzalloc_obj(struct super_block);
static const struct super_operations default_op;
@@ -344,6 +345,13 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
*/
down_write_nested(&s->s_umount, SINGLE_DEPTH_NESTING);
+ if (name)
+ s->s_devname = kstrdup_const(name, GFP_KERNEL_ACCOUNT);
+ else
+ s->s_devname = "none";
+ if (!s->s_devname)
+ goto fail;
+
if (security_sb_alloc(s))
goto fail;
@@ -763,7 +771,7 @@ struct super_block *sget_fc(struct fs_context *fc,
}
if (!s) {
spin_unlock(&sb_lock);
- s = alloc_super(fc->fs_type, fc->sb_flags, user_ns);
+ s = alloc_super(fc->fs_type, fc->sb_flags, user_ns, fc->source);
if (!s)
return ERR_PTR(-ENOMEM);
goto retry;
diff --git a/include/linux/fcntl.h b/include/linux/fcntl.h
index 6ad6b9e7a226..f942411c8ed7 100644
--- a/include/linux/fcntl.h
+++ b/include/linux/fcntl.h
@@ -11,7 +11,7 @@
(O_RDONLY | O_WRONLY | O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | \
O_APPEND | O_NDELAY | O_NONBLOCK | __O_SYNC | O_DSYNC | \
FASYNC | O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | \
- O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE | O_EMPTYPATH)
+ O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE | O_EMPTYPATH | O_ALT)
/* List of all valid flags for openat2(2)'s how->flags argument. */
#define VALID_OPENAT2_FLAGS (VALID_OPEN_FLAGS | OPENAT2_REGULAR)
diff --git a/include/linux/fs/super.h b/include/linux/fs/super.h
index 405612678115..6f0d778940f9 100644
--- a/include/linux/fs/super.h
+++ b/include/linux/fs/super.h
@@ -237,4 +237,14 @@ int thaw_super(struct super_block *super, enum freeze_holder who,
int sb_init_dio_done_wq(struct super_block *sb);
+struct sb_opt_ctx {
+ void (*actor)(struct sb_opt_ctx *ctx, const char *name, const char *value,
+ const struct path *path);
+ void *data;
+};
+
+void sb_add_option(struct sb_opt_ctx *ctx, const char *name, const char *value);
+void sb_add_option_path(struct sb_opt_ctx *ctx, const char *name, const char *value,
+ const struct path *path);
+
#endif /* _LINUX_FS_SUPER_H */
diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
index ef7941e9dc79..f205759af1a6 100644
--- a/include/linux/fs/super_types.h
+++ b/include/linux/fs/super_types.h
@@ -36,6 +36,7 @@ struct workqueue_struct;
struct writeback_control;
struct xattr_handler;
struct fserror_event;
+struct sb_opt_ctx;
extern struct super_block *blockdev_superblock;
@@ -99,6 +100,7 @@ struct super_operations {
int (*statfs)(struct dentry *dentry, struct kstatfs *kstatfs);
void (*umount_begin)(struct super_block *sb);
+ int (*get_options)(struct sb_opt_ctx *ctx, struct dentry *dentry);
int (*show_options)(struct seq_file *seq, struct dentry *dentry);
int (*show_devname)(struct seq_file *seq, struct dentry *dentry);
int (*show_path)(struct seq_file *seq, struct dentry *dentry);
@@ -283,6 +285,8 @@ struct super_block {
*/
atomic_t s_isw_nr_in_flight;
#endif
+ /* Name of device e.g. /dev/dsk/hda1 */
+ const char *s_devname;
} __randomize_layout;
/*
diff --git a/include/linux/mount.h b/include/linux/mount.h
index acfe7ef86a1b..d071855ca4ed 100644
--- a/include/linux/mount.h
+++ b/include/linux/mount.h
@@ -44,6 +44,7 @@ enum mount_flags {
MNT_LOCKED = 0x800000,
MNT_DOOMED = 0x1000000,
MNT_SYNC_UMOUNT = 0x2000000,
+ MNT_CLONABLE = 0x4000000,
MNT_UMOUNT = 0x8000000,
MNT_USER_SETTABLE_MASK = MNT_NOSUID | MNT_NODEV | MNT_NOEXEC
@@ -52,7 +53,7 @@ enum mount_flags {
MNT_ATIME_MASK = MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME,
MNT_INTERNAL_FLAGS = MNT_INTERNAL | MNT_DOOMED |
- MNT_SYNC_UMOUNT | MNT_LOCKED
+ MNT_SYNC_UMOUNT | MNT_LOCKED | MNT_CLONABLE
};
struct vfsmount {
diff --git a/include/linux/namei.h b/include/linux/namei.h
index ebe6e29f7e93..c21dc6536900 100644
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -25,7 +25,8 @@ enum { MAX_NESTED_LINKS = 8 };
#define LOOKUP_RCU BIT(8) /* RCU pathwalk mode; semi-internal */
#define LOOKUP_CACHED BIT(9) /* Only do cached lookup */
#define LOOKUP_PARENT BIT(10) /* Looking up final parent in path */
-/* 5 spare bits for pathwalk */
+#define LOOKUP_ALT BIT(11) /* Look up in alternative namespace */
+/* 4 spare bits for pathwalk */
/* These tell filesystem methods that we are dealing with the final component... */
#define LOOKUP_OPEN BIT(16) /* ... in open */
diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h
index 0abf7da9ab23..fab6f48ff203 100644
--- a/include/linux/pidfs.h
+++ b/include/linux/pidfs.h
@@ -15,7 +15,6 @@ void pidfs_exit(struct task_struct *tsk);
#ifdef CONFIG_COREDUMP
void pidfs_coredump(const struct coredump_params *cprm);
#endif
-extern const struct dentry_operations pidfs_dentry_operations;
int pidfs_register_pid_gfp(struct pid *pid, gfp_t gfp);
/**
diff --git a/include/uapi/asm-generic/fcntl.h b/include/uapi/asm-generic/fcntl.h
index 883cfd7de2ef..70dbbac1c520 100644
--- a/include/uapi/asm-generic/fcntl.h
+++ b/include/uapi/asm-generic/fcntl.h
@@ -96,6 +96,10 @@
#define O_EMPTYPATH (1 << 26) /* allow empty path */
#endif
+#ifndef O_ALT
+#define O_ALT (1 << 27) /* Open in alternative namespace */
+#endif
+
/* a horrid kludge trying to make sure that this will fail on old kernels */
#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h
index 2204708dbf7a..6e794d214cf9 100644
--- a/include/uapi/linux/mount.h
+++ b/include/uapi/linux/mount.h
@@ -63,6 +63,7 @@
*/
#define OPEN_TREE_CLONE (1 << 0) /* Clone the target tree and attach the clone */
#define OPEN_TREE_NAMESPACE (1 << 1) /* Clone the target tree into a new mount namespace */
+#define OPEN_TREE_ALT (1 << 2) /* Lookup in alternative namespace */
#define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */
/*
diff --git a/scripts/gdb/linux/proc.py b/scripts/gdb/linux/proc.py
index 65dd1bd12964..5aaf2dd8db05 100644
--- a/scripts/gdb/linux/proc.py
+++ b/scripts/gdb/linux/proc.py
@@ -174,9 +174,6 @@ values of that process namespace"""
"mount", "super_block", "devname", "pathname", "fstype"))
for mnt in rbtree.rb_inorder_for_each_entry(namespace['mounts'], mount_ptr_type, "mnt_node"):
- devname = mnt['mnt_devname'].string()
- devname = devname if devname else "none"
-
pathname = ""
parent = mnt
while True:
@@ -190,6 +187,8 @@ values of that process namespace"""
pathname = "/"
superblock = mnt['mnt']['mnt_sb']
+ devname = superblock['s_devname'].string()
+ devname = devname if devname else "none"
fstype = superblock['s_type']['name'].string()
s_flags = int(superblock['s_flags'])
m_flags = int(mnt['mnt']['mnt_flags'])
--
2.54.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT
2026-07-15 10:11 [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT Miklos Szeredi
@ 2026-07-15 16:43 ` Giuseppe Scrivano
0 siblings, 0 replies; 2+ messages in thread
From: Giuseppe Scrivano @ 2026-07-15 16:43 UTC (permalink / raw)
To: Miklos Szeredi
Cc: linux-fsdevel, linux-unionfs, linux-api, linux-erofs,
Amir Goldstein, Gao Xiang
Miklos Szeredi <mszeredi@redhat.com> writes:
> This is a prototype patch. Needs to be split up!
>
> 1) O_ALT / OPEN_TREE_ALT
>
> These open an alternative namespace rooted at dfd - instead of resolving
> the path in the real filesystem, it resolves it in a virtual tree that
> exposes metadata about that file.
>
> This is intended to provide an alternative to adding new ioctls:
>
> - provides structured namespace
> - allows accessing data through fs ops
>
> Could call this O_META, but O_ALT seems more generic and there could be
> uses beyond metadata (e.g. seekable, in-place decompression).
>
> This has been discussed previously:
> https://lore.kernel.org/all/CAHk-=wjzLmMRf=QG-n+1HnxWCx4KTQn9+OhVvUSJ=ZCQd6Y1WA@mail.gmail.com/
>
> 2/a) s_op.get_options(), sb_add_option()
>
> .get_options() is a generic version of .show_options().
>
> After converting everyting to seq_show_option() the rest is trivial:
>
> - change first arg to "struct sb_opt_ctx *ctx"
> - replace "seq_show_option(m" with "sb_add_option(ctx"
>
> 2/b) sb_add_option_path()
>
> The last arg is "struct path *". This is for options that designate a
> path (e.g. stacked fs layers).
>
> Will allow opening the path through O_ALT. Ignored when retrieving the
> string value of the option.
>
> 3) MNT_CLONABLE
>
> Allow an internal mount to be cloned. Nsfs and pidfs are already special
> cased, move these over to this flag.
>
> Also allow overlayfs layers to be cloned.
>
> 4) mnt.mnt_devname -> sb.s_devname
>
> This is historical thing, mnt_devname was just copied when cloning the
> mount, sharing the same value for all mounts belonging to a super block.
>
> 5) mount options in metafs
>
> Support retrieving mount options through O_ALT opens:
>
> mount/options/OPT/N: Nth instance of OPT
>
> This will be a symlink pointint to the option value. If the option refers
> to a path (as per 2/b), following the symlink jumps to the given path.
>
> At this point only overlayfs and erofs are converted.
>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
> Documentation/filesystems/vfs.rst | 2 +-
> fs/Makefile | 2 +
> fs/erofs/super.c | 29 +-
> fs/fcntl.c | 2 +-
> fs/internal.h | 1 +
> fs/metafs/Makefile | 6 +
> fs/metafs/metafs.c | 572 ++++++++++++++++++++++++++++++
> fs/metafs/metafs.h | 15 +
> fs/mount.h | 1 -
> fs/namei.c | 16 +
> fs/namespace.c | 84 +++--
> fs/nsfs.c | 1 +
> fs/open.c | 5 +-
> fs/overlayfs/params.c | 43 ++-
> fs/overlayfs/params.h | 2 +-
> fs/overlayfs/super.c | 5 +-
> fs/pidfs.c | 4 +-
> fs/proc_namespace.c | 50 ++-
> fs/super.c | 12 +-
> include/linux/fcntl.h | 2 +-
> include/linux/fs/super.h | 10 +
> include/linux/fs/super_types.h | 4 +
> include/linux/mount.h | 3 +-
> include/linux/namei.h | 3 +-
> include/linux/pidfs.h | 1 -
> include/uapi/asm-generic/fcntl.h | 4 +
> include/uapi/linux/mount.h | 1 +
> scripts/gdb/linux/proc.py | 5 +-
> 28 files changed, 774 insertions(+), 111 deletions(-)
> create mode 100644 fs/metafs/Makefile
> create mode 100644 fs/metafs/metafs.c
> create mode 100644 fs/metafs/metafs.h
>
> diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
> index 7c753148af88..3dc431b6cdbb 100644
> --- a/Documentation/filesystems/vfs.rst
> +++ b/Documentation/filesystems/vfs.rst
> @@ -314,7 +314,7 @@ or bottom half).
> ``show_devname``
> Optional. Called by the VFS to show device name for
> /proc/<pid>/{mounts,mountinfo,mountstats}. If not provided then
> - '(struct mount).mnt_devname' will be used.
> + '(struct super_block).s_devname' will be used.
>
> ``show_path``
> Optional. Called by the VFS (for /proc/<pid>/mountinfo) to show
> diff --git a/fs/Makefile b/fs/Makefile
> index 89a8a9d207d1..87a4c23d5a08 100644
> --- a/fs/Makefile
> +++ b/fs/Makefile
> @@ -58,6 +58,8 @@ obj-$(CONFIG_CONFIGFS_FS) += configfs/
> obj-y += devpts/
>
> obj-$(CONFIG_DLM) += dlm/
> +
> +obj-y += metafs/
>
> # Do not add any filesystems before this line
> obj-$(CONFIG_NETFS_SUPPORT) += netfs/
> diff --git a/fs/erofs/super.c b/fs/erofs/super.c
> index 86fa5c6a0c70..257517c28e1d 100644
> --- a/fs/erofs/super.c
> +++ b/fs/erofs/super.c
> @@ -1028,29 +1028,34 @@ static int erofs_statfs(struct dentry *dentry, struct kstatfs *buf)
> return 0;
> }
>
> -static int erofs_show_options(struct seq_file *seq, struct dentry *root)
> +static int erofs_get_options(struct sb_opt_ctx *ctx, struct dentry *root)
> {
> struct erofs_sb_info *sbi = EROFS_SB(root->d_sb);
> struct erofs_mount_opts *opt = &sbi->opt;
>
> + if (sbi->dif0.file)
> + sb_add_option_path(ctx, "source", NULL, &sbi->dif0.file->f_path);
> +
> if (IS_ENABLED(CONFIG_EROFS_FS_XATTR))
> - seq_puts(seq, test_opt(opt, XATTR_USER) ?
> - ",user_xattr" : ",nouser_xattr");
> + sb_add_option(ctx, test_opt(opt, XATTR_USER) ?
> + "user_xattr" : "nouser_xattr", NULL);
> if (IS_ENABLED(CONFIG_EROFS_FS_POSIX_ACL))
> - seq_puts(seq, test_opt(opt, POSIX_ACL) ? ",acl" : ",noacl");
> + sb_add_option(ctx, test_opt(opt, POSIX_ACL) ? "acl" : "noacl", NULL);
> if (IS_ENABLED(CONFIG_EROFS_FS_ZIP))
> - seq_printf(seq, ",cache_strategy=%s",
> + sb_add_option(ctx, "cache_strategy",
> erofs_param_cache_strategy[opt->cache_strategy].name);
> if (test_opt(opt, DAX_ALWAYS))
> - seq_puts(seq, ",dax=always");
> + sb_add_option(ctx, "dax", "always");
> if (test_opt(opt, DAX_NEVER))
> - seq_puts(seq, ",dax=never");
> + sb_add_option(ctx, "dax", "never");
> if (erofs_is_fileio_mode(sbi) && test_opt(opt, DIRECT_IO))
> - seq_puts(seq, ",directio");
> - if (sbi->dif0.fsoff)
> - seq_printf(seq, ",fsoffset=%llu", sbi->dif0.fsoff);
> + sb_add_option(ctx, "directio", NULL);
> + if (sbi->dif0.fsoff) {
> + char *val __free(kfree) = kasprintf(GFP_KERNEL, "%llu", sbi->dif0.fsoff);
> + sb_add_option(ctx, "fsoffset", val);
> + }
> if (test_opt(opt, INODE_SHARE))
> - seq_puts(seq, ",inode_share");
> + sb_add_option(ctx, "inode_share", NULL);
> return 0;
> }
>
> @@ -1069,7 +1074,7 @@ const struct super_operations erofs_sops = {
> .free_inode = erofs_free_inode,
> .evict_inode = erofs_evict_inode,
> .statfs = erofs_statfs,
> - .show_options = erofs_show_options,
> + .get_options = erofs_get_options,
> };
>
> module_init(erofs_module_init);
> diff --git a/fs/fcntl.c b/fs/fcntl.c
> index c158f082f1da..01b765cfac84 100644
> --- a/fs/fcntl.c
> +++ b/fs/fcntl.c
> @@ -1169,7 +1169,7 @@ static int __init fcntl_init(void)
> * Exceptions: O_NONBLOCK is a two bit define on parisc; O_NDELAY
> * is defined as O_NONBLOCK on some platforms and not on others.
> */
> - BUILD_BUG_ON(22 - 1 /* for O_RDONLY being 0 */ !=
> + BUILD_BUG_ON(23 - 1 /* for O_RDONLY being 0 */ !=
> HWEIGHT32(
> (VALID_OPEN_FLAGS & ~(O_NONBLOCK | O_NDELAY)) |
> __FMODE_EXEC | __O_REGULAR));
> diff --git a/fs/internal.h b/fs/internal.h
> index 355d93f92208..e73a5c1d5b4e 100644
> --- a/fs/internal.h
> +++ b/fs/internal.h
> @@ -95,6 +95,7 @@ int path_umount(const struct path *path, int flags);
> int path_pivot_root(struct path *new, struct path *old);
>
> int show_path(struct seq_file *m, struct dentry *root);
> +int mnt_show_options(struct seq_file *seq, struct vfsmount *mnt);
>
> /*
> * fs_struct.c
> diff --git a/fs/metafs/Makefile b/fs/metafs/Makefile
> new file mode 100644
> index 000000000000..f855b48d9c2b
> --- /dev/null
> +++ b/fs/metafs/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Makefile for meta filesystem
> +#
> +
> +obj-y := metafs.o
> diff --git a/fs/metafs/metafs.c b/fs/metafs/metafs.c
> new file mode 100644
> index 000000000000..15ec0b6064da
> --- /dev/null
> +++ b/fs/metafs/metafs.c
> @@ -0,0 +1,572 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * metafs - virtual filesystem for metadata access
> + *
> + * This provides a way to access metadata about files and filesystems
> + * through O_ALT file descriptors.
> + */
> +
> +#include <linux/fs.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/magic.h>
> +#include <linux/mount.h>
> +#include <linux/namei.h>
> +#include <linux/slab.h>
> +#include <linux/dcache.h>
> +#include <linux/pseudo_fs.h>
> +#include <linux/fs/super.h>
> +#include "metafs.h"
> +
> +/*
> + * metafs superblock and mount
> + */
> +static struct vfsmount *metafs_mnt __ro_after_init;
> +
> +static const struct super_operations metafs_ops = {
> + .statfs = simple_statfs,
> + .drop_inode = inode_just_drop,
> +};
> +
> +static int metafs_init_fs_context(struct fs_context *fc)
> +{
> + struct pseudo_fs_context *ctx = init_pseudo(fc, 0x4d455441); /* 'META' */
> + if (!ctx)
> + return -ENOMEM;
> + ctx->ops = &metafs_ops;
> + return 0;
> +}
> +
> +static struct file_system_type metafs_type = {
> + .name = "metafs",
> + .init_fs_context = metafs_init_fs_context,
> + .kill_sb = kill_anon_super,
> +};
> +
> +/*
> + * Mount option collection structures
> + */
> +enum metafs_dentry_type {
> + METAFS_ROOT,
> + METAFS_OPTION_VALUES,
> + METAFS_SYMLINK,
> +};
> +
> +struct metafs_option_entry {
> + char *value;
> + struct path path;
> +};
> +
> +struct metafs_dentry_info {
> + enum metafs_dentry_type type;
> + union {
> + struct {
> + struct file *base;
> + } root;
> + struct {
> + struct metafs_option_entry *entries;
> + int count;
> + } opt_values;
> + struct metafs_option_entry symlink;
> + };
> +};
> +
> +struct metafs_collect_ctx {
> + struct sb_opt_ctx ctx;
> + const char *target_name;
> + struct super_block *sb;
> + struct metafs_option_entry *entries;
> + int count;
> + int size;
> + int err;
> + bool is_source;
> +};
> +
> +/*
> + * Collect specific mount option values from the base filesystem
> + */
> +static void metafs_collect_option(struct sb_opt_ctx *ctx, const char *name,
> + const char *value, const struct path *path)
> +{
> + struct metafs_collect_ctx *collect_ctx = ctx->data;
> + struct metafs_option_entry *entry;
> +
> + if (strcmp(name, collect_ctx->target_name) != 0)
> + return;
> +
> + if (!value && collect_ctx->is_source)
> + value = collect_ctx->sb->s_devname;
> +
> + if (collect_ctx->count >= collect_ctx->size) {
> + int new_size = collect_ctx->size ? collect_ctx->size * 2 : 4;
> + struct metafs_option_entry *new_entries;
> +
> + new_entries = krealloc_array(collect_ctx->entries, new_size, sizeof(*new_entries),
> + GFP_KERNEL);
> + if (!new_entries) {
> + collect_ctx->err = -ENOMEM;
> + return;
> + }
> + collect_ctx->entries = new_entries;
> + collect_ctx->size = new_size;
> + }
> +
> + entry = &collect_ctx->entries[collect_ctx->count];
> + entry->value = value ? kstrdup(value, GFP_KERNEL) : NULL;
> + if (!entry->value && value) {
> + collect_ctx->err = -ENOMEM;
> + return;
> + }
> + if (path) {
> + entry->path = *path;
> + path_get(&entry->path);
> + } else {
> + memset(&entry->path, 0, sizeof(entry->path));
> + }
> + collect_ctx->count++;
> +}
> +
> +static void metafs_free_entries(struct metafs_option_entry *entries, int count)
> +{
> + int i;
> +
> + for (i = 0; i < count; i++) {
> + kfree(entries[i].value);
> + if (entries[i].path.mnt)
> + path_put(&entries[i].path);
> + }
> + kfree(entries);
> +}
> +
> +static int metafs_get_option_values(struct file *base, const char *option_name,
> + struct metafs_option_entry **entries_out,
> + int *count_out)
> +{
> + const struct path *path = &base->f_path;
> + struct super_block *sb = path->mnt->mnt_sb;
> + struct metafs_collect_ctx collect_ctx = {
> + .ctx.data = &collect_ctx,
> + .ctx.actor = metafs_collect_option,
> + .target_name = option_name,
> + .sb = sb,
> + .is_source = !strcmp(option_name, "source"),
> + };
> + int ret;
> +
> + if (sb->s_op->get_options) {
> + ret = sb->s_op->get_options(&collect_ctx.ctx, path->mnt->mnt_root);
> + if (ret || collect_ctx.err) {
> + metafs_free_entries(collect_ctx.entries, collect_ctx.count);
> + return ret ?: collect_ctx.err;
> + }
> + }
> +
> + /* special case: add "source" option if filesystem didn't add one*/
> + if (!collect_ctx.count && collect_ctx.is_source) {
> + struct metafs_option_entry *ent __free(kfree) = kzalloc_obj(*ent);
> +
> + if (!ent)
> + return -ENOMEM;
> +
> + ent->value = kstrdup(sb->s_devname, GFP_KERNEL);
> + if (!ent->value)
> + return -ENOMEM;
> +
> + collect_ctx.entries = no_free_ptr(ent);
> + collect_ctx.count = 1;
> + }
> +
> + *entries_out = collect_ctx.entries;
> + *count_out = collect_ctx.count;
> + return 0;
> +}
> +
> +/*
> + * metafs symlink operations
> + */
> +static const char *metafs_get_link(struct dentry *dentry, struct inode *inode,
> + struct delayed_call *done)
> +{
> + struct metafs_dentry_info *info;
> +
> + if (!dentry)
> + return ERR_PTR(-ECHILD);
> +
> + info = dentry->d_fsdata;
> + if (info->symlink.path.mnt) {
> + int error;
> +
> + path_get(&info->symlink.path);
> + error = nd_jump_link(&info->symlink.path);
> + return ERR_PTR(error);
> + }
> +
> + return ERR_PTR(-ELOOP);
> +}
> +
> +static int metafs_readlink(struct dentry *dentry, char __user *buffer,
> + int buflen)
> +{
> + struct metafs_dentry_info *info = dentry->d_fsdata;
> + const char *value = info->symlink.value;
> +
> + if (!value)
> + value = "";
> +
> + return readlink_copy(buffer, buflen, value, strlen(value));
> +}
> +
> +static const struct inode_operations metafs_symlink_iops = {
> + .get_link = metafs_get_link,
> + .readlink = metafs_readlink,
> +};
> +
> +/*
> + * Dentry operations - cleanup
> + */
> +static void metafs_d_release(struct dentry *dentry)
> +{
> + struct metafs_dentry_info *info = dentry->d_fsdata;
> +
> + if (!info)
> + return;
> +
> + switch (info->type) {
> + case METAFS_ROOT:
> + fput(info->root.base);
> + break;
> + case METAFS_OPTION_VALUES:
> + metafs_free_entries(info->opt_values.entries,
> + info->opt_values.count);
> + break;
> + case METAFS_SYMLINK:
> + kfree(info->symlink.value);
> + if (info->symlink.path.mnt)
> + path_put(&info->symlink.path);
> + break;
> + default:
> + WARN_ON(1);
> + }
> + kfree(info);
> +}
> +
> +static const struct dentry_operations metafs_dentry_ops = {
> + .d_release = metafs_d_release,
> +};
> +
> +/*
> + * metafs directory operations - forward declarations
> + */
> +static struct file *metafs_get_base_file(struct dentry *dentry);
> +static struct dentry *metafs_root_lookup(struct inode *dir, struct dentry *dentry,
> + unsigned int flags);
> +static struct dentry *metafs_mount_lookup(struct inode *dir, struct dentry *dentry,
> + unsigned int flags);
> +static struct dentry *metafs_options_lookup(struct inode *dir, struct dentry *dentry,
> + unsigned int flags);
> +static struct dentry *metafs_option_values_lookup(struct inode *dir, struct dentry *dentry,
> + unsigned int flags);
> +
> +static const struct inode_operations metafs_root_dir_iops = {
> + .lookup = metafs_root_lookup,
> +};
> +
> +static const struct inode_operations metafs_mount_dir_iops = {
> + .lookup = metafs_mount_lookup,
> +};
> +
> +static const struct inode_operations metafs_options_dir_iops = {
> + .lookup = metafs_options_lookup,
> +};
> +
> +static const struct inode_operations metafs_option_values_dir_iops = {
> + .lookup = metafs_option_values_lookup,
> +};
> +
> +/*
> + * Create a new inode in metafs
> + */
> +static struct inode *metafs_get_inode(struct super_block *sb, umode_t mode,
> + const struct inode_operations *iops)
> +{
> + struct inode *inode = new_inode(sb);
> +
> + if (!inode)
> + return NULL;
> +
> + inode->i_ino = get_next_ino();
> + inode->i_mode = mode;
> + simple_inode_init_ts(inode);
> +
> + if (S_ISDIR(mode)) {
> + inode->i_op = iops;
> + inc_nlink(inode);
> + } else if (S_ISLNK(mode)) {
> + inode->i_op = &metafs_symlink_iops;
> + }
> +
> + return inode;
> +}
> +
> +/*
> + * Lookup in root directory (/)
> + * Only "mount" is valid here
> + */
> +static struct dentry *metafs_root_lookup(struct inode *dir, struct dentry *dentry,
> + unsigned int flags)
> +{
> + const char *name = dentry->d_name.name;
> + struct inode *inode;
> +
> + if (strcmp(name, "mount") != 0)
> + return ERR_PTR(-ENOENT);
> +
> + inode = metafs_get_inode(dir->i_sb, S_IFDIR | 0555, &metafs_mount_dir_iops);
> + if (!inode)
> + return ERR_PTR(-ENOMEM);
> +
> + d_add(dentry, inode);
> + d_mark_dontcache(inode);
> + return NULL;
> +}
> +
> +/*
> + * Lookup in /mount directory
> + * Only "options" is valid here
> + */
> +static struct dentry *metafs_mount_lookup(struct inode *dir, struct dentry *dentry,
> + unsigned int flags)
> +{
> + const char *name = dentry->d_name.name;
> + struct inode *inode;
> +
> + if (strcmp(name, "options") != 0)
> + return ERR_PTR(-ENOENT);
> +
> + inode = metafs_get_inode(dir->i_sb, S_IFDIR | 0555, &metafs_options_dir_iops);
> + if (!inode)
> + return ERR_PTR(-ENOMEM);
> +
> + d_add(dentry, inode);
> + d_mark_dontcache(inode);
> + return NULL;
> +}
> +
> +/*
> + * Lookup in /mount/options directory
> + * Collect values for the requested option name
> + */
> +static struct dentry *metafs_options_lookup(struct inode *dir, struct dentry *dentry,
> + unsigned int flags)
> +{
> + const char *target_name = dentry->d_name.name;
> + struct file *base;
> + struct metafs_dentry_info *info;
> + struct metafs_option_entry *entries = NULL;
> + int count = 0;
> + struct inode *inode;
> + int ret;
> +
> + base = metafs_get_base_file(dentry->d_parent);
> + if (!base)
> + return ERR_PTR(-ENOENT);
> +
> + ret = metafs_get_option_values(base, target_name, &entries, &count);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + if (count == 0) {
> + kfree(entries);
> + return ERR_PTR(-ENOENT);
> + }
> +
> + info = kzalloc(sizeof(*info), GFP_KERNEL);
> + if (!info) {
> + metafs_free_entries(entries, count);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + info->type = METAFS_OPTION_VALUES;
> + info->opt_values.entries = entries;
> + info->opt_values.count = count;
> +
> + inode = metafs_get_inode(dir->i_sb, S_IFDIR | 0555, &metafs_option_values_dir_iops);
> + if (!inode) {
> + metafs_free_entries(entries, count);
> + kfree(info);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + dentry->d_fsdata = info;
> + d_add(dentry, inode);
> + d_mark_dontcache(inode);
> + return NULL;
> +}
> +
> +/*
> + * Lookup in option-specific directory (e.g., /mount/options/lowerdir/)
> + * Return numbered entries (0, 1, 2, ...) as symlinks
> + */
> +static struct dentry *metafs_option_values_lookup(struct inode *dir,
> + struct dentry *dentry,
> + unsigned int flags)
> +{
> + const char *target_name = dentry->d_name.name;
> + struct metafs_dentry_info *parent_info, *info;
> + struct metafs_option_entry *entry;
> + struct inode *inode;
> + unsigned long index;
> + int err;
> +
> + parent_info = dentry->d_parent->d_fsdata;
> + if (WARN_ON(!parent_info || parent_info->type != METAFS_OPTION_VALUES))
> + return ERR_PTR(-ENOENT);
> +
> + if (target_name[0] == '0' && target_name[1])
> + return ERR_PTR(-ENOENT);
> +
> + err = kstrtoul(target_name, 10, &index);
> + if (err || index >= parent_info->opt_values.count)
> + return ERR_PTR(-ENOENT);
> +
> + entry = &parent_info->opt_values.entries[index];
> +
> + info = kzalloc(sizeof(*info), GFP_KERNEL);
> + if (!info)
> + return ERR_PTR(-ENOMEM);
> +
> + info->type = METAFS_SYMLINK;
> + if (entry->value) {
> + info->symlink.value = kstrdup(entry->value, GFP_KERNEL);
> + if (!info->symlink.value) {
> + kfree(info);
> + return ERR_PTR(-ENOMEM);
> + }
> + }
> + if (entry->path.mnt) {
> + info->symlink.path = entry->path;
> + path_get(&info->symlink.path);
> + }
> +
> + inode = metafs_get_inode(dir->i_sb, S_IFLNK | 0777, NULL);
> + if (!inode) {
> + kfree(info->symlink.value);
> + if (info->symlink.path.mnt)
> + path_put(&info->symlink.path);
> + kfree(info);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + dentry->d_fsdata = info;
> + d_add(dentry, inode);
> + d_mark_dontcache(inode);
> + return NULL;
> +}
> +
> +/*
> + * Helper to get base file from any metafs dentry
> + */
> +static struct file *metafs_get_base_file(struct dentry *dentry)
> +{
> + while (dentry) {
> + struct metafs_dentry_info *info = dentry->d_fsdata;
> +
> + if (info && info->type == METAFS_ROOT)
> + return info->root.base;
> + if (dentry == dentry->d_parent)
> + break;
> + dentry = dentry->d_parent;
> + }
> + return NULL;
> +}
> +
> +/*
> + * Create the root directory structure for a metadata open
> + */
> +static struct dentry *metafs_create_root(struct file *base)
> +{
> + struct inode *inode;
> + struct dentry *root;
> + struct super_block *sb = metafs_mnt->mnt_sb;
> + struct metafs_dentry_info *info;
> +
> + info = kzalloc(sizeof(*info), GFP_KERNEL);
> + if (!info)
> + return ERR_PTR(-ENOMEM);
> +
> + info->type = METAFS_ROOT;
> + info->root.base = get_file(base);
> +
> + inode = metafs_get_inode(sb, S_IFDIR | 0555, &metafs_root_dir_iops);
> + if (!inode) {
> + fput(info->root.base);
> + kfree(info);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + root = d_alloc_anon(sb);
> + if (!root) {
> + iput(inode);
> + fput(info->root.base);
> + kfree(info);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + /* Store the info in d_fsdata before instantiating */
> + root->d_fsdata = info;
> + d_instantiate(root, inode);
> + d_mark_dontcache(inode);
> +
> + return root;
> +}
> +
> +/*
> + * Get metadata root path for a base file.
> + *
> + * This is called from path_init() when O_ALT is used.
> + * It returns a path in the metafs that serves as the root
> + * for the metadata lookup.
> + */
> +int metafs_get_root(struct file *base, struct path *path)
> +{
> + struct dentry *root;
> +
> + if (!base)
> + return -EBADF;
> +
> + /* Create the metadata root for this base file */
> + root = metafs_create_root(base);
> + if (IS_ERR(root))
> + return PTR_ERR(root);
> +
> + /*
> + * Return the path to the metadata root.
> + * Normal VFS lookup will take over from here.
> + */
> + path->mnt = mntget(metafs_mnt);
> + path->dentry = root;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(metafs_get_root);
> +
> +static int __init metafs_init(void)
> +{
> + int err;
> +
> + err = register_filesystem(&metafs_type);
> + if (err)
> + return err;
> +
> + metafs_mnt = kern_mount(&metafs_type);
> + if (IS_ERR(metafs_mnt)) {
> + err = PTR_ERR(metafs_mnt);
> + unregister_filesystem(&metafs_type);
> + return err;
> + }
> + set_default_d_op(metafs_mnt->mnt_sb, &metafs_dentry_ops);
> +
> + return 0;
> +}
> +
> +fs_initcall(metafs_init);
> diff --git a/fs/metafs/metafs.h b/fs/metafs/metafs.h
> new file mode 100644
> index 000000000000..d257714260a7
> --- /dev/null
> +++ b/fs/metafs/metafs.h
> @@ -0,0 +1,15 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _LINUX_METAFS_H
> +#define _LINUX_METAFS_H
> +
> +struct path;
> +struct file;
> +
> +/*
> + * Get metadata root path for a base file.
> + * Called from path_init() when O_ALT flag is used.
> + * Returns 0 on success with path filled in, -errno on error.
> + */
> +int metafs_get_root(struct file *base, struct path *path);
> +
> +#endif /* _LINUX_METAFS_H */
> diff --git a/fs/mount.h b/fs/mount.h
> index 94fcc306d21e..69a953bc112b 100644
> --- a/fs/mount.h
> +++ b/fs/mount.h
> @@ -65,7 +65,6 @@ struct mount {
> struct mount * __aligned(1) *mnt_pprev_for_sb;
> /* except that LSB of pprev is stolen */
> #define WRITE_HOLD 1 /* ... for use by mnt_hold_writers() */
> - const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
> struct list_head mnt_list;
> struct list_head mnt_expire; /* link in fs-specific expiry list */
> struct list_head mnt_share; /* circular list of shared mounts */
> diff --git a/fs/namei.c b/fs/namei.c
> index 19ce43c9a6e6..437a14c432e6 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -45,6 +45,7 @@
>
> #include "internal.h"
> #include "mount.h"
> +#include "metafs/metafs.h"
>
> /* [Feb-1997 T. Schoebel-Theuer]
> * Fundamental changes in the pathname lookup mechanisms (namei)
> @@ -2753,6 +2754,21 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
> return ERR_PTR(-ENOENT);
> }
>
> + if (unlikely(flags & LOOKUP_ALT)) {
> + if (flags & LOOKUP_RCU)
> + return ERR_PTR(-ECHILD);
> +
> + /* Handle O_ALT: jump to metafs root */
> + error = metafs_get_root(fd_file(f), &nd->path);
> + if (error)
> + return ERR_PTR(error);
> +
> + nd->inode = nd->path.dentry->d_inode;
> + nd->state |= ND_JUMPED;
> +
> + return s;
> + }
> +
> dentry = fd_file(f)->f_path.dentry;
>
> if (*s && unlikely(!d_can_lookup(dentry)))
> diff --git a/fs/namespace.c b/fs/namespace.c
> index 3d5cd5bf3b05..62cc71c401a2 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -282,7 +282,7 @@ int mnt_get_count(struct mount *mnt)
> #endif
> }
>
> -static struct mount *alloc_vfsmnt(const char *name)
> +static struct mount *alloc_vfsmnt(void)
> {
> struct mount *mnt = kmem_cache_zalloc(mnt_cache, GFP_KERNEL);
> if (mnt) {
> @@ -292,18 +292,10 @@ static struct mount *alloc_vfsmnt(const char *name)
> if (err)
> goto out_free_cache;
>
> - if (name)
> - mnt->mnt_devname = kstrdup_const(name,
> - GFP_KERNEL_ACCOUNT);
> - else
> - mnt->mnt_devname = "none";
> - if (!mnt->mnt_devname)
> - goto out_free_id;
> -
> #ifdef CONFIG_SMP
> mnt->mnt_pcp = alloc_percpu(struct mnt_pcp);
> if (!mnt->mnt_pcp)
> - goto out_free_devname;
> + goto out_free_id;
>
> this_cpu_add(mnt->mnt_pcp->mnt_count, 1);
> #else
> @@ -328,11 +320,9 @@ static struct mount *alloc_vfsmnt(const char *name)
> return mnt;
>
> #ifdef CONFIG_SMP
> -out_free_devname:
> - kfree_const(mnt->mnt_devname);
> -#endif
> out_free_id:
> mnt_free_id(mnt);
> +#endif
> out_free_cache:
> kmem_cache_free(mnt_cache, mnt);
> return NULL;
> @@ -725,7 +715,6 @@ int sb_prepare_remount_readonly(struct super_block *sb)
> static void free_vfsmnt(struct mount *mnt)
> {
> mnt_idmap_put(mnt_idmap(&mnt->mnt));
> - kfree_const(mnt->mnt_devname);
> #ifdef CONFIG_SMP
> free_percpu(mnt->mnt_pcp);
> #endif
> @@ -1180,7 +1169,7 @@ struct vfsmount *vfs_create_mount(struct fs_context *fc)
> if (!fc->root)
> return ERR_PTR(-EINVAL);
>
> - mnt = alloc_vfsmnt(fc->source);
> + mnt = alloc_vfsmnt();
> if (!mnt)
> return ERR_PTR(-ENOMEM);
>
> @@ -1248,7 +1237,7 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,
> struct mount *mnt;
> int err;
>
> - mnt = alloc_vfsmnt(old->mnt_devname);
> + mnt = alloc_vfsmnt();
> if (!mnt)
> return ERR_PTR(-ENOMEM);
>
> @@ -2957,16 +2946,12 @@ static int do_change_type(const struct path *path, int ms_flags)
> static inline bool may_copy_tree(const struct path *path)
> {
> struct mount *mnt = real_mount(path->mnt);
> - const struct dentry_operations *d_op;
>
> if (check_mnt(mnt))
> return true;
>
> - d_op = path->dentry->d_op;
> - if (d_op == &ns_dentry_operations)
> - return true;
> -
> - if (d_op == &pidfs_dentry_operations)
> + /* Make an exception for internal mounts explicitly marked as clonable */
> + if (mnt->mnt_ns == MNT_NS_INTERNAL && mnt->mnt.mnt_flags & MNT_CLONABLE)
> return true;
>
> if (!is_mounted(path->mnt))
> @@ -3201,7 +3186,7 @@ static struct file *vfs_open_tree(int dfd, const char __user *filename, unsigned
>
> if (flags & ~(AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_RECURSIVE |
> AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLONE |
> - OPEN_TREE_CLOEXEC | OPEN_TREE_NAMESPACE))
> + OPEN_TREE_CLOEXEC | OPEN_TREE_NAMESPACE | OPEN_TREE_ALT))
> return ERR_PTR(-EINVAL);
>
> if ((flags & (AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_NAMESPACE)) ==
> @@ -3215,6 +3200,8 @@ static struct file *vfs_open_tree(int dfd, const char __user *filename, unsigned
> lookup_flags &= ~LOOKUP_AUTOMOUNT;
> if (flags & AT_SYMLINK_NOFOLLOW)
> lookup_flags &= ~LOOKUP_FOLLOW;
> + if (flags & OPEN_TREE_ALT)
> + lookup_flags |= LOOKUP_ALT;
>
> /*
> * If we create a new mount namespace with the cloned mount tree we
> @@ -5387,7 +5374,6 @@ static void statmount_fs_subtype(struct kstatmount *s, struct seq_file *seq)
> static int statmount_sb_source(struct kstatmount *s, struct seq_file *seq)
> {
> struct super_block *sb = s->mnt->mnt_sb;
> - struct mount *r = real_mount(s->mnt);
>
> if (sb->s_op->show_devname) {
> size_t start = seq->count;
> @@ -5405,7 +5391,7 @@ static int statmount_sb_source(struct kstatmount *s, struct seq_file *seq)
> seq->count = start;
> seq_commit(seq, string_unescape_inplace(seq->buf + start, UNESCAPE_OCTAL));
> } else {
> - seq_puts(seq, r->mnt_devname);
> + seq_puts(seq, sb->s_devname);
> }
> return 0;
> }
> @@ -5416,6 +5402,40 @@ static void statmount_mnt_ns_id(struct kstatmount *s, struct mnt_namespace *ns)
> s->sm.mnt_ns_id = ns->ns.ns_id;
> }
>
> +void sb_add_option(struct sb_opt_ctx *ctx, const char *name, const char *value)
> +{
> + sb_add_option_path(ctx, name, value, NULL);
> +}
> +
> +void sb_add_option_path(struct sb_opt_ctx *ctx, const char *name, const char *value,
> + const struct path *path)
> +{
> + ctx->actor(ctx, name, value, path);
> +}
> +
> +static void do_show_option(struct sb_opt_ctx *ctx, const char *name, const char *value,
> + const struct path *path)
> +{
> + if (strcmp(name, "source") != 0)
> + seq_show_option(ctx->data, name, value);
> +}
> +
> +int mnt_show_options(struct seq_file *seq, struct vfsmount *mnt)
> +{
> + struct super_block *sb = mnt->mnt_sb;
> +
> + if (sb->s_op->show_options)
> + return sb->s_op->show_options(seq, mnt->mnt_root);
> +
> + if (sb->s_op->get_options) {
> + struct sb_opt_ctx ctx = { .data = seq, .actor = do_show_option };
> +
> + return sb->s_op->get_options(&ctx, mnt->mnt_root);
> + }
> +
> + return 0;
> +}
> +
> static int statmount_mnt_opts(struct kstatmount *s, struct seq_file *seq)
> {
> struct vfsmount *mnt = s->mnt;
> @@ -5427,11 +5447,9 @@ static int statmount_mnt_opts(struct kstatmount *s, struct seq_file *seq)
> if (err)
> return err;
>
> - if (sb->s_op->show_options) {
> - err = sb->s_op->show_options(seq, mnt->mnt_root);
> - if (err)
> - return err;
> - }
> + err = mnt_show_options(seq, mnt);
> + if (err)
> + return err;
>
> if (unlikely(seq_has_overflowed(seq)))
> return -EAGAIN;
> @@ -5479,14 +5497,10 @@ static inline int statmount_opt_process(struct seq_file *seq, size_t start)
> static int statmount_opt_array(struct kstatmount *s, struct seq_file *seq)
> {
> struct vfsmount *mnt = s->mnt;
> - struct super_block *sb = mnt->mnt_sb;
> size_t start = seq->count;
> int err;
>
> - if (!sb->s_op->show_options)
> - return 0;
> -
> - err = sb->s_op->show_options(seq, mnt->mnt_root);
> + err = mnt_show_options(seq, mnt);
> if (err)
> return err;
>
> diff --git a/fs/nsfs.c b/fs/nsfs.c
> index c3b6ae76594a..e110130d0760 100644
> --- a/fs/nsfs.c
> +++ b/fs/nsfs.c
> @@ -684,6 +684,7 @@ void __init nsfs_init(void)
> if (IS_ERR(nsfs_mnt))
> panic("can't set nsfs up\n");
> nsfs_mnt->mnt_sb->s_flags &= ~SB_NOUSER;
> + nsfs_mnt->mnt_flags |= MNT_CLONABLE;
> nsfs_root_path.mnt = nsfs_mnt;
> nsfs_root_path.dentry = nsfs_mnt->mnt_root;
> }
> diff --git a/fs/open.c b/fs/open.c
> index 408925d7bd0b..3a081a9e3062 100644
> --- a/fs/open.c
> +++ b/fs/open.c
> @@ -1131,7 +1131,8 @@ struct file *kernel_file_open(const struct path *path, int flags,
> EXPORT_SYMBOL_GPL(kernel_file_open);
>
> #define WILL_CREATE(flags) (flags & (O_CREAT | __O_TMPFILE))
> -#define O_PATH_FLAGS (O_DIRECTORY | O_NOFOLLOW | O_PATH | O_CLOEXEC | O_EMPTYPATH)
> +#define O_PATH_FLAGS (O_DIRECTORY | O_NOFOLLOW | O_PATH | O_CLOEXEC | O_EMPTYPATH | \
> + O_ALT)
>
> inline struct open_how build_open_how(int flags, umode_t mode)
> {
> @@ -1283,6 +1284,8 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
> lookup_flags |= LOOKUP_FOLLOW;
> if (flags & O_EMPTYPATH)
> lookup_flags |= LOOKUP_EMPTY;
> + if (flags & O_ALT)
> + lookup_flags |= LOOKUP_ALT;
>
> if (how->resolve & RESOLVE_NO_XDEV)
> lookup_flags |= LOOKUP_NO_XDEV;
> diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
> index c93fcaa45d4a..c5a9e858e366 100644
> --- a/fs/overlayfs/params.c
> +++ b/fs/overlayfs/params.c
> @@ -1046,10 +1046,11 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
> * Prints the mount options for a given superblock.
> * Returns zero; does not fail.
> */
> -int ovl_show_options(struct seq_file *m, struct dentry *dentry)
> +int ovl_get_options(struct sb_opt_ctx *ctx, struct dentry *dentry)
> {
> struct super_block *sb = dentry->d_sb;
> struct ovl_fs *ofs = OVL_FS(sb);
> + struct path upperpath;
> size_t nr, nr_merged_lower, nr_lower = 0;
> char **lowerdirs = ofs->config.lowerdirs;
>
> @@ -1062,43 +1063,47 @@ int ovl_show_options(struct seq_file *m, struct dentry *dentry)
> * with the new lowerdir+ and datadir+ mount options.
> */
> if (lowerdirs[0]) {
> - seq_show_option(m, "lowerdir", lowerdirs[0]);
> + sb_add_option(ctx, "lowerdir", lowerdirs[0]);
> } else {
> nr_lower = ofs->numlayer;
> nr_merged_lower = nr_lower - ofs->numdatalayer;
> }
> for (nr = 1; nr < nr_lower; nr++) {
> + struct path lowerpath = {
> + .mnt = ofs->layers[nr].mnt,
> + .dentry = ofs->layers[nr].mnt->mnt_root,
> + };
> if (nr < nr_merged_lower)
> - seq_show_option(m, "lowerdir+", lowerdirs[nr]);
> + sb_add_option_path(ctx, "lowerdir+", lowerdirs[nr],
> + &lowerpath);
> else
> - seq_show_option(m, "datadir+", lowerdirs[nr]);
> + sb_add_option_path(ctx, "datadir+", lowerdirs[nr],
> + &lowerpath);
> }
> if (ofs->config.upperdir) {
> - seq_show_option(m, "upperdir", ofs->config.upperdir);
> - seq_show_option(m, "workdir", ofs->config.workdir);
> + ovl_path_upper(sb->s_root, &upperpath);
> + sb_add_option_path(ctx, "upperdir", ofs->config.upperdir, &upperpath);
> + sb_add_option(ctx, "workdir", ofs->config.workdir);
> }
> if (ofs->config.default_permissions)
> - seq_puts(m, ",default_permissions");
> + sb_add_option(ctx, "default_permissions", NULL);
> if (ofs->config.redirect_mode != ovl_redirect_mode_def())
> - seq_printf(m, ",redirect_dir=%s",
> - ovl_redirect_mode(&ofs->config));
> + sb_add_option(ctx, "redirect_dir", ovl_redirect_mode(&ofs->config));
> if (ofs->config.index != ovl_index_def)
> - seq_printf(m, ",index=%s", str_on_off(ofs->config.index));
> + sb_add_option(ctx, "index", str_on_off(ofs->config.index));
> if (ofs->config.uuid != ovl_uuid_def())
> - seq_printf(m, ",uuid=%s", ovl_uuid_mode(&ofs->config));
> + sb_add_option(ctx, "uuid", ovl_uuid_mode(&ofs->config));
> if (ofs->config.nfs_export != ovl_nfs_export_def)
> - seq_printf(m, ",nfs_export=%s",
> - str_on_off(ofs->config.nfs_export));
> + sb_add_option(ctx, "nfs_export", str_on_off(ofs->config.nfs_export));
> if (ofs->config.xino != ovl_xino_def() && !ovl_same_fs(ofs))
> - seq_printf(m, ",xino=%s", ovl_xino_mode(&ofs->config));
> + sb_add_option(ctx, "xino", ovl_xino_mode(&ofs->config));
> if (ofs->config.metacopy != ovl_metacopy_def)
> - seq_printf(m, ",metacopy=%s", str_on_off(ofs->config.metacopy));
> + sb_add_option(ctx, "metacopy", str_on_off(ofs->config.metacopy));
> if (ofs->config.fsync_mode != ovl_fsync_mode_def())
> - seq_printf(m, ",fsync=%s", ovl_fsync_mode(&ofs->config));
> + sb_add_option(ctx, "fsync", ovl_fsync_mode(&ofs->config));
> if (ofs->config.userxattr)
> - seq_puts(m, ",userxattr");
> + sb_add_option(ctx, "userxattr", NULL);
> if (ofs->config.verity_mode != ovl_verity_mode_def())
> - seq_printf(m, ",verity=%s",
> - ovl_verity_mode(&ofs->config));
> + sb_add_option(ctx, "verity", ovl_verity_mode(&ofs->config));
> return 0;
> }
> diff --git a/fs/overlayfs/params.h b/fs/overlayfs/params.h
> index ffd53cdd8482..2cf9c4f188a9 100644
> --- a/fs/overlayfs/params.h
> +++ b/fs/overlayfs/params.h
> @@ -40,5 +40,5 @@ int ovl_init_fs_context(struct fs_context *fc);
> void ovl_free_fs(struct ovl_fs *ofs);
> int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
> struct ovl_config *config);
> -int ovl_show_options(struct seq_file *m, struct dentry *dentry);
> +int ovl_get_options(struct sb_opt_ctx *ctx, struct dentry *dentry);
> const char *ovl_xino_mode(struct ovl_config *config);
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index 60f0b7ceef0a..4b57b2db0852 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -302,7 +302,7 @@ static const struct super_operations ovl_super_operations = {
> .put_super = ovl_put_super,
> .sync_fs = ovl_sync_fs,
> .statfs = ovl_statfs,
> - .show_options = ovl_show_options,
> + .get_options = ovl_get_options,
> };
>
> #define OVL_WORKDIR_NAME "work"
> @@ -529,6 +529,7 @@ static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs,
>
> /* Don't inherit atime flags */
> upper_mnt->mnt_flags &= ~(MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME);
> + upper_mnt->mnt_flags |= MNT_CLONABLE;
> upper_layer->mnt = upper_mnt;
> upper_layer->idx = 0;
> upper_layer->fsid = 0;
> @@ -1105,7 +1106,7 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
> * Make lower layers R/O. That way fchmod/fchown on lower file
> * will fail instead of modifying lower fs.
> */
> - mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME;
> + mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME | MNT_CLONABLE;
>
> layers[ofs->numlayer].trap = trap;
> layers[ofs->numlayer].mnt = mnt;
> diff --git a/fs/pidfs.c b/fs/pidfs.c
> index aaa609ddab04..3843acd6594d 100644
> --- a/fs/pidfs.c
> +++ b/fs/pidfs.c
> @@ -840,7 +840,7 @@ static char *pidfs_dname(struct dentry *dentry, char *buffer, int buflen)
> return dynamic_dname(buffer, buflen, "anon_inode:[pidfd]");
> }
>
> -const struct dentry_operations pidfs_dentry_operations = {
> +static const struct dentry_operations pidfs_dentry_operations = {
> .d_dname = pidfs_dname,
> .d_prune = stashed_dentry_prune,
> };
> @@ -1151,6 +1151,8 @@ void __init pidfs_init(void)
> if (IS_ERR(pidfs_mnt))
> panic("Failed to mount pidfs pseudo filesystem");
>
> + pidfs_mnt->mnt_flags |= MNT_CLONABLE;
> +
> pidfs_root_path.mnt = pidfs_mnt;
> pidfs_root_path.dentry = pidfs_mnt->mnt_root;
> }
> diff --git a/fs/proc_namespace.c b/fs/proc_namespace.c
> index 5c555db68aa2..94bc4c9aa43f 100644
> --- a/fs/proc_namespace.c
> +++ b/fs/proc_namespace.c
> @@ -98,21 +98,27 @@ static void show_type(struct seq_file *m, struct super_block *sb)
> }
> }
>
> +static int show_devname(struct seq_file *m, struct vfsmount *mnt)
> +{
> + struct super_block *sb = mnt->mnt_sb;
> +
> + if (sb->s_op->show_devname)
> + return sb->s_op->show_devname(m, mnt->mnt_root);
> +
> + mangle(m, sb->s_devname);
> + return 0;
> +}
> +
> static int show_vfsmnt(struct seq_file *m, struct vfsmount *mnt)
> {
> struct proc_mounts *p = m->private;
> - struct mount *r = real_mount(mnt);
> struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
> struct super_block *sb = mnt_path.dentry->d_sb;
> int err;
>
> - if (sb->s_op->show_devname) {
> - err = sb->s_op->show_devname(m, mnt_path.dentry);
> - if (err)
> - goto out;
> - } else {
> - mangle(m, r->mnt_devname);
> - }
> + err = show_devname(m, mnt);
> + if (err)
> + goto out;
> seq_putc(m, ' ');
> /* mountpoints outside of chroot jail will give SEQ_SKIP on this */
> err = seq_path_root(m, &mnt_path, &p->root, " \t\n\\");
> @@ -125,8 +131,7 @@ static int show_vfsmnt(struct seq_file *m, struct vfsmount *mnt)
> if (err)
> goto out;
> show_vfsmnt_opts(m, mnt);
> - if (sb->s_op->show_options)
> - err = sb->s_op->show_options(m, mnt_path.dentry);
> + err = mnt_show_options(m, mnt);
> seq_puts(m, " 0 0\n");
> out:
> return err;
> @@ -172,19 +177,14 @@ static int show_mountinfo(struct seq_file *m, struct vfsmount *mnt)
> seq_puts(m, " - ");
> show_type(m, sb);
> seq_putc(m, ' ');
> - if (sb->s_op->show_devname) {
> - err = sb->s_op->show_devname(m, mnt->mnt_root);
> - if (err)
> - goto out;
> - } else {
> - mangle(m, r->mnt_devname);
> - }
> + err = show_devname(m, mnt);
> + if (err)
> + goto out;
> seq_puts(m, sb_rdonly(sb) ? " ro" : " rw");
> err = show_sb_opts(m, sb);
> if (err)
> goto out;
> - if (sb->s_op->show_options)
> - err = sb->s_op->show_options(m, mnt->mnt_root);
> + err = mnt_show_options(m, mnt);
> seq_putc(m, '\n');
> out:
> return err;
> @@ -193,21 +193,15 @@ static int show_mountinfo(struct seq_file *m, struct vfsmount *mnt)
> static int show_vfsstat(struct seq_file *m, struct vfsmount *mnt)
> {
> struct proc_mounts *p = m->private;
> - struct mount *r = real_mount(mnt);
> struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
> struct super_block *sb = mnt_path.dentry->d_sb;
> int err;
>
> /* device */
> seq_puts(m, "device ");
> - if (sb->s_op->show_devname) {
> - err = sb->s_op->show_devname(m, mnt_path.dentry);
> - if (err)
> - goto out;
> - } else {
> - mangle(m, r->mnt_devname);
> - }
> -
> + err = show_devname(m, mnt);
> + if (err)
> + goto out;
> /* mount point */
> seq_puts(m, " mounted on ");
> /* mountpoints outside of chroot jail will give SEQ_SKIP on this */
> diff --git a/fs/super.c b/fs/super.c
> index a8fd61136aaf..e5531cb88f89 100644
> --- a/fs/super.c
> +++ b/fs/super.c
> @@ -280,6 +280,7 @@ static void destroy_super_work(struct work_struct *work)
> security_sb_free(s);
> put_user_ns(s->s_user_ns);
> kfree(s->s_subtype);
> + kfree_const(s->s_devname);
> for (int i = 0; i < SB_FREEZE_LEVELS; i++)
> percpu_free_rwsem(&s->s_writers.rw_sem[i]);
> kfree(s);
> @@ -315,7 +316,7 @@ static void destroy_unused_super(struct super_block *s)
> * returns a pointer new superblock or %NULL if allocation had failed.
> */
> static struct super_block *alloc_super(struct file_system_type *type, int flags,
> - struct user_namespace *user_ns)
> + struct user_namespace *user_ns, const char *name)
> {
> struct super_block *s = kzalloc_obj(struct super_block);
> static const struct super_operations default_op;
> @@ -344,6 +345,13 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
> */
> down_write_nested(&s->s_umount, SINGLE_DEPTH_NESTING);
>
> + if (name)
> + s->s_devname = kstrdup_const(name, GFP_KERNEL_ACCOUNT);
> + else
> + s->s_devname = "none";
> + if (!s->s_devname)
> + goto fail;
> +
> if (security_sb_alloc(s))
> goto fail;
>
> @@ -763,7 +771,7 @@ struct super_block *sget_fc(struct fs_context *fc,
> }
> if (!s) {
> spin_unlock(&sb_lock);
> - s = alloc_super(fc->fs_type, fc->sb_flags, user_ns);
> + s = alloc_super(fc->fs_type, fc->sb_flags, user_ns, fc->source);
> if (!s)
> return ERR_PTR(-ENOMEM);
> goto retry;
> diff --git a/include/linux/fcntl.h b/include/linux/fcntl.h
> index 6ad6b9e7a226..f942411c8ed7 100644
> --- a/include/linux/fcntl.h
> +++ b/include/linux/fcntl.h
> @@ -11,7 +11,7 @@
> (O_RDONLY | O_WRONLY | O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | \
> O_APPEND | O_NDELAY | O_NONBLOCK | __O_SYNC | O_DSYNC | \
> FASYNC | O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | \
> - O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE | O_EMPTYPATH)
> + O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE | O_EMPTYPATH | O_ALT)
>
> /* List of all valid flags for openat2(2)'s how->flags argument. */
> #define VALID_OPENAT2_FLAGS (VALID_OPEN_FLAGS | OPENAT2_REGULAR)
> diff --git a/include/linux/fs/super.h b/include/linux/fs/super.h
> index 405612678115..6f0d778940f9 100644
> --- a/include/linux/fs/super.h
> +++ b/include/linux/fs/super.h
> @@ -237,4 +237,14 @@ int thaw_super(struct super_block *super, enum freeze_holder who,
>
> int sb_init_dio_done_wq(struct super_block *sb);
>
> +struct sb_opt_ctx {
> + void (*actor)(struct sb_opt_ctx *ctx, const char *name, const char *value,
> + const struct path *path);
> + void *data;
> +};
> +
> +void sb_add_option(struct sb_opt_ctx *ctx, const char *name, const char *value);
> +void sb_add_option_path(struct sb_opt_ctx *ctx, const char *name, const char *value,
> + const struct path *path);
> +
> #endif /* _LINUX_FS_SUPER_H */
> diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
> index ef7941e9dc79..f205759af1a6 100644
> --- a/include/linux/fs/super_types.h
> +++ b/include/linux/fs/super_types.h
> @@ -36,6 +36,7 @@ struct workqueue_struct;
> struct writeback_control;
> struct xattr_handler;
> struct fserror_event;
> +struct sb_opt_ctx;
>
> extern struct super_block *blockdev_superblock;
>
> @@ -99,6 +100,7 @@ struct super_operations {
> int (*statfs)(struct dentry *dentry, struct kstatfs *kstatfs);
> void (*umount_begin)(struct super_block *sb);
>
> + int (*get_options)(struct sb_opt_ctx *ctx, struct dentry *dentry);
> int (*show_options)(struct seq_file *seq, struct dentry *dentry);
> int (*show_devname)(struct seq_file *seq, struct dentry *dentry);
> int (*show_path)(struct seq_file *seq, struct dentry *dentry);
> @@ -283,6 +285,8 @@ struct super_block {
> */
> atomic_t s_isw_nr_in_flight;
> #endif
> + /* Name of device e.g. /dev/dsk/hda1 */
> + const char *s_devname;
> } __randomize_layout;
>
> /*
> diff --git a/include/linux/mount.h b/include/linux/mount.h
> index acfe7ef86a1b..d071855ca4ed 100644
> --- a/include/linux/mount.h
> +++ b/include/linux/mount.h
> @@ -44,6 +44,7 @@ enum mount_flags {
> MNT_LOCKED = 0x800000,
> MNT_DOOMED = 0x1000000,
> MNT_SYNC_UMOUNT = 0x2000000,
> + MNT_CLONABLE = 0x4000000,
> MNT_UMOUNT = 0x8000000,
>
> MNT_USER_SETTABLE_MASK = MNT_NOSUID | MNT_NODEV | MNT_NOEXEC
> @@ -52,7 +53,7 @@ enum mount_flags {
> MNT_ATIME_MASK = MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME,
>
> MNT_INTERNAL_FLAGS = MNT_INTERNAL | MNT_DOOMED |
> - MNT_SYNC_UMOUNT | MNT_LOCKED
> + MNT_SYNC_UMOUNT | MNT_LOCKED | MNT_CLONABLE
> };
>
> struct vfsmount {
> diff --git a/include/linux/namei.h b/include/linux/namei.h
> index ebe6e29f7e93..c21dc6536900 100644
> --- a/include/linux/namei.h
> +++ b/include/linux/namei.h
> @@ -25,7 +25,8 @@ enum { MAX_NESTED_LINKS = 8 };
> #define LOOKUP_RCU BIT(8) /* RCU pathwalk mode; semi-internal */
> #define LOOKUP_CACHED BIT(9) /* Only do cached lookup */
> #define LOOKUP_PARENT BIT(10) /* Looking up final parent in path */
> -/* 5 spare bits for pathwalk */
> +#define LOOKUP_ALT BIT(11) /* Look up in alternative namespace */
> +/* 4 spare bits for pathwalk */
>
> /* These tell filesystem methods that we are dealing with the final component... */
> #define LOOKUP_OPEN BIT(16) /* ... in open */
> diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h
> index 0abf7da9ab23..fab6f48ff203 100644
> --- a/include/linux/pidfs.h
> +++ b/include/linux/pidfs.h
> @@ -15,7 +15,6 @@ void pidfs_exit(struct task_struct *tsk);
> #ifdef CONFIG_COREDUMP
> void pidfs_coredump(const struct coredump_params *cprm);
> #endif
> -extern const struct dentry_operations pidfs_dentry_operations;
> int pidfs_register_pid_gfp(struct pid *pid, gfp_t gfp);
>
> /**
> diff --git a/include/uapi/asm-generic/fcntl.h b/include/uapi/asm-generic/fcntl.h
> index 883cfd7de2ef..70dbbac1c520 100644
> --- a/include/uapi/asm-generic/fcntl.h
> +++ b/include/uapi/asm-generic/fcntl.h
> @@ -96,6 +96,10 @@
> #define O_EMPTYPATH (1 << 26) /* allow empty path */
> #endif
>
> +#ifndef O_ALT
> +#define O_ALT (1 << 27) /* Open in alternative namespace */
> +#endif
> +
> /* a horrid kludge trying to make sure that this will fail on old kernels */
> #define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
>
> diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h
> index 2204708dbf7a..6e794d214cf9 100644
> --- a/include/uapi/linux/mount.h
> +++ b/include/uapi/linux/mount.h
> @@ -63,6 +63,7 @@
> */
> #define OPEN_TREE_CLONE (1 << 0) /* Clone the target tree and attach the clone */
> #define OPEN_TREE_NAMESPACE (1 << 1) /* Clone the target tree into a new mount namespace */
> +#define OPEN_TREE_ALT (1 << 2) /* Lookup in alternative namespace */
> #define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */
>
> /*
> diff --git a/scripts/gdb/linux/proc.py b/scripts/gdb/linux/proc.py
> index 65dd1bd12964..5aaf2dd8db05 100644
> --- a/scripts/gdb/linux/proc.py
> +++ b/scripts/gdb/linux/proc.py
> @@ -174,9 +174,6 @@ values of that process namespace"""
> "mount", "super_block", "devname", "pathname", "fstype"))
>
> for mnt in rbtree.rb_inorder_for_each_entry(namespace['mounts'], mount_ptr_type, "mnt_node"):
> - devname = mnt['mnt_devname'].string()
> - devname = devname if devname else "none"
> -
> pathname = ""
> parent = mnt
> while True:
> @@ -190,6 +187,8 @@ values of that process namespace"""
> pathname = "/"
>
> superblock = mnt['mnt']['mnt_sb']
> + devname = superblock['s_devname'].string()
> + devname = devname if devname else "none"
> fstype = superblock['s_type']['name'].string()
> s_flags = int(superblock['s_flags'])
> m_flags = int(mnt['mnt']['mnt_flags'])
Thanks for the patch! I've tested it for my use case, and it works
well.
I had to amend the following diff though:
diff --git a/fs/namespace.c b/fs/namespace.c
index 62cc71c401a2..cf528e92e17e 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -5406,12 +5406,14 @@ void sb_add_option(struct sb_opt_ctx *ctx, const char *name, const char *value)
{
sb_add_option_path(ctx, name, value, NULL);
}
+EXPORT_SYMBOL_GPL(sb_add_option);
void sb_add_option_path(struct sb_opt_ctx *ctx, const char *name, const char *value,
const struct path *path)
{
ctx->actor(ctx, name, value, path);
}
+EXPORT_SYMBOL_GPL(sb_add_option_path);
static void do_show_option(struct sb_opt_ctx *ctx, const char *name, const char *value,
const struct path *path)
Regards,
Giuseppe
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-15 16:43 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-15 10:11 [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT Miklos Szeredi
2026-07-15 16:43 ` Giuseppe Scrivano
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox