Re: [PATCH] ovl: add ioctls to retrieve layer file descriptors
Amir Goldstein <[email protected]> Wed, 8 Jul 2026 12:40:31 +0200
| Newsgroups | org.kernel.vger.linux-unionfs,org.kernel.vger.linux-api,org.kernel.vger.linux-fsdevel |
|---|---|
| Message-ID | <CAOQ4uxjC2JZ94r3dxJzHFFpder66biPxN-2Yy0ZeqDSyNooaPA@mail.gmail.com> |
On Wed, Jul 8, 2026 at 12:00=E2=80=AFPM Giuseppe Scrivano <gscrivan@redhat.= com> wrote: > > 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 =3D=3D 0 returns the upper layer (-ENOENT if > no upper is configured), arg >=3D 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 <[email protected]> > --- > 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.h > > diff --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.g= it > F: Documentation/filesystems/overlayfs.rst > F: fs/overlayfs/ > +F: include/uapi/linux/overlay.h overlayfs.h please > > P54 WIRELESS DRIVER > M: Christian Lamparter <[email protected]> > 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 =3D = { > .copy_file_range =3D ovl_copy_file_range, > .remap_file_range =3D ovl_remap_file_range, > .setlease =3D generic_setlease, > + .unlocked_ioctl =3D ovl_ioctl, > + .compat_ioctl =3D 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 *workdi= r, > 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 > struct ovl_sb *fs; > /* Index of this layer in fs root (upper idx =3D=3D 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, st= ruct fs_parameter *param, > return PTR_ERR(layer_name); > > err =3D ovl_do_parse_layer(fc, layer_name, &layer_path, l= ayer); > + if (!err && !is_upper_layer(layer)) { > + struct ovl_fs_context *ctx =3D fc->fs_private; > + > + ctx->lower[ctx->nr - 1].origin =3D 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 =3D NULL; > + if (l->origin) > + fput(l->origin); > + l->origin =3D NULL; > } > ctx->nr =3D 0; > ctx->nr_data =3D 0; > @@ -856,6 +864,8 @@ void ovl_free_fs(struct ovl_fs *ofs) > mounts =3D (struct vfsmount **) ofs->config.lowerdirs; > for (i =3D 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] =3D 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 =3D= { > .iterate_shared =3D shared_ovl_iterate, > .llseek =3D ovl_dir_llseek, > .fsync =3D ovl_dir_fsync, > + .unlocked_ioctl =3D ovl_ioctl, > + .compat_ioctl =3D compat_ptr_ioctl, > .release =3D ovl_dir_release, > .setlease =3D 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, s= truct ovl_fs *ofs, > */ > mnt->mnt_flags |=3D MNT_READONLY | MNT_NOATIME; > > + layers[ofs->numlayer].origin =3D l->origin; > + l->origin =3D NULL; > layers[ofs->numlayer].trap =3D trap; > layers[ofs->numlayer].mnt =3D mnt; > layers[ofs->numlayer].idx =3D 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 =3D file_inode(filp)->i_sb; > + struct ovl_fs *ofs =3D OVL_FS(sb); > + struct path root; > + struct file *f; > + int fd; > + > + if (arg >=3D ofs->numlayer) > + return -ENOENT; > + if (arg =3D=3D 0 && !ovl_upper_mnt(ofs)) > + return -ENOENT; > + if (!ofs->layers[arg].origin) > + return -EOPNOTSUPP; > + > + root.mnt =3D mntget(ofs->layers[arg].origin->f_path.mnt); > + root.dentry =3D dget(root.mnt->mnt_root); > + > + fd =3D get_unused_fd_flags(O_CLOEXEC); > + if (fd < 0) { > + path_put(&root); > + return fd; > + } > + > + f =3D 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 a= rg) > +{ > + struct super_block *sb =3D file_inode(filp)->i_sb; > + struct ovl_fs *ofs =3D OVL_FS(sb); > + struct ovl_layers_info info =3D { > + .numlower =3D ofs->numlayer - 1, > + .numlowerdata =3D ofs->numdatalayer, > + .has_upper =3D !!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 =3D 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 =3D { > .owner =3D THIS_MODULE, > .name =3D "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 > @@ -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 futur= e. Thanks, Amir.