| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * FUSE passthrough to backing file. | |
| * | |
| * Copyright (c) 2023 CTERA Networks. | |
| */ | |
| #include "dev.h" | |
| #include "fuse_i.h" | |
| #include <linux/file.h> | |
| struct fuse_backing *fuse_backing_get(struct fuse_backing *fb) | |
| { | |
| if (fb && refcount_inc_not_zero(&fb->count)) | |
| return fb; | |
| return NULL; | |
| } | |
| static void fuse_backing_free(struct fuse_backing *fb) | |
| { | |
| pr_debug("%s: fb=0x%p\n", __func__, fb); | |
| if (fb->file) | |
| fput(fb->file); | |
| put_cred(fb->cred); | |
| kfree_rcu(fb, rcu); | |
| } | |
| void fuse_backing_put(struct fuse_backing *fb) | |
| { | |
| if (fb && refcount_dec_and_test(&fb->count)) | |
| fuse_backing_free(fb); | |
| } | |
| void fuse_backing_files_init(struct fuse_conn *fc) | |
| { | |
| idr_init(&fc->backing_files_map); | |
| } | |
| static int fuse_backing_id_alloc(struct fuse_conn *fc, struct fuse_backing *fb) | |
| { | |
| int id; | |
| idr_preload(GFP_KERNEL); | |
| spin_lock(&fc->lock); | |
| /* FIXME: xarray might be space inefficient */ | |
| id = idr_alloc_cyclic(&fc->backing_files_map, fb, 1, 0, GFP_ATOMIC); | |
| spin_unlock(&fc->lock); | |
| idr_preload_end(); | |
| WARN_ON_ONCE(id == 0); | |
| return id; | |
| } | |
| static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc, | |
| int id) | |
| { | |
| struct fuse_backing *fb; | |
| spin_lock(&fc->lock); | |
| fb = idr_remove(&fc->backing_files_map, id); | |
| spin_unlock(&fc->lock); | |
| return fb; | |
| } | |
| static int fuse_backing_id_free(int id, void *p, void *data) | |
| { | |
| struct fuse_backing *fb = p; | |
| WARN_ON_ONCE(refcount_read(&fb->count) != 1); | |
| fuse_backing_free(fb); | |
| return 0; | |
| } | |
| 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); | |
| } | |
| int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map) | |
| { | |
| struct file *file; | |
| 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); | |
| /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */ | |
| res = -EPERM; | |
| if (!fc->passthrough || !capable(CAP_SYS_ADMIN)) | |
| goto out; | |
| res = -EINVAL; | |
| if (map->flags || map->padding) | |
| goto out; | |
| file = fget_raw(map->fd); | |
| res = -EBADF; | |
| if (!file) | |
| goto out; | |
| /* 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; | |
| backing_sb = file_inode(file)->i_sb; | |
| res = -ELOOP; | |
| if (backing_sb->s_stack_depth >= fc->max_stack_depth) | |
| goto out_fput; | |
| fb = kmalloc_obj(struct fuse_backing); | |
| res = -ENOMEM; | |
| if (!fb) | |
| goto out_fput; | |
| fb->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; | |
| } | |
| 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) | |
| { | |
| struct fuse_backing *fb = NULL; | |
| int err; | |
| pr_debug("%s: backing_id=%d\n", __func__, backing_id); | |
| /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */ | |
| err = -EPERM; | |
| if (!fc->passthrough || !capable(CAP_SYS_ADMIN)) | |
| goto out; | |
| err = -EINVAL; | |
| if (backing_id <= 0) | |
| goto out; | |
| err = -ENOENT; | |
| fb = fuse_backing_id_remove(fc, backing_id); | |
| if (!fb) | |
| goto out; | |
| fuse_backing_put(fb); | |
| err = 0; | |
| out: | |
| pr_debug("%s: fb=0x%p, err=%i\n", __func__, fb, err); | |
| return err; | |
| } | |
| struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, int 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(); | |
| return fb; | |
| } |