* [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
` (2 more replies)
0 siblings, 3 replies; 16+ 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] 16+ 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 2026-07-19 14:12 ` Andy Lutomirski 2026-07-22 15:44 ` Christian Brauner 2 siblings, 0 replies; 16+ 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] 16+ 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 @ 2026-07-19 14:12 ` Andy Lutomirski 2026-07-19 18:42 ` Miklos Szeredi 2026-07-20 9:22 ` Miklos Szeredi 2026-07-22 15:44 ` Christian Brauner 2 siblings, 2 replies; 16+ messages in thread From: Andy Lutomirski @ 2026-07-19 14:12 UTC (permalink / raw) To: Miklos Szeredi Cc: Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Amir Goldstein, Gao Xiang On Wed, Jul 15, 2026 at 3:17 AM Miklos Szeredi <mszeredi@redhat.com> wrote: > > 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 gives me the willies a bit. It's very cool, but it has some potential security issues that I think we need to watch out for. As sort-of prior art, NTFS supports alternate streams and Reiser4, ahem, supported files-as-directories. Some questions that we would need to consider: 1. Can you open an fd referring to a "directory" within an O_ALT tree? What happens when an unsuspecting process gets such an fd? Can you fchdir to it? chroot to it? clone its tree and possibly bind-mount it somewhere? 2. Can you bind-mount *onto* something inside O_ALT? 3. Whose creds are used for which operations? 4. Can you mmap the things in O_ALT? Is this determined per object or generically? 5. Would we ever allow FUSE to expose custom things inside O_ALT? What contents of O_ALT can a program trust come from the kernel and have the expected semantics? --Andy -- Andy Lutomirski AMA Capital Management, LLC ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-19 14:12 ` Andy Lutomirski @ 2026-07-19 18:42 ` Miklos Szeredi 2026-07-20 9:22 ` Miklos Szeredi 1 sibling, 0 replies; 16+ messages in thread From: Miklos Szeredi @ 2026-07-19 18:42 UTC (permalink / raw) To: Andy Lutomirski Cc: Miklos Szeredi, Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Amir Goldstein, Gao Xiang On Sun, 19 Jul 2026 at 16:13, Andy Lutomirski <luto@amacapital.net> wrote: > This gives me the willies a bit. It's very cool, but it has some > potential security issues that I think we need to watch out for. As > sort-of prior art, NTFS supports alternate streams and Reiser4, ahem, > supported files-as-directories. > > Some questions that we would need to consider: > > 1. Can you open an fd referring to a "directory" within an O_ALT tree? Yes. > What happens when an unsuspecting process gets such an fd? Can you > fchdir to it? chroot to it? Yes. However you cannot escape from that particular base file's O_ALT tree. > clone its tree and possibly bind-mount > it somewhere? No, it's an "internal" mount, so no cloning. > 2. Can you bind-mount *onto* something inside O_ALT? Again, no, because it's an internal mount, not in any mount namespace. > 3. Whose creds are used for which operations? The creds of the process doing the operation. > 4. Can you mmap the things in O_ALT? Is this determined per object or > generically? Per object. > 5. Would we ever allow FUSE to expose custom things inside O_ALT? > What contents of O_ALT can a program trust come from the kernel and > have the expected semantics? I think adding such a plugin would be similar to adding a mount. So I'd suggest allowing it with the same privs (CAP_SYS_ADMIN) and constraints (limited to the current mount namespace). Thanks, Miklos ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-19 14:12 ` Andy Lutomirski 2026-07-19 18:42 ` Miklos Szeredi @ 2026-07-20 9:22 ` Miklos Szeredi 2026-07-22 14:25 ` Christian Brauner 1 sibling, 1 reply; 16+ messages in thread From: Miklos Szeredi @ 2026-07-20 9:22 UTC (permalink / raw) To: Andy Lutomirski Cc: Miklos Szeredi, Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Amir Goldstein, Gao Xiang On Sun, 19 Jul 2026 at 16:13, Andy Lutomirski <luto@amacapital.net> wrote: > This gives me the willies a bit. It's very cool, but it has some > potential security issues that I think we need to watch out for. As > sort-of prior art, NTFS supports alternate streams and Reiser4, ahem, > supported files-as-directories. I realized that we can keep the creepiness factor minimal by introducing this first in procfs only. That way zero changes are needed in the path lookup code: sprintf(buf, "/proc/self/fd-alt/%d/%s", base_fd, alt_path); alt_fd = open(buf, O_PATH); Thanks, Miklos ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-20 9:22 ` Miklos Szeredi @ 2026-07-22 14:25 ` Christian Brauner 2026-07-22 14:51 ` Miklos Szeredi 0 siblings, 1 reply; 16+ messages in thread From: Christian Brauner @ 2026-07-22 14:25 UTC (permalink / raw) To: Miklos Szeredi Cc: Andy Lutomirski, Miklos Szeredi, Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Amir Goldstein, Gao Xiang On 2026-07-20 11:22 +0200, Miklos Szeredi wrote: > On Sun, 19 Jul 2026 at 16:13, Andy Lutomirski <luto@amacapital.net> wrote: > > > This gives me the willies a bit. It's very cool, but it has some > > potential security issues that I think we need to watch out for. As > > sort-of prior art, NTFS supports alternate streams and Reiser4, ahem, > > supported files-as-directories. > > I realized that we can keep the creepiness factor minimal by > introducing this first in procfs only. That way zero changes are > needed in the path lookup code: > > sprintf(buf, "/proc/self/fd-alt/%d/%s", base_fd, alt_path); > alt_fd = open(buf, O_PATH); I'm still vehemently opposed to stuffing more creepy apis into procfs. So this gets clear opposition from me. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-22 14:25 ` Christian Brauner @ 2026-07-22 14:51 ` Miklos Szeredi 0 siblings, 0 replies; 16+ messages in thread From: Miklos Szeredi @ 2026-07-22 14:51 UTC (permalink / raw) To: Christian Brauner Cc: Andy Lutomirski, Miklos Szeredi, Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Amir Goldstein, Gao Xiang On Wed, 22 Jul 2026 at 16:25, Christian Brauner <brauner@kernel.org> wrote: > I'm still vehemently opposed to stuffing more creepy apis into procfs. > So this gets clear opposition from me. Let's try to leave out loaded terms from a technical discussion. I never said this was creepy, Andy was qualifying the O_ALT thing. Other than that, do you have a specific issue with the proposed interface? Thanks, Miklos ^ permalink raw reply [flat|nested] 16+ 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 2026-07-19 14:12 ` Andy Lutomirski @ 2026-07-22 15:44 ` Christian Brauner 2026-07-22 16:29 ` Andy Lutomirski 2026-07-23 8:00 ` Miklos Szeredi 2 siblings, 2 replies; 16+ messages in thread From: Christian Brauner @ 2026-07-22 15:44 UTC (permalink / raw) To: Miklos Szeredi Cc: Giuseppe Scrivano, 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> We've added a primary set of apis to retrieve mount options. I really dislike the idea of introducing a completely orthogonal layer to do the same thing (or more) and also make it filesystem based while at it. The other proposal of moving it into procfs doesn't lower the creepiness factor. It just paints it a different color. My objection is to the shape of this not where the entry point lives. metafs is the same thing in a different color. I really don't see any way that we would ever merge something like this. Like, I'm fine having an api where we can use open_tree() to see through into underlying layers and given the right permission pull a mount out of it. That seems something we can potentially do. There's a few issues on top of that. O_ALT is silently ignored unless you spell the open as openat(dirfd, relative, ...). path_init() only looks at LOOKUP_ALT in the dirfd branch. open("/abs", O_ALT) and openat(AT_FDCWD, "rel", O_ALT) fall straight through and open the real file. The layer symlinks are an unchecked jump into internal, out-of-namespace mounts. Following mount/options/upperdir/0 ends in nd_jump_link(). That doesn't do permission checks and no mnt_ns check. The overlay upper/lower layers are internal mounts with no mountpoint in any namespace. So any unprivileged process that can open the overlay root gets an O_PATH handle to a layer root it has no search-permission path to and on a mount outside its namespace and can then use it as a dirfd. Pulling the mount out is gated by CAP_SYS_ADMIN, but the handle and the traversal are not. That's exactly Andy's "whose creds" question, it's inherent to the design, and the procfs variant has it too. get_options() forks ->show_options() permanently. It's added alongside the existing method and mnt_show_options() now tries one then the other. That leaves the tree split forever or commits us to a flag-day conversion of everything. The new thig is also heavie and loses the seq_file overflow-retry. All so the 99% of filesystems that will never expose a path option can carry a more complicated interface. mnt_devname to s_devname move is a behavioral change. alloc_super() records fc->source only on the sb-create path in sget_fc(). Sharing an existing sb path never updates it. For filesystems that share a superblock across mounts every mount now reports the first mounter's source in /proc/self/mountinfo and statmount(). And the smaller details really show how inelegant this model is and why Al and I have opposed it multiple times before. Scalar options are modeled as symlinks and non-path options are dangling links whose readlink still returns the value while for path options readlink returns the option string but following the link jumps somewhere else entirely. erofs silently emits a valueless "fsoffset" if the kasprintf fails. There's no Kconfig at all. metafs is obj-y and kern_mount()ed in every kernel. I don't want to see this leak into every container going forward at all with the same hackishness to make it somehow safe in containers. Like I said before: I'm fine with an API where we can use open_tree() to see through into underlying layers, and given the right permission pull a mount out of it. That is a narrow, checkable primitive and it needs none of this. No new open flag, no second ->show_options, no metafs, no mnt_devname surgery. That part we can potentially do. But a generic metadata interface layered on top of yet another magic filesystem whether it's mounted or hidden behind procfs I feel very strongly about. I also have great difficulty seeing Al supporting it. -- ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-22 15:44 ` Christian Brauner @ 2026-07-22 16:29 ` Andy Lutomirski 2026-07-23 8:22 ` Miklos Szeredi 2026-07-23 8:00 ` Miklos Szeredi 1 sibling, 1 reply; 16+ messages in thread From: Andy Lutomirski @ 2026-07-22 16:29 UTC (permalink / raw) To: Christian Brauner Cc: Miklos Szeredi, Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Amir Goldstein, Gao Xiang On Wed, Jul 22, 2026 at 9:06 AM Christian Brauner <brauner@kernel.org> wrote: > Like I said before: I'm fine with an API where we can use open_tree() to > see through into underlying layers, and given the right permission pull > a mount out of it. That is a narrow, checkable primitive and it needs > none of this. No new open flag, no second ->show_options, no metafs, no > mnt_devname surgery. That part we can potentially do. What, exactly, do you mean as the distinction between "[seeing] through into underlying layers" and "[pulling] a mount out of it". I have a guess, but I'm not convinced that my guess is right. Other than that question, I think I generally agree with you. The interesting operations here (getting an fd to something that was previously inaccessible) are different enough from normal path lookups that I think they deserve to be explicit syscalls or syscall modes, not magic links. --Andy ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-22 16:29 ` Andy Lutomirski @ 2026-07-23 8:22 ` Miklos Szeredi 2026-07-23 9:00 ` Andy Lutomirski 0 siblings, 1 reply; 16+ messages in thread From: Miklos Szeredi @ 2026-07-23 8:22 UTC (permalink / raw) To: Andy Lutomirski Cc: Christian Brauner, Miklos Szeredi, Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Amir Goldstein, Gao Xiang On Wed, 22 Jul 2026 at 18:36, Andy Lutomirski <luto@amacapital.net> wrote: > Other than that question, I think I generally agree with you. The > interesting operations here (getting an fd to something that was > previously inaccessible) are different enough from normal path lookups > that I think they deserve to be explicit syscalls or syscall modes, > not magic links. This is a super specialized use case. I sort of agree that doing statmount() as a syscall was not a bad idea. But new syscall for getting a backing layer, that makes sense on just a couple of filesystems? And that leaves us with ioctl. And ioclt() returning an open fd has it's own problems, besides ioctl being a generally bad interface. And we have all these powerful concepts and interfaces for filesystems, why the big resistance to actually using them?. Sure, it's easy to misuse, but I don't yet see why this particular case would be a misuse. Enlighten me please. Thanks, Miklos ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-23 8:22 ` Miklos Szeredi @ 2026-07-23 9:00 ` Andy Lutomirski 2026-07-23 9:19 ` Miklos Szeredi 0 siblings, 1 reply; 16+ messages in thread From: Andy Lutomirski @ 2026-07-23 9:00 UTC (permalink / raw) To: Miklos Szeredi Cc: Christian Brauner, Miklos Szeredi, Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Amir Goldstein, Xiang Gao > On Jul 23, 2026, at 10:22 AM, Miklos Szeredi <miklos@szeredi.hu> wrote: > > On Wed, 22 Jul 2026 at 18:36, Andy Lutomirski <luto@amacapital.net> wrote: > >> Other than that question, I think I generally agree with you. The >> interesting operations here (getting an fd to something that was >> previously inaccessible) are different enough from normal path lookups >> that I think they deserve to be explicit syscalls or syscall modes, >> not magic links. > > This is a super specialized use case. > > I sort of agree that doing statmount() as a syscall was not a bad > idea. But new syscall for getting a backing layer, that makes sense > on just a couple of filesystems? > > And that leaves us with ioctl. And ioclt() returning an open fd has > it's own problems, besides ioctl being a generally bad interface. > > And we have all these powerful concepts and interfaces for > filesystems, why the big resistance to actually using them?. > > Sure, it's easy to misuse, but I don't yet see why this particular > case would be a misuse. Enlighten me please. > I think there are are a couple of complications involved with open, openat, etc that aren’t present with explicit for-the-purpose syscalls: - O_XYZ flags to open() are all kinds of awful, for historical reasons that are not fundamental to the concept. - symlinks. If we have a new API where opening /proc/something/magic/blah can access something that ought to be inaccessible when accessed intentionally and with privilege, a symlink pointing at /proc/something/… can cause the API to be used inadvertently. Admittedly we have this problem with basically all symlinks, so this isn’t exactly unique. - our nasty fs permission model. We gave a sort of gnarly mix of a bit of fd-based permission and mostly mode/ACL-based permissions for path traversal and opening, and mapping this nicely only new APIs (as opposed to actual files and directories) can have unpleasant results. Of course, I’m busy arguing (slowly and without a concrete proposal) that we should have proper capability-like fds, and maybe that’s kind of an answer to this: What if we had an API to get an fd to the “control filesystem” for a superblock, like your O_ALT but as a real syscall or maybe only accessible via one of the newer and less janky open variants? And what if the resulting fd and the filesystem tree it represented had a few properties that made it very different from normal directory fds: - You cannot mount anything on it or its subdirectories, nor can you open_tree or otherwise mount it anywhere. But you can open_tree the very specific things in it that point outside of the special API (e.g. the overlayfs layers). - Privilege is fully captured by the original call that gets you the fd. current->cred is not checked when *using* it except to the extent that you might need privileges over your own namespaces to do operations that might affect them. - You can’t fchdir or (hypothetically) fchroot into it. (Not sure how important this is.) - Maybe you can’t follow /proc/pid/fd/N links into it either? Or maybe that would break CRIU too badly. The basic idea here is to try to treat it like an API that happens to use the open machinery but not as part of the filesystem hierarchy. One could go even farther and try to remove a bunch of the parts that make implementing it tedious. For example, if this hierarchy had “/layers/1/options”, there is really no reason to over open an fd to layers or layers/1, and if we had a readfile syscall there wouldn’t be a reason to open an fd to options either. But maybe this is a silly direction to move in. > Thanks, > Miklos ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-23 9:00 ` Andy Lutomirski @ 2026-07-23 9:19 ` Miklos Szeredi 2026-07-23 13:53 ` Andy Lutomirski 0 siblings, 1 reply; 16+ messages in thread From: Miklos Szeredi @ 2026-07-23 9:19 UTC (permalink / raw) To: Andy Lutomirski Cc: Christian Brauner, Miklos Szeredi, Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Amir Goldstein, Xiang Gao On Thu, 23 Jul 2026 at 11:01, Andy Lutomirski <luto@amacapital.net> wrote: > - O_XYZ flags to open() are all kinds of awful, for historical reasons that are not fundamental to the concept. I'm fine with limiting this to openat2(). > - symlinks. If we have a new API where opening /proc/something/magic/blah can access something that ought to be inaccessible when accessed intentionally and with privilege, a symlink pointing at /proc/something/… can cause the API to be used inadvertently. Admittedly we have this problem with basically all symlinks, so this isn’t exactly unique. > > - our nasty fs permission model. We gave a sort of gnarly mix of a bit of fd-based permission and mostly mode/ACL-based permissions for path traversal and opening, and mapping this nicely only new APIs (as opposed to actual files and directories) can have unpleasant results. Pseudo filesystems don't usually do mode/acl based permission checks. I don't see any issue with that. > Of course, I’m busy arguing (slowly and without a concrete proposal) that we should have proper capability-like fds, and maybe that’s kind of an answer to this: > > What if we had an API to get an fd to the “control filesystem” for a superblock, like your O_ALT but as a real syscall or maybe only accessible via one of the newer and less janky open variants? And what if the resulting fd and the filesystem tree it represented had a few properties that made it very different from normal directory fds: > > - You cannot mount anything on it or its subdirectories, nor can you open_tree or otherwise mount it anywhere. But you can open_tree the very specific things in it that point outside of the special API (e.g. the overlayfs layers). This patch provides exeactly those properties. > - Privilege is fully captured by the original call that gets you the fd. current->cred is not checked when *using* it except to the extent that you might need privileges over your own namespaces to do operations that might affect them. This is trivial to add to metafs: just store a ref to current->cred besides the path. Not sure how this security model would work, though. > > - You can’t fchdir or (hypothetically) fchroot into it. (Not sure how important this is.) Not sure if there are internal mounts that have directories? If not, we can just add a check to fchdir/chroot against mnt_ns being NULL. > - Maybe you can’t follow /proc/pid/fd/N links into it either? Or maybe that would break CRIU too badly. > > The basic idea here is to try to treat it like an API that happens to use the open machinery but not as part of the filesystem hierarchy. I'm not opposed to limiting this in various ways, though I don't really see the advantage. One thing I hate vehemently is directory lseek to a non-zero offset. I'd happily error out on that one. Thanks, Miklos ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-23 9:19 ` Miklos Szeredi @ 2026-07-23 13:53 ` Andy Lutomirski 0 siblings, 0 replies; 16+ messages in thread From: Andy Lutomirski @ 2026-07-23 13:53 UTC (permalink / raw) To: Miklos Szeredi Cc: Christian Brauner, Miklos Szeredi, Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Amir Goldstein, Xiang Gao On Thu, Jul 23, 2026 at 2:19 AM Miklos Szeredi <miklos@szeredi.hu> wrote: > > On Thu, 23 Jul 2026 at 11:01, Andy Lutomirski <luto@amacapital.net> wrote: > > > - O_XYZ flags to open() are all kinds of awful, for historical reasons that are not fundamental to the concept. > > I'm fine with limiting this to openat2(). > > > - symlinks. If we have a new API where opening /proc/something/magic/blah can access something that ought to be inaccessible when accessed intentionally and with privilege, a symlink pointing at /proc/something/… can cause the API to be used inadvertently. Admittedly we have this problem with basically all symlinks, so this isn’t exactly unique. > > > > - our nasty fs permission model. We gave a sort of gnarly mix of a bit of fd-based permission and mostly mode/ACL-based permissions for path traversal and opening, and mapping this nicely only new APIs (as opposed to actual files and directories) can have unpleasant results. > > Pseudo filesystems don't usually do mode/acl based permission checks. > I don't see any issue with that. > > > Of course, I’m busy arguing (slowly and without a concrete proposal) that we should have proper capability-like fds, and maybe that’s kind of an answer to this: > > > > What if we had an API to get an fd to the “control filesystem” for a superblock, like your O_ALT but as a real syscall or maybe only accessible via one of the newer and less janky open variants? And what if the resulting fd and the filesystem tree it represented had a few properties that made it very different from normal directory fds: > > > > - You cannot mount anything on it or its subdirectories, nor can you open_tree or otherwise mount it anywhere. But you can open_tree the very specific things in it that point outside of the special API (e.g. the overlayfs layers). > > This patch provides exeactly those properties. > > > - Privilege is fully captured by the original call that gets you the fd. current->cred is not checked when *using* it except to the extent that you might need privileges over your own namespaces to do operations that might affect them. > > This is trivial to add to metafs: just store a ref to current->cred > besides the path. We have f_cred that might be good enough, although I'm not sure it propagates through path lookup. > > Not sure how this security model would work, though. If I have CAP_SYS_ADMIN and I open the metafs and then drop privilege, then I would still be able to do all the metafs operations as if I had CAP_SYS_ADMIN so long as I use the fd I opened to do those operations. > > > > > - You can’t fchdir or (hypothetically) fchroot into it. (Not sure how important this is.) > > Not sure if there are internal mounts that have directories? If not, > we can just add a check to fchdir/chroot against mnt_ns being NULL. > > > - Maybe you can’t follow /proc/pid/fd/N links into it either? Or maybe that would break CRIU too badly. > > > > The basic idea here is to try to treat it like an API that happens to use the open machinery but not as part of the filesystem hierarchy. > > I'm not opposed to limiting this in various ways, though I don't > really see the advantage. I'll sleep on it. Maybe it's all fine. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-22 15:44 ` Christian Brauner 2026-07-22 16:29 ` Andy Lutomirski @ 2026-07-23 8:00 ` Miklos Szeredi 2026-07-23 9:20 ` Amir Goldstein 1 sibling, 1 reply; 16+ messages in thread From: Miklos Szeredi @ 2026-07-23 8:00 UTC (permalink / raw) To: Christian Brauner Cc: Miklos Szeredi, Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Amir Goldstein, Gao Xiang On Wed, 22 Jul 2026 at 18:06, Christian Brauner <brauner@kernel.org> wrote: > We've added a primary set of apis to retrieve mount options. I really > dislike the idea of introducing a completely orthogonal layer to do the > same thing (or more) and also make it filesystem based while at it. Fair enough. > The other proposal of moving it into procfs doesn't lower the creepiness > factor. It just paints it a different color. My objection is to the > shape of this not where the entry point lives. Also fair point. The reason I like the proc entry point is that it makes this accessible in the global namespace. > Like, I'm fine having an api where we can use open_tree() to see through > into underlying layers and given the right permission pull a mount out > of it. That seems something we can potentially do. That's exactly what I was proposing. The "retrieve all mount options" was a bad idea. Will drop it. > There's a few issues on top of that. O_ALT is silently ignored unless > you spell the open as openat(dirfd, relative, ...). path_init() only > looks at LOOKUP_ALT in the dirfd branch. open("/abs", O_ALT) and > openat(AT_FDCWD, "rel", O_ALT) fall straight through and open the real > file. Good point. It should either reject the non fd case (including plain open(2) or use cwd as the base. I'm leaning towards the former. > The layer symlinks are an unchecked jump into internal, out-of-namespace > mounts. Following mount/options/upperdir/0 ends in nd_jump_link(). That > doesn't do permission checks and no mnt_ns check. It should do the permission check, will fix. It's jumping into an anonymous mount, why should it check mnt_ns? > The overlay upper/lower layers are internal mounts with no mountpoint in > any namespace. So any unprivileged process that can open the overlay > root gets an O_PATH handle to a layer root it has no search-permission > path to and on a mount outside its namespace and can then use it as a > dirfd. It's just missing the permission check in metafs_path_get_link(). > Pulling the mount out is gated by CAP_SYS_ADMIN, but the handle and the > traversal are not. That's exactly Andy's "whose creds" question, it's > inherent to the design, and the procfs variant has it too. Maybe I'm not getting something. Why would the metafs based design be more limited in how it checks permission and with which creds than any other interface? > > get_options() forks ->show_options() permanently. It's added > alongside the existing method and mnt_show_options() now tries one then > the other. > > That leaves the tree split forever or commits us to a flag-day > conversion of everything. The new thig is also heavie and loses the > seq_file overflow-retry. All so the 99% of filesystems that will never > expose a path option can carry a more complicated interface. This is something that would be useful anyway. Would prevent the back and forth escaping for statmount. I can do a full conversion patch either with coccinelle or claude in a couple hours. And nothing is lost, the layer above seq_file is just an abstract class, could make it call exactly the same seq_file ops as before. > mnt_devname to s_devname move is a behavioral change. alloc_super() > records fc->source only on the sb-create path in sget_fc(). Sharing an > existing sb path never updates it. For filesystems that share a > superblock across mounts every mount now reports the first mounter's > source in /proc/self/mountinfo and statmount(). Well, drat. Doesn't really matter, though. > And the smaller details really show how inelegant this model is and why > Al and I have opposed it multiple times before. Scalar options are > modeled as symlinks and non-path options are dangling links whose > readlink still returns the value while for path options readlink returns > the option string but following the link jumps somewhere else entirely. Hey, this is was a prototype to show that this can do what Giuseppe needs. Not with nicely polished interfaces. Completely agree with you, that's a shitty interface that I've done in there. Not proud of it. But that's completely besides the point. The point is that it would allow a lot of things to be done more elegantly, without adding special interfaces all over the place. > But a generic metadata interface layered on top of yet another magic > filesystem whether it's mounted or hidden behind procfs I feel very > strongly about. I also have great difficulty seeing Al supporting it. Me too, as you have obviously noted. I'm going offline for two weeks starting this weekend, so no lengthy and heated discussions for a while. But I'll be back! HA HA HA ha Thanks, Miklos ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-23 8:00 ` Miklos Szeredi @ 2026-07-23 9:20 ` Amir Goldstein 2026-07-26 17:02 ` Giuseppe Scrivano 0 siblings, 1 reply; 16+ messages in thread From: Amir Goldstein @ 2026-07-23 9:20 UTC (permalink / raw) To: Miklos Szeredi Cc: Christian Brauner, Miklos Szeredi, Giuseppe Scrivano, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Gao Xiang > > And the smaller details really show how inelegant this model is and why > > Al and I have opposed it multiple times before. Scalar options are > > modeled as symlinks and non-path options are dangling links whose > > readlink still returns the value while for path options readlink returns > > the option string but following the link jumps somewhere else entirely. > > Hey, this is was a prototype to show that this can do what Giuseppe > needs. Not with nicely polished interfaces. > I want to do a sidebar regarding "what Giuseppe needs". As I wrote I am all for introspection of overlayfs layers, but I was never fully convinced that getting an open fd was the correct API. The starting point of the use case is a userspace daemon that wants to reuse mounted erofs images, which are used as lower layers. When considering a single daemon (e.g. composefs) this reuse would be better done completely in userspace without kernel involvement. The reason for a need for kernel UAPI for introspection of layers was to allow cross daemon optimizations, so that containerd could reuse erofs images mounted by composefs. This optimization works under the assumption that the image file or the image file hash is a unique identifier of the desired lower layer. But what if composefs mounted the image with idmapping or some other property? And how many container runtimes are there out there? Is it really worth the elaborate effort to provide introspection to this level? My intuition is that introspection should provide static information like uuid/fsid/fhandle rather than an open fd. I am willing to be convinced otherwise with the proper arguments. Thanks, Amir. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [RFC PATCH] fs: allow opening overlayfs/erofs layers through O_ALT 2026-07-23 9:20 ` Amir Goldstein @ 2026-07-26 17:02 ` Giuseppe Scrivano 0 siblings, 0 replies; 16+ messages in thread From: Giuseppe Scrivano @ 2026-07-26 17:02 UTC (permalink / raw) To: Amir Goldstein Cc: Miklos Szeredi, Christian Brauner, Miklos Szeredi, linux-fsdevel, linux-unionfs, linux-api, linux-erofs, Gao Xiang Amir Goldstein <amir73il@gmail.com> writes: >> > And the smaller details really show how inelegant this model is and why >> > Al and I have opposed it multiple times before. Scalar options are >> > modeled as symlinks and non-path options are dangling links whose >> > readlink still returns the value while for path options readlink returns >> > the option string but following the link jumps somewhere else entirely. >> >> Hey, this is was a prototype to show that this can do what Giuseppe >> needs. Not with nicely polished interfaces. >> > > I want to do a sidebar regarding "what Giuseppe needs". > > As I wrote I am all for introspection of overlayfs layers, but I was never > fully convinced that getting an open fd was the correct API. > > The starting point of the use case is a userspace daemon that wants > to reuse mounted erofs images, which are used as lower layers. > > When considering a single daemon (e.g. composefs) this reuse > would be better done completely in userspace without kernel involvement. > > The reason for a need for kernel UAPI for introspection of layers was > to allow cross daemon optimizations, so that containerd could reuse > erofs images mounted by composefs. > > This optimization works under the assumption that the image file > or the image file hash is a unique identifier of the desired lower layer. > > But what if composefs mounted the image with idmapping or some other > property? > > And how many container runtimes are there out there? > Is it really worth the elaborate effort to provide introspection > to this level? > > My intuition is that introspection should provide static information > like uuid/fsid/fhandle rather than an open fd. > > I am willing to be convinced otherwise with the proper arguments. > > Thanks, > Amir. My end goal is to retrieve the EROFS mount itself that was used as an overlay lower layer so that it can be reused across multiple overlay mounts. I'd like to do it without requiring a daemon to keep the file descriptor open. If the daemon crashes or is terminated for any reason, it can't retrieve this information from the kernel anymore, and the daemon must also worry about closing the file descriptor when it's no longer used by any mount. It seems like an unnecessary duplication of state given the kernel already knowns about that. How can the uuid/fsid/fhandle alone be used to solve this? The overlay mounts are private mounts that cannot be accessed anymore, even if you know their handle. In fact, before posting these patches I thought about extending open_by_handle_at to work with mounts, in addition to namespaces and pidfds. Christian didn't agree with this idea and suggested the introspection API for overlay. IMO, this functionality would solve the problem in a very elegant way, because we could store the handle for the EROFS mount in a file and try to reuse it later. On ESTALE, the mount is recreated. This would need only some locking, without the daemon duplicating the kernel state. Regards, Giuseppe ^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-07-26 17:02 UTC | newest] Thread overview: 16+ 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 2026-07-19 14:12 ` Andy Lutomirski 2026-07-19 18:42 ` Miklos Szeredi 2026-07-20 9:22 ` Miklos Szeredi 2026-07-22 14:25 ` Christian Brauner 2026-07-22 14:51 ` Miklos Szeredi 2026-07-22 15:44 ` Christian Brauner 2026-07-22 16:29 ` Andy Lutomirski 2026-07-23 8:22 ` Miklos Szeredi 2026-07-23 9:00 ` Andy Lutomirski 2026-07-23 9:19 ` Miklos Szeredi 2026-07-23 13:53 ` Andy Lutomirski 2026-07-23 8:00 ` Miklos Szeredi 2026-07-23 9:20 ` Amir Goldstein 2026-07-26 17:02 ` Giuseppe Scrivano
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.