// 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;}
// 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;
}