1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
|
// SPDX-License-Identifier: GPL-2.0
/*
* FUSE passthrough to backing file.
*
* Copyright (c) 2023 CTERA Networks.
*/
#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(sizeof(struct fuse_backing), GFP_KERNEL);
res = -ENOMEM;
if (!fb)
goto out_fput;
fb->file = file;
fb->cred = prepare_creds();
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;
}
|