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.

Reply via email to