| 1 | |
| 2 | |
| 3 | * FUSE passthrough to backing file.
|
| 4 | *
|
| 5 | * Copyright (c) 2023 CTERA Networks.
|
| 6 |
|
| 7 |
|
| 8 | #include "dev.h"
|
| 9 | #include "fuse_i.h"
|
| 10 |
|
| 11 | #include <linux/file.h>
|
| 12 |
|
| 13 | struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
|
| 14 | {
|
| 15 | if (fb && refcount_inc_not_zero(&fb->count))
|
| 16 | return fb;
|
| 17 | return NULL;
|
| 18 | }
|
| 19 |
|
| 20 | static void fuse_backing_free(struct fuse_backing *fb)
|
| 21 | {
|
| 22 | pr_debug("%s: fb=0x%p\n", __func__, fb);
|
| 23 |
|
| 24 | if (fb->file)
|
| 25 | fput(fb->file);
|
| 26 | put_cred(fb->cred);
|
| 27 | kfree_rcu(fb, rcu);
|
| 28 | }
|
| 29 |
|
| 30 | void fuse_backing_put(struct fuse_backing *fb)
|
| 31 | {
|
| 32 | if (fb && refcount_dec_and_test(&fb->count))
|
| 33 | fuse_backing_free(fb);
|
| 34 | }
|
| 35 |
|
| 36 | void fuse_backing_files_init(struct fuse_conn *fc)
|
| 37 | {
|
| 38 | idr_init(&fc->backing_files_map);
|
| 39 | }
|
| 40 |
|
| 41 | static int fuse_backing_id_alloc(struct fuse_conn *fc, struct fuse_backing *fb)
|
| 42 | {
|
| 43 | int id;
|
| 44 |
|
| 45 | idr_preload(GFP_KERNEL);
|
| 46 | spin_lock(&fc->lock);
|
| 47 |
|
| 48 | id = idr_alloc_cyclic(&fc->backing_files_map, fb, 1, 0, GFP_ATOMIC);
|
| 49 | spin_unlock(&fc->lock);
|
| 50 | idr_preload_end();
|
| 51 |
|
| 52 | WARN_ON_ONCE(id == 0);
|
| 53 | return id;
|
| 54 | }
|
| 55 |
|
| 56 | static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc,
|
| 57 | int id)
|
| 58 | {
|
| 59 | struct fuse_backing *fb;
|
| 60 |
|
| 61 | spin_lock(&fc->lock);
|
| 62 | fb = idr_remove(&fc->backing_files_map, id);
|
| 63 | spin_unlock(&fc->lock);
|
| 64 |
|
| 65 | return fb;
|
| 66 | }
|
| 67 |
|
| 68 | static int fuse_backing_id_free(int id, void *p, void *data)
|
| 69 | {
|
| 70 | struct fuse_backing *fb = p;
|
| 71 |
|
| 72 | WARN_ON_ONCE(refcount_read(&fb->count) != 1);
|
| 73 | fuse_backing_free(fb);
|
| 74 | return 0;
|
| 75 | }
|
| 76 |
|
| 77 | void fuse_backing_files_free(struct fuse_conn *fc)
|
| 78 | {
|
| 79 | idr_for_each(&fc->backing_files_map, fuse_backing_id_free, NULL);
|
| 80 | idr_destroy(&fc->backing_files_map);
|
| 81 | }
|
| 82 |
|
| 83 | int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
|
| 84 | {
|
| 85 | struct file *file;
|
| 86 | struct super_block *backing_sb;
|
| 87 | struct fuse_backing *fb = NULL;
|
| 88 | int res;
|
| 89 |
|
| 90 | pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags);
|
| 91 |
|
| 92 |
|
| 93 | res = -EPERM;
|
| 94 | if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
|
| 95 | goto out;
|
| 96 |
|
| 97 | res = -EINVAL;
|
| 98 | if (map->flags || map->padding)
|
| 99 | goto out;
|
| 100 |
|
| 101 | file = fget_raw(map->fd);
|
| 102 | res = -EBADF;
|
| 103 | if (!file)
|
| 104 | goto out;
|
| 105 |
|
| 106 |
|
| 107 | res = d_is_dir(file->f_path.dentry) ? -EISDIR : -EINVAL;
|
| 108 | if (!d_is_reg(file->f_path.dentry))
|
| 109 | goto out_fput;
|
| 110 |
|
| 111 | backing_sb = file_inode(file)->i_sb;
|
| 112 | res = -ELOOP;
|
| 113 | if (backing_sb->s_stack_depth >= fc->max_stack_depth)
|
| 114 | goto out_fput;
|
| 115 |
|
| 116 | fb = kmalloc_obj(struct fuse_backing);
|
| 117 | res = -ENOMEM;
|
| 118 | if (!fb)
|
| 119 | goto out_fput;
|
| 120 |
|
| 121 | fb->file = file;
|
| 122 | fb->cred = get_current_cred();
|
| 123 | refcount_set(&fb->count, 1);
|
| 124 |
|
| 125 | res = fuse_backing_id_alloc(fc, fb);
|
| 126 | if (res < 0) {
|
| 127 | fuse_backing_free(fb);
|
| 128 | fb = NULL;
|
| 129 | }
|
| 130 |
|
| 131 | out:
|
| 132 | pr_debug("%s: fb=0x%p, ret=%i\n", __func__, fb, res);
|
| 133 |
|
| 134 | return res;
|
| 135 |
|
| 136 | out_fput:
|
| 137 | fput(file);
|
| 138 | goto out;
|
| 139 | }
|
| 140 |
|
| 141 | int fuse_backing_close(struct fuse_conn *fc, int backing_id)
|
| 142 | {
|
| 143 | struct fuse_backing *fb = NULL;
|
| 144 | int err;
|
| 145 |
|
| 146 | pr_debug("%s: backing_id=%d\n", __func__, backing_id);
|
| 147 |
|
| 148 |
|
| 149 | err = -EPERM;
|
| 150 | if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
|
| 151 | goto out;
|
| 152 |
|
| 153 | err = -EINVAL;
|
| 154 | if (backing_id <= 0)
|
| 155 | goto out;
|
| 156 |
|
| 157 | err = -ENOENT;
|
| 158 | fb = fuse_backing_id_remove(fc, backing_id);
|
| 159 | if (!fb)
|
| 160 | goto out;
|
| 161 |
|
| 162 | fuse_backing_put(fb);
|
| 163 | err = 0;
|
| 164 | out:
|
| 165 | pr_debug("%s: fb=0x%p, err=%i\n", __func__, fb, err);
|
| 166 |
|
| 167 | return err;
|
| 168 | }
|
| 169 |
|
| 170 | struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, int backing_id)
|
| 171 | {
|
| 172 | struct fuse_backing *fb;
|
| 173 |
|
| 174 | rcu_read_lock();
|
| 175 | fb = idr_find(&fc->backing_files_map, backing_id);
|
| 176 | fb = fuse_backing_get(fb);
|
| 177 | rcu_read_unlock();
|
| 178 |
|
| 179 | return fb;
|
| 180 | }
|