[RFC PATCH] overlayfs: add OVL_IOC_GETINFOFD ioctl that opens ovlinfofd

From: Alexander Mikhalitsyn
Date: Mon Oct 05 2020 - 13:03:34 EST


Second variant of possible interface to get source-dirs fhandles from
userspace. OVL_IOC_GETINFOFD ioctls opens special [ovlinfofd] descriptor
which is really just seq_file. When read from this seq_file we will get
something like this:
===
numlower: 2
L fhandle-bytes:c fhandle-type:1 f_handle:9685a2160200000000000000
L fhandle-bytes:c fhandle-type:1 f_handle:c74cd5c10300000000000000
U fhandle-bytes:c fhandle-type:1 f_handle:e45842640400000000000000
W fhandle-bytes:c fhandle-type:1 f_handle:d393374d0500000000000000
===

Cc: Amir Goldstein <amir73il@xxxxxxxxx>
Cc: Andrei Vagin <avagin@xxxxxxxxx>
Cc: Pavel Tikhomirov <ptikhomirov@xxxxxxxxxxxxx>
Cc: Miklos Szeredi <miklos@xxxxxxxxxx>
Cc: David Howells <dhowells@xxxxxxxxxx>
Cc: linux-unionfs@xxxxxxxxxxxxxxx
Cc: linux-fsdevel@xxxxxxxxxxxxxxx
Cc: linux-kernel@xxxxxxxxxxxxxxx
Signed-off-by: Alexander Mikhalitsyn <alexander.mikhalitsyn@xxxxxxxxxxxxx>
---
fs/overlayfs/readdir.c | 171 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 171 insertions(+)

diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index 12ee043d2b3a..60c3c47a6b3e 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -14,6 +14,9 @@
#include <linux/cred.h>
#include <linux/ratelimit.h>
#include <linux/exportfs.h>
+#include <linux/anon_inodes.h>
+#include <linux/seq_file.h>
+#include <linux/syscalls.h>
#include "overlayfs.h"

