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