Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.15.
  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
 14static void fuse_file_accessed(struct file *file)
 15{
 16	struct inode *inode = file_inode(file);
 17
 18	fuse_invalidate_atime(inode);
 19}
 20
 21static 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
 28ssize_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
 54ssize_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
 83ssize_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
102ssize_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
127ssize_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(backing_file, vma, &ctx);
141}
142
143struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
144{
145	if (fb && refcount_inc_not_zero(&fb->count))
146		return fb;
147	return NULL;
148}
149
150static 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(fb->cred);
157	kfree_rcu(fb, rcu);
158}
159
160void fuse_backing_put(struct fuse_backing *fb)
161{
162	if (fb && refcount_dec_and_test(&fb->count))
163		fuse_backing_free(fb);
164}
165
166void fuse_backing_files_init(struct fuse_conn *fc)
167{
168	idr_init(&fc->backing_files_map);
169}
170
171static 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(&fc->lock);
177	/* FIXME: xarray might be space inefficient */
178	id = idr_alloc_cyclic(&fc->backing_files_map, fb, 1, 0, GFP_ATOMIC);
179	spin_unlock(&fc->lock);
180	idr_preload_end();
181
182	WARN_ON_ONCE(id == 0);
183	return id;
184}
185
186static struct fuse_backing *fuse_backing_id_remove(struct fuse_conn *fc,
187						   int id)
188{
189	struct fuse_backing *fb;
190
191	spin_lock(&fc->lock);
192	fb = idr_remove(&fc->backing_files_map, id);
193	spin_unlock(&fc->lock);
194
195	return fb;
196}
197
198static 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
207void fuse_backing_files_free(struct fuse_conn *fc)
208{
209	idr_for_each(&fc->backing_files_map, fuse_backing_id_free, NULL);
210	idr_destroy(&fc->backing_files_map);
211}
212
213int 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 || map->padding)
229		goto out;
230
231	file = fget(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(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(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(&fb->count, 1);
253
254	res = fuse_backing_id_alloc(fc, fb);
255	if (res < 0) {
256		fuse_backing_free(fb);
257		fb = NULL;
258	}
259
260out:
261	pr_debug("%s: fb=0x%p, ret=%i\n", __func__, fb, res);
262
263	return res;
264
265out_fput:
266	fput(file);
267	goto out;
268}
269
270int 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, backing_id);
288	if (!fb)
289		goto out;
290
291	fuse_backing_put(fb);
292	err = 0;
293out:
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 */
304struct 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, 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(&file->f_path, file->f_flags,
329					 &fb->file->f_path, fb->cred);
330	err = PTR_ERR(backing_file);
331	if (IS_ERR(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(fb->cred);
339out:
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(err) : fb;
344}
345
346void 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(ff->cred);
354	ff->cred = NULL;
355}