On Thu, Oct 1, 2026 at 5:09 PM Miklos Szeredi <[email protected]> wrote: > > 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.
Description outdated. > > 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. Can you reserve id 0? or have a reason to need it? > > 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]) Please reserve backing_id 0 > + 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); It would feel a little less hacky if we do: else fuse_backing_files_init_32(fc); and set fc->passthrough = 1; inside both of them to gate against fuse_backing_files_free() called without processing init response, or without either FUSE_PASSTHROUGH in the init response. Thanks, Amir.
