Add support for server allocated 64-bit backing IDs alongside the existing kernel allocated 32-bit IDs.
Opened with FUSE_DEV_IOC_BACKING_OPEN, but the backing ID is sent via fuse_backing_map.backing_id, with FUSE_BACKING_ID_64 being set in .flags. Closed with FUSE_NOTIFY_BACKING_REMOVE. Since close only provides the backing ID, not the file descriptor, it doesn't have to be done with an ioctl. All 64 bit values are valid. Signed-off-by: Miklos Szeredi <[email protected]> --- fs/fuse/backing.c | 180 ++++++++++++++++++++++++++++---------- fs/fuse/dev.c | 21 +++++ fs/fuse/dev.h | 3 + fs/fuse/fuse_i.h | 21 ++++- fs/fuse/inode.c | 6 +- fs/fuse/notify.c | 28 ++++++ fs/fuse/passthrough.c | 3 + include/uapi/linux/fuse.h | 22 ++++- 8 files changed, 231 insertions(+), 53 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 433fa3098d71..3c879df7989c 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -9,6 +9,7 @@ #include "fuse_i.h" #include <linux/file.h> +#include <linux/rhashtable.h> static struct fuse_backing *fuse_backing_get(struct fuse_backing *fb) { @@ -48,6 +49,8 @@ static int fuse_backing_id_alloc(struct fuse_conn *fc, struct fuse_backing *fb) id = idr_alloc_cyclic(&fc->backing_files_map, fb, 1, 0, GFP_ATOMIC); spin_unlock(&fc->lock); idr_preload_end(); + if (id > 0) + fb->backing_id = id; WARN_ON_ONCE(id == 0); return id; @@ -61,81 +64,131 @@ static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc, spin_lock(&fc->lock); fb = idr_remove(&fc->backing_files_map, id); spin_unlock(&fc->lock); + if (fb) + fb->backing_id = 0; return fb; } -static int fuse_backing_id_free(int id, void *p, void *data) -{ - struct fuse_backing *fb = p; +static const struct rhashtable_params fuse_backing_prm = { + .head_offset = offsetof(struct fuse_backing, hash_node), + .key_offset = offsetof(struct fuse_backing, backing_id), + .key_len = sizeof_field(struct fuse_backing, backing_id), +}; - WARN_ON_ONCE(refcount_read(&fb->count) != 1); - fuse_backing_free(fb); - return 0; +static int fuse_backing_add_64(struct fuse_conn *fc, struct fuse_backing *fb) +{ + return rhashtable_insert_fast(&fc->backing_64_ht, &fb->hash_node, fuse_backing_prm); } -void fuse_backing_files_free(struct fuse_conn *fc) +int fuse_backing_close_64(struct fuse_conn *fc, u64 backing_id) { - idr_for_each(&fc->backing_files_map, fuse_backing_id_free, NULL); - idr_destroy(&fc->backing_files_map); + struct fuse_backing *fb; + int err; + + if (!fc->backing_id_64) + return -EINVAL; + + scoped_guard(spinlock, &fc->lock) { + fb = rhashtable_lookup_fast(&fc->backing_64_ht, &backing_id, fuse_backing_prm); + if (!fb) + return -ENOENT; + + err = rhashtable_remove_fast(&fc->backing_64_ht, &fb->hash_node, fuse_backing_prm); + WARN_ON(err); + } + fb->backing_id = 0; + fuse_backing_put(fb); + + return 0; } -int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map) +static struct fuse_backing *fuse_backing_new(struct fuse_conn *fc, int fd) { - struct file *file; + struct fuse_backing *fb; struct super_block *backing_sb; - struct fuse_backing *fb = NULL; - int res; - - pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags); + struct file *file; /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */ - res = -EPERM; if (!fc->passthrough || !capable(CAP_SYS_ADMIN)) - goto out; + return ERR_PTR(-EPERM); - res = -EINVAL; - if (map->flags || map->padding) - goto out; + CLASS(fd_raw, f)(fd); + if (fd_empty(f)) + return ERR_PTR(-EBADF); - file = fget_raw(map->fd); - res = -EBADF; - if (!file) - goto out; + file = fd_file(f); /* read/write/splice/mmap passthrough only relevant for regular files */ - res = d_is_dir(file->f_path.dentry) ? -EISDIR : -EINVAL; if (!d_is_reg(file->f_path.dentry)) - goto out_fput; + return d_is_dir(file->f_path.dentry) ? ERR_PTR(-EISDIR) : ERR_PTR(-EINVAL); backing_sb = file_inode(file)->i_sb; - res = -ELOOP; if (backing_sb->s_stack_depth >= fc->max_stack_depth) - goto out_fput; + return ERR_PTR(-ELOOP); fb = kmalloc_obj(struct fuse_backing); - res = -ENOMEM; if (!fb) - goto out_fput; + return ERR_PTR(-ENOMEM); - fb->file = file; + fb->file = get_file(file); fb->cred = get_current_cred(); refcount_set(&fb->count, 1); - res = fuse_backing_id_alloc(fc, fb); - if (res < 0) { - fuse_backing_free(fb); - fb = NULL; + return fb; +} + +int fuse_backing_open_64(struct fuse_conn *fc, struct fuse_backing_create_in *map) +{ + struct fuse_backing *fb; + int res; + + res = -EINVAL; + if (map->padding || map->spare[0] || map->spare[1]) + goto out; + + if (!fc->backing_id_64) + goto out; + + fb = fuse_backing_new(fc, map->fd); + res = PTR_ERR(fb); + if (!IS_ERR(fb)) { + fb->backing_id = map->backing_id; + res = fuse_backing_add_64(fc, fb); + if (res < 0) + fuse_backing_free(fb); } +out: + return res; +} + +int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map) +{ + struct fuse_backing *fb = NULL; + int res; + + pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags); + res = -EINVAL; + if (map->flags || map->padding) + goto out; + + if (fc->backing_id_64) + goto out; + + fb = fuse_backing_new(fc, map->fd); + res = PTR_ERR(fb); + if (!IS_ERR(fb)) { + res = fuse_backing_id_alloc(fc, fb); + if (res < 0) { + fuse_backing_free(fb); + fb = NULL; + } + } out: pr_debug("%s: fb=0x%p, ret=%i\n", __func__, fb, res); return res; - -out_fput: - fput(file); - goto out; } int fuse_backing_close(struct fuse_conn *fc, int backing_id) @@ -145,6 +198,9 @@ int fuse_backing_close(struct fuse_conn *fc, int backing_id) pr_debug("%s: backing_id=%d\n", __func__, backing_id); + if (fc->backing_id_64) + return -EINVAL; + /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */ err = -EPERM; if (!fc->passthrough || !capable(CAP_SYS_ADMIN)) @@ -167,14 +223,48 @@ int fuse_backing_close(struct fuse_conn *fc, int backing_id) return err; } -struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, int backing_id) +struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, u64 backing_id) { struct fuse_backing *fb; - rcu_read_lock(); - fb = idr_find(&fc->backing_files_map, backing_id); - fb = fuse_backing_get(fb); - rcu_read_unlock(); + guard(rcu)(); + if (!fc->backing_id_64) + fb = idr_find(&fc->backing_files_map, backing_id); + else + fb = rhashtable_lookup(&fc->backing_64_ht, &backing_id, fuse_backing_prm); - return fb; + return fuse_backing_get(fb); +} + +static void fuse_backing_check_free(struct fuse_backing *fb) +{ + WARN_ON_ONCE(refcount_read(&fb->count) != 1); + fuse_backing_free(fb); +} + +static int fuse_backing_idr_free(int id, void *p, void *data) +{ + fuse_backing_check_free(p); + return 0; +} + +static void fuse_backing_rht_free(void *p, void *data) +{ + fuse_backing_check_free(p); +} + +void fuse_backing_files_free(struct fuse_conn *fc) +{ + if (fc->backing_id_64) { + rhashtable_free_and_destroy(&fc->backing_64_ht, fuse_backing_rht_free, NULL); + } else { + idr_for_each(&fc->backing_files_map, fuse_backing_idr_free, NULL); + idr_destroy(&fc->backing_files_map); + } +} + +void fuse_backing_files_init_64(struct fuse_conn *fc) +{ + rhashtable_init(&fc->backing_64_ht, &fuse_backing_prm); + fc->backing_id_64 = true; } diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 2d7ee5498f1c..8b63a08a26b6 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -2339,6 +2339,24 @@ static long fuse_dev_ioctl_backing_open(struct file *file, return fuse_backing_open(fud->chan->conn, &map); } +static long fuse_dev_ioctl_backing_create(struct file *file, + struct fuse_backing_create_in __user *argp) +{ + struct fuse_dev *fud = fuse_get_dev(file); + struct fuse_backing_create_in map; + + if (IS_ERR(fud)) + return PTR_ERR(fud); + + if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) + return -EOPNOTSUPP; + + if (copy_from_user(&map, argp, sizeof(map))) + return -EFAULT; + + return fuse_backing_open_64(fud->chan->conn, &map); +} + static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp) { struct fuse_dev *fud = fuse_get_dev(file); @@ -2379,6 +2397,9 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd, case FUSE_DEV_IOC_BACKING_OPEN: return fuse_dev_ioctl_backing_open(file, argp); + case FUSE_DEV_IOC_BACKING_CREATE: + return fuse_dev_ioctl_backing_create(file, argp); + case FUSE_DEV_IOC_BACKING_CLOSE: return fuse_dev_ioctl_backing_close(file, argp); diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index f6c47ae0395b..dbffd5bed2f5 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -14,6 +14,7 @@ struct fuse_dev; struct fuse_args; struct fuse_copy_state; struct fuse_backing_map; +struct fuse_backing_create_in; struct file; struct folio; enum fuse_notify_code; @@ -87,6 +88,8 @@ int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map); int fuse_backing_close(struct fuse_conn *fc, int backing_id); +int fuse_backing_open_64(struct fuse_conn *fc, struct fuse_backing_create_in *map); +int fuse_backing_close_64(struct fuse_conn *fc, u64 backing_id); int fuse_copy_one(struct fuse_copy_state *cs, void *val, unsigned size); int fuse_copy_folio(struct fuse_copy_state *cs, struct folio **foliop, diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 3dd4dff24c50..003ef3c35d7a 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -32,6 +32,7 @@ #include <linux/pid_namespace.h> #include <linux/refcount.h> #include <linux/user_namespace.h> +#include <linux/rhashtable-types.h> /** Default max number of pages that can be used in a single read request */ #define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32 @@ -92,7 +93,8 @@ struct fuse_submount_lookup { struct fuse_backing { struct file *file; const struct cred *cred; - + u64 backing_id; + struct rhash_head hash_node; /* refcount */ refcount_t count; struct rcu_head rcu; @@ -689,6 +691,9 @@ struct fuse_conn { /** @init_security: Initialize security xattrs when creating a new inode */ unsigned int init_security:1; + /** Backing ID is 64 bit and allocated by the server */ + bool backing_id_64:1; + /** * @create_supp_group: Add supplementary group info when creating * a new inode @@ -770,8 +775,14 @@ struct fuse_conn { struct fuse_sync_bucket __rcu *curr_bucket; #ifdef CONFIG_FUSE_PASSTHROUGH - /** @backing_files_map: IDR for backing files ids */ - struct idr backing_files_map; + /* Selected by backing_id_64 */ + union { + /** @backing_files_map: IDR for backing files ids */ + struct idr backing_files_map; + + /** @backing_64_ht: 64 bit ID lookup hash table */ + struct rhashtable backing_64_ht; + }; #endif }; @@ -1270,7 +1281,7 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff, /* backing.c */ #ifdef CONFIG_FUSE_PASSTHROUGH void fuse_backing_put(struct fuse_backing *fb); -struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, int backing_id); + #else static inline void fuse_backing_put(struct fuse_backing *fb) @@ -1278,7 +1289,9 @@ static inline void fuse_backing_put(struct fuse_backing *fb) } #endif +struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, u64 backing_id); void fuse_backing_files_init(struct fuse_conn *fc); +void fuse_backing_files_init_64(struct fuse_conn *fc); void fuse_backing_files_free(struct fuse_conn *fc); /* passthrough.c */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index cbb10e19e7e8..bb76bddfc241 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1408,13 +1408,15 @@ static void process_init_reply(struct fuse_args *args, int error) * them together. */ if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) && - (flags & FUSE_PASSTHROUGH) && + (flags & (FUSE_PASSTHROUGH | FUSE_PASSTHROUGH_V2)) && arg->max_stack_depth > 0 && arg->max_stack_depth <= FILESYSTEM_MAX_STACK_DEPTH && !(flags & FUSE_WRITEBACK_CACHE)) { fc->passthrough = 1; fc->max_stack_depth = arg->max_stack_depth; fm->sb->s_stack_depth = arg->max_stack_depth; + if (flags & FUSE_PASSTHROUGH_V2) + fuse_backing_files_init_64(fc); } if (flags & FUSE_NO_EXPORT_SUPPORT) fm->sb->s_export_op = &fuse_export_fid_operations; @@ -1500,7 +1502,7 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm) if (fm->fc->auto_submounts) flags |= FUSE_SUBMOUNTS; if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) - flags |= FUSE_PASSTHROUGH; + flags |= FUSE_PASSTHROUGH | FUSE_PASSTHROUGH_V2; /* Only offered to sufficiently privileged servers; see * fuse_syncfs_enable(). */ diff --git a/fs/fuse/notify.c b/fs/fuse/notify.c index c0428e03d138..93e916a16ac9 100644 --- a/fs/fuse/notify.c +++ b/fs/fuse/notify.c @@ -409,6 +409,31 @@ static int fuse_notify_prune(struct fuse_conn *fc, unsigned int size, return 0; } +static int fuse_notify_backing_remove(struct fuse_conn *fc, unsigned int size, + struct fuse_copy_state *cs) +{ + struct fuse_notify_backing_remove_out outarg; + int err; + + if (size != sizeof(outarg)) + return -EINVAL; + + err = fuse_copy_one(cs, &outarg, sizeof(outarg)); + if (err) + return err; + + if (outarg.reserved) + return -EINVAL; + + if (!fc->backing_id_64) + return -EINVAL; + + if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) + return -EOPNOTSUPP; + + return fuse_backing_close_64(fc, outarg.backing_id); +} + int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, unsigned int size, struct fuse_copy_state *cs) { @@ -440,6 +465,9 @@ int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, case FUSE_NOTIFY_PRUNE: return fuse_notify_prune(fc, size, cs); + case FUSE_NOTIFY_BACKING_REMOVE: + return fuse_notify_backing_remove(fc, size, cs); + default: return -EINVAL; } diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c index 489838462d1e..313c8d7ffc09 100644 --- a/fs/fuse/passthrough.c +++ b/fs/fuse/passthrough.c @@ -160,6 +160,9 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id) struct fuse_backing *fb = NULL; struct file *backing_file; + if (fc->backing_id_64) + return ERR_PTR(fuse_EIO("incompatible backing version")); + if (backing_id <= 0) return ERR_PTR(fuse_EIO("invalid backing_id")); diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 10a7f31c4bdf..c88d7094af44 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -251,6 +251,9 @@ * * 7.47 * - add FUSE_HAS_SYNCFS opt-in flag for privileged userspace servers + * - add FUSE_PASSTHROUGH_V2 + * - add FUSE_DEV_IOC_BACKING_CREATE, struct fuse_backing_create_in + * - add FUSE_NOTIFY_BACKING_REMOVE, struct fuse_notify_backing_remove_out */ #ifndef _LINUX_FUSE_H @@ -473,6 +476,7 @@ struct fuse_file_lock { * with CAP_SYS_ADMIN in the initial user namespace (the same * privilege that mounting virtiofs or fuseblk requires). * Insufficiently privileged servers ignore it. + * FUSE_PASSTHROUGH_V2: use 64 bit server allocated backing ID */ #define FUSE_ASYNC_READ (1 << 0) #define FUSE_POSIX_LOCKS (1 << 1) @@ -522,6 +526,7 @@ struct fuse_file_lock { #define FUSE_REQUEST_TIMEOUT (1ULL << 42) #define FUSE_HAS_IO_URING_BUFPOOL (1ULL << 43) #define FUSE_HAS_SYNCFS (1ULL << 44) +#define FUSE_PASSTHROUGH_V2 (1ULL << 45) /** * CUSE INIT request/reply flags @@ -709,6 +714,7 @@ enum fuse_notify_code { FUSE_NOTIFY_RESEND = 7, FUSE_NOTIFY_INC_EPOCH = 8, FUSE_NOTIFY_PRUNE = 9, + FUSE_NOTIFY_BACKING_REMOVE = 10, }; /* The read buffer is required to be at least 8k, but may be much larger */ @@ -1159,13 +1165,20 @@ struct fuse_backing_map { uint64_t padding; }; +struct fuse_backing_create_in { + int32_t fd; + uint32_t padding; + uint64_t backing_id; + uint64_t spare[2]; +}; + /* Device ioctls: */ #define FUSE_DEV_IOC_MAGIC 229 #define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t) -#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \ - struct fuse_backing_map) +#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, struct fuse_backing_map) #define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t) #define FUSE_DEV_IOC_SYNC_INIT _IO(FUSE_DEV_IOC_MAGIC, 3) +#define FUSE_DEV_IOC_BACKING_CREATE _IOW(FUSE_DEV_IOC_MAGIC, 4, struct fuse_backing_create_in) struct fuse_lseek_in { uint64_t fh; @@ -1193,6 +1206,11 @@ struct fuse_copy_file_range_out { uint64_t bytes_copied; }; +struct fuse_notify_backing_remove_out { + uint64_t backing_id; + uint64_t reserved; +}; + #define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0) #define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1) struct fuse_setupmapping_in { -- 2.54.0
