Re: [PATCH] ovl: add ioctls to retrieve layer file descriptors
From: Amir Goldstein <amir73il@gmail.com>
Date: 2026-07-08 10:40:49
Also in:
linux-fsdevel, linux-unionfs
On Wed, Jul 8, 2026 at 12:00 PM Giuseppe Scrivano [off-list ref] wrote:
quoted hunk ↗ jump to hunk
Add two ioctls to overlay filesystem to allow userspace to retrieve information about the overlay layers: OVL_IOC_OPEN_LAYER: return an O_PATH fd to the root of a layer. arg == 0 returns the upper layer (-ENOENT if no upper is configured), arg >= 1 returns lower layers (-ENOENT if index is out of range). OVL_IOC_GET_LAYERS_INFO: copy a struct ovl_layers_info to userspace with numlower, numlowerdata, and has_upper. The ioctls work on any overlayfs file or directory and require CAP_SYS_ADMIN in the mounter's user namespace. The UAPI constants and struct are defined in include/uapi/linux/overlay.h. Signed-off-by: Giuseppe Scrivano <redacted> --- MAINTAINERS | 1 + fs/overlayfs/file.c | 2 ++ fs/overlayfs/overlayfs.h | 4 +++ fs/overlayfs/ovl_entry.h | 2 ++ fs/overlayfs/params.c | 10 ++++++ fs/overlayfs/params.h | 1 + fs/overlayfs/readdir.c | 2 ++ fs/overlayfs/super.c | 70 ++++++++++++++++++++++++++++++++++++ include/uapi/linux/overlay.h | 30 ++++++++++++++++ 9 files changed, 122 insertions(+) create mode 100644 include/uapi/linux/overlay.hdiff --git a/MAINTAINERS b/MAINTAINERS index 25453040dffb..b64c696686e4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS@@ -20368,6 +20368,7 @@ S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.git F: Documentation/filesystems/overlayfs.rst F: fs/overlayfs/ +F: include/uapi/linux/overlay.h
overlayfs.h please
quoted hunk ↗ jump to hunk
P54 WIRELESS DRIVER M: Christian Lamparter [off-list ref]diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index 27cc07738f33..fc9c448c5959 100644 --- a/fs/overlayfs/file.c +++ b/fs/overlayfs/file.c@@ -649,4 +649,6 @@ const struct file_operations ovl_file_operations = { .copy_file_range = ovl_copy_file_range, .remap_file_range = ovl_remap_file_range, .setlease = generic_setlease, + .unlocked_ioctl = ovl_ioctl, + .compat_ioctl = compat_ptr_ioctl, };diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index b75df37f70ac..10f7ef8cb78c 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h@@ -8,6 +8,7 @@ #include <linux/uuid.h> #include <linux/fs.h> #include <linux/fsverity.h> +#include <uapi/linux/overlay.h> #include <linux/namei.h> #include <linux/posix_acl.h> #include <linux/posix_acl_xattr.h>@@ -908,6 +909,9 @@ void ovl_tempname(char name[OVL_TEMPNAME_SIZE]); struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir, struct ovl_cattr *attr); +/* super.c */ +long ovl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); + /* file.c */ extern const struct file_operations ovl_file_operations; int ovl_real_fileattr_get(const struct path *realpath, struct file_kattr *fa);diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 80cad4ea96a3..b8f4bca89a27 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h@@ -35,6 +35,8 @@ struct ovl_layer { struct vfsmount *mnt; /* Trap in ovl inode cache */ struct inode *trap; + /* Keeps the original fsmount file alive for OVL_IOC_OPEN_LAYER */ + struct file *origin;
as Miklos wrote, keep the file is an overkill
quoted hunk ↗ jump to hunk
struct ovl_sb *fs; /* Index of this layer in fs root (upper idx == 0) */ int idx;diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c index c93fcaa45d4a..92d1a56178f2 100644 --- a/fs/overlayfs/params.c +++ b/fs/overlayfs/params.c@@ -482,6 +482,11 @@ static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param, return PTR_ERR(layer_name); err = ovl_do_parse_layer(fc, layer_name, &layer_path, layer); + if (!err && !is_upper_layer(layer)) { + struct ovl_fs_context *ctx = fc->fs_private; + + ctx->lower[ctx->nr - 1].origin = get_file(param->file); + } break; } default:@@ -504,6 +509,9 @@ static void ovl_reset_lowerdirs(struct ovl_fs_context *ctx) path_put(&l->path); kfree(l->name); l->name = NULL; + if (l->origin) + fput(l->origin); + l->origin = NULL; } ctx->nr = 0; ctx->nr_data = 0;@@ -856,6 +864,8 @@ void ovl_free_fs(struct ovl_fs *ofs) mounts = (struct vfsmount **) ofs->config.lowerdirs; for (i = 0; i < ofs->numlayer; i++) { iput(ofs->layers[i].trap); + if (ofs->layers[i].origin) + fput(ofs->layers[i].origin); kfree(ofs->config.lowerdirs[i]); mounts[i] = ofs->layers[i].mnt; }diff --git a/fs/overlayfs/params.h b/fs/overlayfs/params.h index ffd53cdd8482..1d8fe8fbaca2 100644 --- a/fs/overlayfs/params.h +++ b/fs/overlayfs/params.h@@ -22,6 +22,7 @@ struct ovl_opt_set { struct ovl_fs_context_layer { char *name; struct path path; + struct file *origin; }; struct ovl_fs_context {diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index e7fe29cb6028..7bab71c8bcc2 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c@@ -1069,6 +1069,8 @@ const struct file_operations ovl_dir_operations = { .iterate_shared = shared_ovl_iterate, .llseek = ovl_dir_llseek, .fsync = ovl_dir_fsync, + .unlocked_ioctl = ovl_ioctl, + .compat_ioctl = compat_ptr_ioctl, .release = ovl_dir_release, .setlease = generic_setlease, };diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 60f0b7ceef0a..d143002e74b2 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c@@ -1107,6 +1107,8 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, */ mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME; + layers[ofs->numlayer].origin = l->origin; + l->origin = NULL; layers[ofs->numlayer].trap = trap; layers[ofs->numlayer].mnt = mnt; layers[ofs->numlayer].idx = ofs->numlayer;@@ -1568,6 +1570,74 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc) return err; } +static long ovl_ioctl_open_layer(struct file *filp, unsigned long arg) +{ + struct super_block *sb = file_inode(filp)->i_sb; + struct ovl_fs *ofs = OVL_FS(sb); + struct path root; + struct file *f; + int fd; + + if (arg >= ofs->numlayer) + return -ENOENT; + if (arg == 0 && !ovl_upper_mnt(ofs)) + return -ENOENT; + if (!ofs->layers[arg].origin) + return -EOPNOTSUPP; + + root.mnt = mntget(ofs->layers[arg].origin->f_path.mnt); + root.dentry = dget(root.mnt->mnt_root); + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) { + path_put(&root); + return fd; + } + + f = dentry_open(&root, O_PATH | O_NOFOLLOW, current_cred()); + path_put(&root); + if (IS_ERR(f)) { + put_unused_fd(fd); + return PTR_ERR(f); + } + + fd_install(fd, f); + return fd; +} + +static long ovl_ioctl_get_layers_info(struct file *filp, unsigned long arg) +{ + struct super_block *sb = file_inode(filp)->i_sb; + struct ovl_fs *ofs = OVL_FS(sb); + struct ovl_layers_info info = { + .numlower = ofs->numlayer - 1, + .numlowerdata = ofs->numdatalayer, + .has_upper = !!ovl_upper_mnt(ofs), + }; + + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +long ovl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct ovl_fs *ofs = OVL_FS(file_inode(filp)->i_sb); + + if (!ns_capable(ofs->creator_cred->user_ns, CAP_SYS_ADMIN)) + return -EPERM; + + switch (cmd) { + case OVL_IOC_OPEN_LAYER: + return ovl_ioctl_open_layer(filp, arg); + case OVL_IOC_GET_LAYERS_INFO: + return ovl_ioctl_get_layers_info(filp, arg); + default: + return -ENOTTY; + } +} + struct file_system_type ovl_fs_type = { .owner = THIS_MODULE, .name = "overlay",diff --git a/include/uapi/linux/overlay.h b/include/uapi/linux/overlay.h new file mode 100644 index 000000000000..c92ccecd9e21 --- /dev/null +++ b/include/uapi/linux/overlay.h
overlayfs.h please
quoted hunk ↗ jump to hunk
@@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_OVERLAY_H +#define _UAPI_LINUX_OVERLAY_H + +#include <linux/ioctl.h> +#include <linux/types.h> + +/** + * struct ovl_layers_info - overlay layer configuration summary + * @numlower: number of lower (metadata) layers + * @numlowerdata: number of data-only lower layers + * @has_upper: 1 if an upper layer is configured, 0 otherwise + */ +struct ovl_layers_info { + __u32 numlower; + __u32 numlowerdata; + __u32 has_upper; +};
Whether this stays as ioctl or statmount blob please use: __u32 flags; __u32 pad; and use a flag for has_upper so we can extend this data struct in the future. Thanks, Amir.