]>
Commit | Line | Data |
---|---|---|
7dc4e97a AG |
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> | |
4a90451b | 11 | #include <linux/backing-file.h> |
5ca73468 | 12 | #include <linux/splice.h> |
7dc4e97a | 13 | |
57e1176e AG |
14 | static void fuse_file_accessed(struct file *file) |
15 | { | |
16 | struct inode *inode = file_inode(file); | |
17 | ||
18 | fuse_invalidate_atime(inode); | |
19 | } | |
20 | ||
21 | static void fuse_file_modified(struct file *file) | |
22 | { | |
23 | struct inode *inode = file_inode(file); | |
24 | ||
25 | fuse_invalidate_attr_mask(inode, FUSE_STATX_MODSIZE); | |
26 | } | |
27 | ||
28 | ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *iter) | |
29 | { | |
30 | struct file *file = iocb->ki_filp; | |
31 | struct fuse_file *ff = file->private_data; | |
32 | struct file *backing_file = fuse_file_passthrough(ff); | |
33 | size_t count = iov_iter_count(iter); | |
34 | ssize_t ret; | |
35 | struct backing_file_ctx ctx = { | |
36 | .cred = ff->cred, | |
37 | .user_file = file, | |
38 | .accessed = fuse_file_accessed, | |
39 | }; | |
40 | ||
41 | ||
42 | pr_debug("%s: backing_file=0x%p, pos=%lld, len=%zu\n", __func__, | |
43 | backing_file, iocb->ki_pos, count); | |
44 | ||
45 | if (!count) | |
46 | return 0; | |
47 | ||
48 | ret = backing_file_read_iter(backing_file, iter, iocb, iocb->ki_flags, | |
49 | &ctx); | |
50 | ||
51 | return ret; | |
52 | } | |
53 | ||
54 | ssize_t fuse_passthrough_write_iter(struct kiocb *iocb, | |
55 | struct iov_iter *iter) | |
56 | { | |
57 | struct file *file = iocb->ki_filp; | |
58 | struct inode *inode = file_inode(file); | |
59 | struct fuse_file *ff = file->private_data; | |
60 | struct file *backing_file = fuse_file_passthrough(ff); | |
61 | size_t count = iov_iter_count(iter); | |
62 | ssize_t ret; | |
63 | struct backing_file_ctx ctx = { | |
64 | .cred = ff->cred, | |
65 | .user_file = file, | |
66 | .end_write = fuse_file_modified, | |
67 | }; | |
68 | ||
69 | pr_debug("%s: backing_file=0x%p, pos=%lld, len=%zu\n", __func__, | |
70 | backing_file, iocb->ki_pos, count); | |
71 | ||
72 | if (!count) | |
73 | return 0; | |
74 | ||
75 | inode_lock(inode); | |
76 | ret = backing_file_write_iter(backing_file, iter, iocb, iocb->ki_flags, | |
77 | &ctx); | |
78 | inode_unlock(inode); | |
79 | ||
80 | return ret; | |
81 | } | |
82 | ||
5ca73468 AG |
83 | ssize_t fuse_passthrough_splice_read(struct file *in, loff_t *ppos, |
84 | struct pipe_inode_info *pipe, | |
85 | size_t len, unsigned int flags) | |
86 | { | |
87 | struct fuse_file *ff = in->private_data; | |
88 | struct file *backing_file = fuse_file_passthrough(ff); | |
89 | struct backing_file_ctx ctx = { | |
90 | .cred = ff->cred, | |
91 | .user_file = in, | |
92 | .accessed = fuse_file_accessed, | |
93 | }; | |
94 | ||
95 | pr_debug("%s: backing_file=0x%p, pos=%lld, len=%zu, flags=0x%x\n", __func__, | |
96 | backing_file, ppos ? *ppos : 0, len, flags); | |
97 | ||
98 | return backing_file_splice_read(backing_file, ppos, pipe, len, flags, | |
99 | &ctx); | |
100 | } | |
101 | ||
102 | ssize_t fuse_passthrough_splice_write(struct pipe_inode_info *pipe, | |
103 | struct file *out, loff_t *ppos, | |
104 | size_t len, unsigned int flags) | |
105 | { | |
106 | struct fuse_file *ff = out->private_data; | |
107 | struct file *backing_file = fuse_file_passthrough(ff); | |
108 | struct inode *inode = file_inode(out); | |
109 | ssize_t ret; | |
110 | struct backing_file_ctx ctx = { | |
111 | .cred = ff->cred, | |
112 | .user_file = out, | |
113 | .end_write = fuse_file_modified, | |
114 | }; | |
115 | ||
116 | pr_debug("%s: backing_file=0x%p, pos=%lld, len=%zu, flags=0x%x\n", __func__, | |
117 | backing_file, ppos ? *ppos : 0, len, flags); | |
118 | ||
119 | inode_lock(inode); | |
120 | ret = backing_file_splice_write(pipe, backing_file, ppos, len, flags, | |
121 | &ctx); | |
122 | inode_unlock(inode); | |
123 | ||
124 | return ret; | |
125 | } | |
126 | ||
7dc4e97a AG |
127 | struct fuse_backing *fuse_backing_get(struct fuse_backing *fb) |
128 | { | |
129 | if (fb && refcount_inc_not_zero(&fb->count)) | |
130 | return fb; | |
131 | return NULL; | |
132 | } | |
133 | ||
134 | static void fuse_backing_free(struct fuse_backing *fb) | |
135 | { | |
44350256 AG |
136 | pr_debug("%s: fb=0x%p\n", __func__, fb); |
137 | ||
7dc4e97a AG |
138 | if (fb->file) |
139 | fput(fb->file); | |
44350256 | 140 | put_cred(fb->cred); |
7dc4e97a AG |
141 | kfree_rcu(fb, rcu); |
142 | } | |
143 | ||
144 | void fuse_backing_put(struct fuse_backing *fb) | |
145 | { | |
146 | if (fb && refcount_dec_and_test(&fb->count)) | |
147 | fuse_backing_free(fb); | |
148 | } | |
44350256 AG |
149 | |
150 | void fuse_backing_files_init(struct fuse_conn *fc) | |
151 | { | |
152 | idr_init(&fc->backing_files_map); | |
153 | } | |
154 | ||
155 | static int fuse_backing_id_alloc(struct fuse_conn *fc, struct fuse_backing *fb) | |
156 | { | |
157 | int id; | |
158 | ||
159 | idr_preload(GFP_KERNEL); | |
160 | spin_lock(&fc->lock); | |
161 | /* FIXME: xarray might be space inefficient */ | |
162 | id = idr_alloc_cyclic(&fc->backing_files_map, fb, 1, 0, GFP_ATOMIC); | |
163 | spin_unlock(&fc->lock); | |
164 | idr_preload_end(); | |
165 | ||
166 | WARN_ON_ONCE(id == 0); | |
167 | return id; | |
168 | } | |
169 | ||
170 | static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc, | |
171 | int id) | |
172 | { | |
173 | struct fuse_backing *fb; | |
174 | ||
175 | spin_lock(&fc->lock); | |
176 | fb = idr_remove(&fc->backing_files_map, id); | |
177 | spin_unlock(&fc->lock); | |
178 | ||
179 | return fb; | |
180 | } | |
181 | ||
182 | static int fuse_backing_id_free(int id, void *p, void *data) | |
183 | { | |
184 | struct fuse_backing *fb = p; | |
185 | ||
186 | WARN_ON_ONCE(refcount_read(&fb->count) != 1); | |
187 | fuse_backing_free(fb); | |
188 | return 0; | |
189 | } | |
190 | ||
191 | void fuse_backing_files_free(struct fuse_conn *fc) | |
192 | { | |
193 | idr_for_each(&fc->backing_files_map, fuse_backing_id_free, NULL); | |
194 | idr_destroy(&fc->backing_files_map); | |
195 | } | |
196 | ||
197 | int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map) | |
198 | { | |
199 | struct file *file; | |
200 | struct super_block *backing_sb; | |
201 | struct fuse_backing *fb = NULL; | |
202 | int res; | |
203 | ||
204 | pr_debug("%s: fd=%d flags=0x%x\n", __func__, map->fd, map->flags); | |
205 | ||
206 | /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */ | |
207 | res = -EPERM; | |
208 | if (!fc->passthrough || !capable(CAP_SYS_ADMIN)) | |
209 | goto out; | |
210 | ||
211 | res = -EINVAL; | |
212 | if (map->flags) | |
213 | goto out; | |
214 | ||
215 | file = fget(map->fd); | |
216 | res = -EBADF; | |
217 | if (!file) | |
218 | goto out; | |
219 | ||
220 | res = -EOPNOTSUPP; | |
221 | if (!file->f_op->read_iter || !file->f_op->write_iter) | |
222 | goto out_fput; | |
223 | ||
224 | backing_sb = file_inode(file)->i_sb; | |
225 | res = -ELOOP; | |
226 | if (backing_sb->s_stack_depth >= fc->max_stack_depth) | |
227 | goto out_fput; | |
228 | ||
229 | fb = kmalloc(sizeof(struct fuse_backing), GFP_KERNEL); | |
230 | res = -ENOMEM; | |
231 | if (!fb) | |
232 | goto out_fput; | |
233 | ||
234 | fb->file = file; | |
235 | fb->cred = prepare_creds(); | |
236 | refcount_set(&fb->count, 1); | |
237 | ||
238 | res = fuse_backing_id_alloc(fc, fb); | |
239 | if (res < 0) { | |
240 | fuse_backing_free(fb); | |
241 | fb = NULL; | |
242 | } | |
243 | ||
244 | out: | |
245 | pr_debug("%s: fb=0x%p, ret=%i\n", __func__, fb, res); | |
246 | ||
247 | return res; | |
248 | ||
249 | out_fput: | |
250 | fput(file); | |
251 | goto out; | |
252 | } | |
253 | ||
254 | int fuse_backing_close(struct fuse_conn *fc, int backing_id) | |
255 | { | |
256 | struct fuse_backing *fb = NULL; | |
257 | int err; | |
258 | ||
259 | pr_debug("%s: backing_id=%d\n", __func__, backing_id); | |
260 | ||
261 | /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */ | |
262 | err = -EPERM; | |
263 | if (!fc->passthrough || !capable(CAP_SYS_ADMIN)) | |
264 | goto out; | |
265 | ||
266 | err = -EINVAL; | |
267 | if (backing_id <= 0) | |
268 | goto out; | |
269 | ||
270 | err = -ENOENT; | |
271 | fb = fuse_backing_id_remove(fc, backing_id); | |
272 | if (!fb) | |
273 | goto out; | |
274 | ||
275 | fuse_backing_put(fb); | |
276 | err = 0; | |
277 | out: | |
278 | pr_debug("%s: fb=0x%p, err=%i\n", __func__, fb, err); | |
279 | ||
280 | return err; | |
281 | } | |
4a90451b AG |
282 | |
283 | /* | |
284 | * Setup passthrough to a backing file. | |
285 | * | |
286 | * Returns an fb object with elevated refcount to be stored in fuse inode. | |
287 | */ | |
288 | struct fuse_backing *fuse_passthrough_open(struct file *file, | |
289 | struct inode *inode, | |
290 | int backing_id) | |
291 | { | |
292 | struct fuse_file *ff = file->private_data; | |
293 | struct fuse_conn *fc = ff->fm->fc; | |
294 | struct fuse_backing *fb = NULL; | |
295 | struct file *backing_file; | |
296 | int err; | |
297 | ||
298 | err = -EINVAL; | |
299 | if (backing_id <= 0) | |
300 | goto out; | |
301 | ||
302 | rcu_read_lock(); | |
303 | fb = idr_find(&fc->backing_files_map, backing_id); | |
304 | fb = fuse_backing_get(fb); | |
305 | rcu_read_unlock(); | |
306 | ||
307 | err = -ENOENT; | |
308 | if (!fb) | |
309 | goto out; | |
310 | ||
311 | /* Allocate backing file per fuse file to store fuse path */ | |
312 | backing_file = backing_file_open(&file->f_path, file->f_flags, | |
313 | &fb->file->f_path, fb->cred); | |
314 | err = PTR_ERR(backing_file); | |
315 | if (IS_ERR(backing_file)) { | |
316 | fuse_backing_put(fb); | |
317 | goto out; | |
318 | } | |
319 | ||
320 | err = 0; | |
321 | ff->passthrough = backing_file; | |
322 | ff->cred = get_cred(fb->cred); | |
323 | out: | |
324 | pr_debug("%s: backing_id=%d, fb=0x%p, backing_file=0x%p, err=%i\n", __func__, | |
325 | backing_id, fb, ff->passthrough, err); | |
326 | ||
327 | return err ? ERR_PTR(err) : fb; | |
328 | } | |
329 | ||
330 | void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb) | |
331 | { | |
332 | pr_debug("%s: fb=0x%p, backing_file=0x%p\n", __func__, | |
333 | fb, ff->passthrough); | |
334 | ||
335 | fput(ff->passthrough); | |
336 | ff->passthrough = NULL; | |
337 | put_cred(ff->cred); | |
338 | ff->cred = NULL; | |
339 | } |