struct ovl_cache_entry {
@@ -1067,11 +1070,175 @@ static long ovl_ioctl_get_work_fhandle(struct super_block *sb,
return __ovl_ioctl_get_fhandle(ofs->workbasedir, arg);
}

+static int ovlinfofd_release(struct inode *inode, struct file *file)
+{
+ printk("ovlinfofd_release\n");
+ return single_release(inode, file);
+}
+
+#ifdef CONFIG_PROC_FS
+static void ovlinfofd_show_fdinfo(struct seq_file *m, struct file *f)
+{
+ /* TODO */
+}
+#endif
+
+static const struct file_operations ovlinfofd_fops = {
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PROC_FS
+ .show_fdinfo = ovlinfofd_show_fdinfo,
+#endif
+ .release = ovlinfofd_release,
+ .read = seq_read,
+ .llseek = seq_lseek,
+};
+
+static long __ovl_ioctl_show_dentry_fhandle(struct seq_file *s,
+ const char *prefix,
+ struct dentry *origin)
+{
+ struct ovl_mnt_opt_fh *fh;
+ int ret = 0, i;
+
+ fh = __ovl_encode_mnt_opt_fh(origin);
+ if (IS_ERR(fh))
+ return PTR_ERR(fh);
+
+ seq_printf(s, "%s fhandle-bytes:%x fhandle-type:%x f_handle:",
+ prefix, fh->fh.handle_bytes, fh->fh.handle_type);
+
+ for (i = 0; i < fh->fh.handle_bytes; i++)
+ seq_printf(s, "%02x", (int)fh->fh.f_handle[i]);
+
+ seq_putc(s, '\n');
+
+ kfree(fh);
+ return ret;
+}
+
+static long ovl_ioctl_show_lower_fhandle(struct seq_file *s,
+ unsigned long arg)
+{
+ struct super_block *sb = s->private;
+ struct ovl_entry *oe = sb->s_root->d_fsdata;
+ struct dentry *origin;
+
+ if (arg >= oe->numlower)
+ return -EINVAL;
+
+ origin = oe->lowerstack[arg].dentry;
+
+ return __ovl_ioctl_show_dentry_fhandle(s, "L", origin);
+}
+
+static long ovl_ioctl_show_upper_fhandle(struct seq_file *s)
+{
+ struct super_block *sb = s->private;
+ struct ovl_fs *ofs = sb->s_fs_info;
+ struct dentry *origin;
+
+ if (!ofs->config.upperdir)
+ return -EINVAL;
+
+ origin = OVL_I(d_inode(sb->s_root))->__upperdentry;
+
+ return __ovl_ioctl_show_dentry_fhandle(s, "U", origin);
+}
+
+static long ovl_ioctl_show_work_fhandle(struct seq_file *s)
+{
+ struct super_block *sb = s->private;
+ struct ovl_fs *ofs = sb->s_fs_info;
+
+ if (!ofs->config.upperdir)
+ return -EINVAL;
+
+ return __ovl_ioctl_show_dentry_fhandle(s, "W", ofs->workbasedir);
+}
+
+static int ovlinfofd_show(struct seq_file *s, void *unused)
+{
+ struct super_block *sb = s->private;
+ struct ovl_entry *oe = sb->s_root->d_fsdata;
+ int i;
+
+ printk("ovlinfofd_show\n");
+
+ seq_printf(s, "numlower: %d\n", oe->numlower);
+
+ for (i = 0; i < oe->numlower; i++)
+ ovl_ioctl_show_lower_fhandle(s, i);
+ ovl_ioctl_show_upper_fhandle(s);
+ ovl_ioctl_show_work_fhandle(s);
+
+ return 0;
+}
+
+static long ovl_ioctl_get_info_fd(struct super_block *sb,
+ unsigned long arg)
+{
+ struct ovl_fs *ofs = sb->s_fs_info;
+ struct ovl_entry *oe = sb->s_root->d_fsdata;
+ int err, ufd, flags = arg;
+ struct fd f;
+
+ if (flags & ~(O_CLOEXEC))
+ return -EINVAL;
+
+ /* FIXME Comment taken from signalfd.c. Need to think about this.
+ * When we call this, the initialization must be complete, since
+ * anon_inode_getfd() will install the fd.
+ */
+ ufd = anon_inode_getfd("[ovlinfofd]", &ovlinfofd_fops, NULL,
+ O_RDONLY | (flags & (O_CLOEXEC)));
+ if (ufd < 0)
+ return ufd;
+
+ f = fdget(ufd);
+ if (!f.file) {
+ err = -EBADF;
+ goto err_close;
+ }
+
+ /*
+ * It's good to have some good guess of seq_file buffer size
+ * from start because if we will just use single_open() function
+ * then we will make several seq_file overflows and .show callback
+ * will be called several times. It's very bad for performance.
+ *
+ * Guess is very simple: we show fhandles as hex string. So,
+ * all that we need is take MAX_HANDLE_SZ * 2 and multiply by
+ * number of overlayfs mount source-dirs.
+ */
+ err = single_open_size(f.file, ovlinfofd_show, sb,
+ MAX_HANDLE_SZ * 2 *
+ (oe->numlower + 2 * !!ofs->config.upperdir));
+ if (err)
+ goto err_fdput;
+
+ /*
+ * We doing tricky things by combining anon_inode_getfd with seq_files,
+ * so, it's better to check that all fine with fops after single_open_size
+ * call.
+ */
+ WARN_ON(f.file->f_op != &ovlinfofd_fops);
+ fdput(f);
+
+ return ufd;
+
+err_fdput:
+ fdput(f);
+err_close:
+ ksys_close(ufd);
+ return err;
+}
+
#define OVL_IOC_GETLWRFHNDLSNUM _IO('o', 1)
// DISCUSS: what if MAX_HANDLE_SZ will change?
#define OVL_IOC_GETLWRFHNDL _IOR('o', 2, struct ovl_mnt_opt_fh)
#define OVL_IOC_GETUPPRFHNDL _IOR('o', 3, struct ovl_mnt_opt_fh)
#define OVL_IOC_GETWRKFHNDL _IOR('o', 4, struct ovl_mnt_opt_fh)
+#define OVL_IOC_GETINFOFD _IO('o', 5)

static long ovl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
@@ -1094,6 +1261,10 @@ static long ovl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
ret = ovl_ioctl_get_work_fhandle(file_inode(file)->i_sb, arg);
break;

+ case OVL_IOC_GETINFOFD:
+ ret = ovl_ioctl_get_info_fd(file_inode(file)->i_sb, arg);
+ break;
+
default:
ret = -ENOTTY;
}
--
2.25.1