[PATCH] fuse: Expose read from directory to userspace

From: Emily Maier
Date: Fri Apr 22 2016 - 09:09:43 EST


The VFS permits filesystems to implement read(2) on directories, but
FUSE unconditionally returns -EISDIR for it. Expose this to userspace as
FUSE_DIR_READ and continue returning the old behavior if the filesystem
doesn't implement it.

Signed-off-by: Emily Maier <emily@xxxxxxxxxxxxxx>
---
fs/fuse/dir.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++-
fs/fuse/fuse_i.h | 6 ++++++
include/uapi/linux/fuse.h | 18 ++++++++++++++++-
3 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 4b855b6..dbae010 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1870,6 +1870,55 @@ static int fuse_removexattr(struct dentry *entry, const char *name)
return err;
}

+static ssize_t fuse_dir_read(struct file *file, char __user *buf, size_t size,
+ loff_t *ppos)
+{
+ struct inode *inode = file_inode(file);
+ struct fuse_conn *fc = get_fuse_conn(inode);
+ struct fuse_dir_read_in inarg;
+ ssize_t ret;
+ struct fuse_file *ff = file->private_data;
+ char *kbuf;
+ FUSE_ARGS(args);
+
+ if (fc->no_dir_read || fc->minor < 25)
+ return -EISDIR;
+
+ if (size > FUSE_DIR_READ_MAX)
+ size = FUSE_DIR_READ_MAX;
+ kbuf = kmalloc(size, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.size = size;
+ inarg.off = *ppos;
+ inarg.fh = ff->fh;
+ args.in.h.opcode = FUSE_DIR_READ;
+ args.in.h.nodeid = get_node_id(inode);
+ args.in.numargs = 1;
+ args.in.args[0].size = sizeof(inarg);
+ args.in.args[0].value = &inarg;
+ args.out.numargs = 1;
+ args.out.argvar = 1;
+ args.out.args[0].size = size;
+ args.out.args[0].value = kbuf;
+ ret = fuse_simple_request(fc, &args);
+ if (ret == -ENOSYS) {
+ fc->no_dir_read = 1;
+ ret = -EISDIR;
+ }
+ if (ret > 0) {
+ if (copy_to_user(buf, kbuf, ret))
+ ret = -EFAULT;
+ else
+ *ppos += ret;
+ }
+ fuse_invalidate_atime(inode);
+ kfree(kbuf);
+ return ret;
+}
+
static const struct inode_operations fuse_dir_inode_operations = {
.lookup = fuse_lookup,
.mkdir = fuse_mkdir,
@@ -1892,7 +1941,7 @@ static const struct inode_operations fuse_dir_inode_operations = {

static const struct file_operations fuse_dir_operations = {
.llseek = generic_file_llseek,
- .read = generic_read_dir,
+ .read = fuse_dir_read,
.iterate = fuse_readdir,
.open = fuse_dir_open,
.release = fuse_dir_release,
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index eddbe02..a953910 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -48,6 +48,9 @@
/** Number of page pointers embedded in fuse_req */
#define FUSE_REQ_INLINE_PAGES 1

+/** Maximum length of a read from a directory */
+#define FUSE_DIR_READ_MAX 4096
+
/** List of active connections */
extern struct list_head fuse_conn_list;

@@ -658,6 +661,9 @@ struct fuse_conn {

/** List of device instances belonging to this connection */
struct list_head devices;
+
+ /** Is dir_read not implemented by fs? */
+ unsigned no_dir_read:1;
};

static inline struct fuse_conn *get_fuse_conn_super(struct super_block *sb)
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 5974fae..50f5fa5 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -105,6 +105,9 @@
*
* 7.24
* - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+ *
+ * 7.25
+ * - add FUSE_DIR_READ
*/

#ifndef _LINUX_FUSE_H
@@ -140,7 +143,7 @@
#define FUSE_KERNEL_VERSION 7

/** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 24
+#define FUSE_KERNEL_MINOR_VERSION 25

/** The node ID of the root inode */
#define FUSE_ROOT_ID 1
@@ -362,6 +365,7 @@ enum fuse_opcode {
FUSE_READDIRPLUS = 44,
FUSE_RENAME2 = 45,
FUSE_LSEEK = 46,
+ FUSE_DIR_READ = 47,

/* CUSE specific operations */
CUSE_INIT = 4096,
@@ -773,4 +777,16 @@ struct fuse_lseek_out {
uint64_t offset;
};

+struct fuse_dir_read_in {
+ uint32_t size;
+ uint32_t padding;
+ int64_t off;
+ uint64_t fh;
+};
+
+struct fuse_dir_read_out {
+ uint32_t size;
+ uint32_t padding;
+};
+
#endif /* _LINUX_FUSE_H */
--
2.5.5