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 | #include <linux/backing-file.h> |
12 | #include <linux/splice.h> |
13 | |
14 | static void fuse_file_accessed(struct file *file) |
15 | { |
16 | struct inode *inode = file_inode(f: 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(f: 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(i: 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(file: backing_file, iter, iocb, flags: iocb->ki_flags, |
49 | ctx: &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(f: 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(i: 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(file: backing_file, iter, iocb, flags: iocb->ki_flags, |
77 | ctx: &ctx); |
78 | inode_unlock(inode); |
79 | |
80 | return ret; |
81 | } |
82 | |
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(in: backing_file, ppos, pipe, len, flags, |
99 | ctx: &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(f: 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, out: backing_file, ppos, len, flags, |
121 | ctx: &ctx); |
122 | inode_unlock(inode); |
123 | |
124 | return ret; |
125 | } |
126 | |
127 | ssize_t fuse_passthrough_mmap(struct file *file, struct vm_area_struct *vma) |
128 | { |
129 | struct fuse_file *ff = file->private_data; |
130 | struct file *backing_file = fuse_file_passthrough(ff); |
131 | struct backing_file_ctx ctx = { |
132 | .cred = ff->cred, |
133 | .user_file = file, |
134 | .accessed = fuse_file_accessed, |
135 | }; |
136 | |
137 | pr_debug("%s: backing_file=0x%p, start=%lu, end=%lu\n" , __func__, |
138 | backing_file, vma->vm_start, vma->vm_end); |
139 | |
140 | return backing_file_mmap(file: backing_file, vma, ctx: &ctx); |
141 | } |
142 | |
143 | struct fuse_backing *fuse_backing_get(struct fuse_backing *fb) |
144 | { |
145 | if (fb && refcount_inc_not_zero(r: &fb->count)) |
146 | return fb; |
147 | return NULL; |
148 | } |
149 | |
150 | static void fuse_backing_free(struct fuse_backing *fb) |
151 | { |
152 | pr_debug("%s: fb=0x%p\n" , __func__, fb); |
153 | |
154 | if (fb->file) |
155 | fput(fb->file); |
156 | put_cred(cred: fb->cred); |
157 | kfree_rcu(fb, rcu); |
158 | } |
159 | |
160 | void fuse_backing_put(struct fuse_backing *fb) |
161 | { |
162 | if (fb && refcount_dec_and_test(r: &fb->count)) |
163 | fuse_backing_free(fb); |
164 | } |
165 | |
166 | void fuse_backing_files_init(struct fuse_conn *fc) |
167 | { |
168 | idr_init(idr: &fc->backing_files_map); |
169 | } |
170 | |
171 | static int fuse_backing_id_alloc(struct fuse_conn *fc, struct fuse_backing *fb) |
172 | { |
173 | int id; |
174 | |
175 | idr_preload(GFP_KERNEL); |
176 | spin_lock(lock: &fc->lock); |
177 | /* FIXME: xarray might be space inefficient */ |
178 | id = idr_alloc_cyclic(&fc->backing_files_map, ptr: fb, start: 1, end: 0, GFP_ATOMIC); |
179 | spin_unlock(lock: &fc->lock); |
180 | idr_preload_end(); |
181 | |
182 | WARN_ON_ONCE(id == 0); |
183 | return id; |
184 | } |
185 | |
186 | static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc, |
187 | int id) |
188 | { |
189 | struct fuse_backing *fb; |
190 | |
191 | spin_lock(lock: &fc->lock); |
192 | fb = idr_remove(&fc->backing_files_map, id); |
193 | spin_unlock(lock: &fc->lock); |
194 | |
195 | return fb; |
196 | } |
197 | |
198 | static int fuse_backing_id_free(int id, void *p, void *data) |
199 | { |
200 | struct fuse_backing *fb = p; |
201 | |
202 | WARN_ON_ONCE(refcount_read(&fb->count) != 1); |
203 | fuse_backing_free(fb); |
204 | return 0; |
205 | } |
206 | |
207 | void fuse_backing_files_free(struct fuse_conn *fc) |
208 | { |
209 | idr_for_each(&fc->backing_files_map, fn: fuse_backing_id_free, NULL); |
210 | idr_destroy(&fc->backing_files_map); |
211 | } |
212 | |
213 | int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map) |
214 | { |
215 | struct file *file; |
216 | struct super_block *backing_sb; |
217 | struct fuse_backing *fb = NULL; |
218 | int res; |
219 | |
220 | pr_debug("%s: fd=%d flags=0x%x\n" , __func__, map->fd, map->flags); |
221 | |
222 | /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */ |
223 | res = -EPERM; |
224 | if (!fc->passthrough || !capable(CAP_SYS_ADMIN)) |
225 | goto out; |
226 | |
227 | res = -EINVAL; |
228 | if (map->flags) |
229 | goto out; |
230 | |
231 | file = fget(fd: map->fd); |
232 | res = -EBADF; |
233 | if (!file) |
234 | goto out; |
235 | |
236 | res = -EOPNOTSUPP; |
237 | if (!file->f_op->read_iter || !file->f_op->write_iter) |
238 | goto out_fput; |
239 | |
240 | backing_sb = file_inode(f: file)->i_sb; |
241 | res = -ELOOP; |
242 | if (backing_sb->s_stack_depth >= fc->max_stack_depth) |
243 | goto out_fput; |
244 | |
245 | fb = kmalloc(size: sizeof(struct fuse_backing), GFP_KERNEL); |
246 | res = -ENOMEM; |
247 | if (!fb) |
248 | goto out_fput; |
249 | |
250 | fb->file = file; |
251 | fb->cred = prepare_creds(); |
252 | refcount_set(r: &fb->count, n: 1); |
253 | |
254 | res = fuse_backing_id_alloc(fc, fb); |
255 | if (res < 0) { |
256 | fuse_backing_free(fb); |
257 | fb = NULL; |
258 | } |
259 | |
260 | out: |
261 | pr_debug("%s: fb=0x%p, ret=%i\n" , __func__, fb, res); |
262 | |
263 | return res; |
264 | |
265 | out_fput: |
266 | fput(file); |
267 | goto out; |
268 | } |
269 | |
270 | int fuse_backing_close(struct fuse_conn *fc, int backing_id) |
271 | { |
272 | struct fuse_backing *fb = NULL; |
273 | int err; |
274 | |
275 | pr_debug("%s: backing_id=%d\n" , __func__, backing_id); |
276 | |
277 | /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */ |
278 | err = -EPERM; |
279 | if (!fc->passthrough || !capable(CAP_SYS_ADMIN)) |
280 | goto out; |
281 | |
282 | err = -EINVAL; |
283 | if (backing_id <= 0) |
284 | goto out; |
285 | |
286 | err = -ENOENT; |
287 | fb = fuse_backing_id_remove(fc, id: backing_id); |
288 | if (!fb) |
289 | goto out; |
290 | |
291 | fuse_backing_put(fb); |
292 | err = 0; |
293 | out: |
294 | pr_debug("%s: fb=0x%p, err=%i\n" , __func__, fb, err); |
295 | |
296 | return err; |
297 | } |
298 | |
299 | /* |
300 | * Setup passthrough to a backing file. |
301 | * |
302 | * Returns an fb object with elevated refcount to be stored in fuse inode. |
303 | */ |
304 | struct fuse_backing *fuse_passthrough_open(struct file *file, |
305 | struct inode *inode, |
306 | int backing_id) |
307 | { |
308 | struct fuse_file *ff = file->private_data; |
309 | struct fuse_conn *fc = ff->fm->fc; |
310 | struct fuse_backing *fb = NULL; |
311 | struct file *backing_file; |
312 | int err; |
313 | |
314 | err = -EINVAL; |
315 | if (backing_id <= 0) |
316 | goto out; |
317 | |
318 | rcu_read_lock(); |
319 | fb = idr_find(&fc->backing_files_map, id: backing_id); |
320 | fb = fuse_backing_get(fb); |
321 | rcu_read_unlock(); |
322 | |
323 | err = -ENOENT; |
324 | if (!fb) |
325 | goto out; |
326 | |
327 | /* Allocate backing file per fuse file to store fuse path */ |
328 | backing_file = backing_file_open(user_path: &file->f_path, flags: file->f_flags, |
329 | real_path: &fb->file->f_path, cred: fb->cred); |
330 | err = PTR_ERR(ptr: backing_file); |
331 | if (IS_ERR(ptr: backing_file)) { |
332 | fuse_backing_put(fb); |
333 | goto out; |
334 | } |
335 | |
336 | err = 0; |
337 | ff->passthrough = backing_file; |
338 | ff->cred = get_cred(cred: fb->cred); |
339 | out: |
340 | pr_debug("%s: backing_id=%d, fb=0x%p, backing_file=0x%p, err=%i\n" , __func__, |
341 | backing_id, fb, ff->passthrough, err); |
342 | |
343 | return err ? ERR_PTR(error: err) : fb; |
344 | } |
345 | |
346 | void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb) |
347 | { |
348 | pr_debug("%s: fb=0x%p, backing_file=0x%p\n" , __func__, |
349 | fb, ff->passthrough); |
350 | |
351 | fput(ff->passthrough); |
352 | ff->passthrough = NULL; |
353 | put_cred(cred: ff->cred); |
354 | ff->cred = NULL; |
355 | } |
356 | |