Implement the FUSE passthrough ioctl that associates a backing (passthrough) file with the fuse_file. The file descriptor passed to the ioctl by the FUSE daemon is used to access the relative file pointer, that will be copied to the fuse_file data structure to consolidate the link between the FUSE and the backing file. To enable the passthrough mode, FUSE daemon calls the FUSE_DEV_IOC_PASSTHROUGH_OPEN ioctl and, if the call succeeds, receives back an identifier that will be used at open/create response time in the fuse_open_out field to associate the FUSE file to the backing file. The value returned by the ioctl to user space can be: > 0: success, the value can be used as part of an open/create reply. <= 0: an error occurred. == 0: represents an error to preserve backward compatibility - the fuse_open_out field that is used to pass the backing_id back to the kernel uses the same bits that were previously as struct padding, and is commonly zero-initialized (e.g., in the libfuse implementation). Removing 0 from the correct values fixes the ambiguity between the case in which 0 corresponds to a real backing_id, a missing implementation of FUSE passthrough or a request for a normal FUSE file, simplifying the user space implementation. For the passthrough mode to be successfully activated, the backing file filesystem must implement both read_iter and write_iter file operations. This extra check avoids special pseudo files to be used for passhrough. Passthrough comes with another limitation: no further filesystem stacking is allowed for those FUSE filesystems using passthrough. The FUSE daemon must call FUSE_DEV_IOC_PASSTHROUGH_CLOSE ioctl to drop the reference on the backing file. This can be done immediattely after responding to open or at a later time. In any case, the backing file will be kept open by the FUSE driver until the last fuse_file that was setup to passthrough to that backing file is closed AND the FUSE_DEV_IOC_PASSTHROUGH_CLOSE ioctl was called. Signed-off-by: Alessio Balsini <balsini@xxxxxxxxxxx> Signed-off-by: Amir Goldstein <amir73il@xxxxxxxxx> --- fs/fuse/fuse_i.h | 1 + fs/fuse/inode.c | 1 + fs/fuse/passthrough.c | 89 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index f52604534ff6..9ad1cc37a5c4 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -181,6 +181,7 @@ struct fuse_passthrough { /** refcount */ refcount_t count; + struct rcu_head rcu; }; /** FUSE specific file data */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 271586fac008..4200fb13883e 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1301,6 +1301,7 @@ static int fuse_passthrough_id_free(int id, void *p, void *data) WARN_ON_ONCE(refcount_read(&passthrough->count) != 1); fuse_passthrough_free(passthrough); + return 0; } diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c index fc723e004de9..8d090ae252f2 100644 --- a/fs/fuse/passthrough.c +++ b/fs/fuse/passthrough.c @@ -3,6 +3,7 @@ #include "fuse_i.h" #include <linux/file.h> +#include <linux/idr.h> /* * Returns passthrough_fh id that can be passed with FOPEN_PASSTHROUGH @@ -10,18 +11,98 @@ */ int fuse_passthrough_open(struct fuse_conn *fc, int backing_fd) { - return -EINVAL; + struct file *passthrough_filp; + struct inode *passthrough_inode; + struct super_block *passthrough_sb; + struct fuse_passthrough *passthrough; + int res; + + if (!fc->passthrough) + return -EPERM; + + passthrough_filp = fget(backing_fd); + if (!passthrough_filp) + return -EBADF; + + res = -EOPNOTSUPP; + if (!passthrough_filp->f_op->read_iter || + !passthrough_filp->f_op->write_iter) + goto out_fput; + + passthrough_inode = file_inode(passthrough_filp); + passthrough_sb = passthrough_inode->i_sb; + res = -ELOOP; + if (passthrough_sb->s_stack_depth >= FILESYSTEM_MAX_STACK_DEPTH) + goto out_fput; + + passthrough = kmalloc(sizeof(struct fuse_passthrough), GFP_KERNEL); + res = -ENOMEM; + if (!passthrough) + goto out_fput; + + passthrough->filp = passthrough_filp; + refcount_set(&passthrough->count, 1); + + idr_preload(GFP_KERNEL); + spin_lock(&fc->lock); + res = idr_alloc_cyclic(&fc->passthrough_files_map, passthrough, 1, 0, + GFP_ATOMIC); + spin_unlock(&fc->lock); + idr_preload_end(); + + if (res < 0) + fuse_passthrough_free(passthrough); + + return res; + +out_fput: + fput(passthrough_filp); + + return res; } int fuse_passthrough_close(struct fuse_conn *fc, int passthrough_fh) { - return -EINVAL; + struct fuse_passthrough *passthrough; + + if (!fc->passthrough) + return -EPERM; + + if (passthrough_fh <= 0) + return -EINVAL; + + spin_lock(&fc->lock); + passthrough = idr_remove(&fc->passthrough_files_map, passthrough_fh); + spin_unlock(&fc->lock); + + if (!passthrough) + return -ENOENT; + + fuse_passthrough_put(passthrough); + + return 0; } int fuse_passthrough_setup(struct fuse_conn *fc, struct fuse_file *ff, struct fuse_open_out *openarg) { - return -EINVAL; + int passthrough_fh = openarg->passthrough_fh; + struct fuse_passthrough *passthrough; + + if (passthrough_fh <= 0) + return -EINVAL; + + rcu_read_lock(); + passthrough = idr_find(&fc->passthrough_files_map, passthrough_fh); + if (passthrough && !refcount_inc_not_zero(&passthrough->count)) + passthrough = NULL; + rcu_read_unlock(); + if (!passthrough) + return -ENOENT; + + ff->passthrough = passthrough; + + return 0; } void fuse_passthrough_put(struct fuse_passthrough *passthrough) @@ -36,5 +117,5 @@ void fuse_passthrough_free(struct fuse_passthrough *passthrough) fput(passthrough->filp); passthrough->filp = NULL; } - kfree(passthrough); + kfree_rcu(passthrough, rcu); } -- 2.34.1