From b674dcef4e58318c92b061c62899d5598203547e Mon Sep 17 00:00:00 2001 From: Chunsheng Luo Date: Mon, 12 Jan 2026 16:56:36 +0800 Subject: [PATCH] fuse: add ioctl to cleanup all backing files To simplify crash recovery and reduce performance impact, backing_ids are not persisted across daemon restarts. After crash recovery, this may lead to resource leaks if backing file resources are not properly cleaned up. Add FUSE_DEV_IOC_BACKING_CLOSE_ALL ioctl to release all backing_ids and put backing files. When the FUSE daemon restarts, it can use this ioctl to cleanup all backing file resources. Signed-off-by: Chunsheng Luo --- fs/fuse/backing.c | 77 +++++++++++++++++++++++++++++++++++---- fs/fuse/dev.c | 16 ++++++++ fs/fuse/fuse_i.h | 5 ++- fs/fuse/inode.c | 11 +++--- include/uapi/linux/fuse.h | 1 + 5 files changed, 95 insertions(+), 15 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 4afda419dd14..2da024dc003d 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -32,19 +32,29 @@ void fuse_backing_put(struct fuse_backing *fb) fuse_backing_free(fb); } -void fuse_backing_files_init(struct fuse_conn *fc) +int fuse_backing_files_init(struct fuse_conn *fc) { - idr_init(&fc->backing_files_map); + struct idr *idr; + + idr = kzalloc(sizeof(*idr), GFP_KERNEL); + if (!idr) + return -ENOMEM; + idr_init(idr); + rcu_assign_pointer(fc->backing_files_map, idr); + return 0; } static int fuse_backing_id_alloc(struct fuse_conn *fc, struct fuse_backing *fb) { + struct idr *idr; int id; idr_preload(GFP_KERNEL); spin_lock(&fc->lock); + idr = rcu_dereference_protected(fc->backing_files_map, + lockdep_is_held(&fc->lock)); /* FIXME: xarray might be space inefficient */ - id = idr_alloc_cyclic(&fc->backing_files_map, fb, 1, 0, GFP_ATOMIC); + id = idr_alloc_cyclic(idr, fb, 1, 0, GFP_ATOMIC); spin_unlock(&fc->lock); idr_preload_end(); @@ -55,10 +65,13 @@ static int fuse_backing_id_alloc(struct fuse_conn *fc, struct fuse_backing *fb) static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc, int id) { + struct idr *idr; struct fuse_backing *fb; spin_lock(&fc->lock); - fb = idr_remove(&fc->backing_files_map, id); + idr = rcu_dereference_protected(fc->backing_files_map, + lockdep_is_held(&fc->lock)); + fb = idr_remove(idr, id); spin_unlock(&fc->lock); return fb; @@ -75,8 +88,13 @@ static int fuse_backing_id_free(int id, void *p, void *data) void fuse_backing_files_free(struct fuse_conn *fc) { - idr_for_each(&fc->backing_files_map, fuse_backing_id_free, NULL); - idr_destroy(&fc->backing_files_map); + struct idr *idr = rcu_dereference_protected(fc->backing_files_map, 1); + + if (idr) { + idr_for_each(idr, fuse_backing_id_free, NULL); + idr_destroy(idr); + kfree(idr); + } } int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map) @@ -166,12 +184,57 @@ int fuse_backing_close(struct fuse_conn *fc, int backing_id) return err; } +int fuse_backing_close_all(struct fuse_conn *fc) +{ + struct idr *old_idr, *new_idr; + struct fuse_backing *fb; + int id; + + if (!fc->passthrough || !capable(CAP_SYS_ADMIN)) + return -EPERM; + + new_idr = kzalloc(sizeof(*new_idr), GFP_KERNEL); + if (!new_idr) + return -ENOMEM; + + idr_init(new_idr); + + /* + * Atomically exchange the old IDR with a new empty one under lock. + * This avoids long lock hold times and races with concurrent + * open/close operations. + */ + spin_lock(&fc->lock); + old_idr = rcu_dereference_protected(fc->backing_files_map, + lockdep_is_held(&fc->lock)); + rcu_assign_pointer(fc->backing_files_map, new_idr); + spin_unlock(&fc->lock); + + /* + * Ensure all concurrent RCU readers complete before releasing backing + * files, so any in-flight lookups can safely take references. + */ + synchronize_rcu(); + + if (old_idr) { + idr_for_each_entry(old_idr, fb, id) + fuse_backing_put(fb); + + idr_destroy(old_idr); + kfree(old_idr); + } + + return 0; +} + struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, int backing_id) { + struct idr *idr; struct fuse_backing *fb; rcu_read_lock(); - fb = idr_find(&fc->backing_files_map, backing_id); + idr = rcu_dereference(fc->backing_files_map); + fb = idr ? idr_find(idr, backing_id) : NULL; fb = fuse_backing_get(fb); rcu_read_unlock(); diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 6d59cbc877c6..f05d55302598 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -2654,6 +2654,19 @@ static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp) return fuse_backing_close(fud->fc, backing_id); } +static long fuse_dev_ioctl_backing_close_all(struct file *file) +{ + struct fuse_dev *fud = fuse_get_dev(file); + + if (IS_ERR(fud)) + return PTR_ERR(fud); + + if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) + return -EOPNOTSUPP; + + return fuse_backing_close_all(fud->fc); +} + static long fuse_dev_ioctl_sync_init(struct file *file) { int err = -EINVAL; @@ -2682,6 +2695,9 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd, case FUSE_DEV_IOC_BACKING_CLOSE: return fuse_dev_ioctl_backing_close(file, argp); + case FUSE_DEV_IOC_BACKING_CLOSE_ALL: + return fuse_dev_ioctl_backing_close_all(file); + case FUSE_DEV_IOC_SYNC_INIT: return fuse_dev_ioctl_sync_init(file); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 7f16049387d1..f45c5042e31a 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -979,7 +979,7 @@ struct fuse_conn { #ifdef CONFIG_FUSE_PASSTHROUGH /** IDR for backing files ids */ - struct idr backing_files_map; + struct idr __rcu *backing_files_map; #endif #ifdef CONFIG_FUSE_IO_URING @@ -1569,10 +1569,11 @@ static inline struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, } #endif -void fuse_backing_files_init(struct fuse_conn *fc); +int fuse_backing_files_init(struct fuse_conn *fc); void fuse_backing_files_free(struct fuse_conn *fc); 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_close_all(struct fuse_conn *fc); /* passthrough.c */ static inline struct fuse_backing *fuse_inode_backing(struct fuse_inode *fi) diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 819e50d66622..b63a067d50f8 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1001,9 +1001,6 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, fc->name_max = FUSE_NAME_LOW_MAX; fc->timeout.req_timeout = 0; - if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) - fuse_backing_files_init(fc); - INIT_LIST_HEAD(&fc->mounts); list_add(&fm->fc_entry, &fc->mounts); fm->fc = fc; @@ -1439,9 +1436,11 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, 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 (fuse_backing_files_init(fc) == 0) { + fc->passthrough = 1; + fc->max_stack_depth = arg->max_stack_depth; + fm->sb->s_stack_depth = arg->max_stack_depth; + } } if (flags & FUSE_NO_EXPORT_SUPPORT) fm->sb->s_export_op = &fuse_export_fid_operations; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index c13e1f9a2f12..e4ff28a4ff40 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -1139,6 +1139,7 @@ struct fuse_backing_map { 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_CLOSE_ALL _IO(FUSE_DEV_IOC_MAGIC, 4) struct fuse_lseek_in { uint64_t fh; -- 2.41.0