Thread (20 messages) 20 messages, 4 authors, 3d ago

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.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.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.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help