On Thu, Oct 1, 2026 at 7:07 PM Amir Goldstein <[email protected]> wrote:
>
> 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.

Also we should not enable passthrough if rhashtable_init has failed.

Thanks,
Amir.

Reply via email to