diff mbox

fuse: Expose read from directory to userspace

Message ID 1461329404-7967-1-git-send-email-emily@emilymaier.net (mailing list archive)
State New, archived
Headers show

Commit Message

Emily Maier April 22, 2016, 12:50 p.m. UTC
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@emilymaier.net>
---
 fs/fuse/dir.c             | 51 ++++++++++++++++++++++++++++++++++++++++++++++-
 fs/fuse/fuse_i.h          |  6 ++++++
 include/uapi/linux/fuse.h | 18 ++++++++++++++++-
 3 files changed, 73 insertions(+), 2 deletions(-)

Comments

Al Viro April 22, 2016, 1:23 p.m. UTC | #1
On Fri, Apr 22, 2016 at 08:50:04AM -0400, Emily Maier wrote:
> 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.

What the hell for?  read() on directories had been an anachronism since
early 80s, has nothing resembling a sane semantics, etc.

Why would we want to implement that idiocy?
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Emily Maier April 24, 2016, 4:57 a.m. UTC | #2
On Fri, Apr 22, 2016 at 02:23:56PM +0100, Al Viro wrote:
> On Fri, Apr 22, 2016 at 08:50:04AM -0400, Emily Maier wrote:
> > 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.
> 
> What the hell for?  read() on directories had been an anachronism since
> early 80s, has nothing resembling a sane semantics, etc.
> 
> Why would we want to implement that idiocy?
> --
> To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

To permit getting metadata on a directory by reading from it, similarly 
to ceph's "dirstat" option.
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

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 */