]> git.ipfire.org Git - thirdparty/kernel/linux.git/blob - fs/fuse/passthrough.c
fuse: implement open in passthrough mode
[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 #include <linux/backing-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 /* FIXME: xarray might be space inefficient */
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 /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
93 res = -EPERM;
94 if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
95 goto out;
96
97 res = -EINVAL;
98 if (map->flags)
99 goto out;
100
101 file = fget(map->fd);
102 res = -EBADF;
103 if (!file)
104 goto out;
105
106 res = -EOPNOTSUPP;
107 if (!file->f_op->read_iter || !file->f_op->write_iter)
108 goto out_fput;
109
110 backing_sb = file_inode(file)->i_sb;
111 res = -ELOOP;
112 if (backing_sb->s_stack_depth >= fc->max_stack_depth)
113 goto out_fput;
114
115 fb = kmalloc(sizeof(struct fuse_backing), GFP_KERNEL);
116 res = -ENOMEM;
117 if (!fb)
118 goto out_fput;
119
120 fb->file = file;
121 fb->cred = prepare_creds();
122 refcount_set(&fb->count, 1);
123
124 res = fuse_backing_id_alloc(fc, fb);
125 if (res < 0) {
126 fuse_backing_free(fb);
127 fb = NULL;
128 }
129
130 out:
131 pr_debug("%s: fb=0x%p, ret=%i\n", __func__, fb, res);
132
133 return res;
134
135 out_fput:
136 fput(file);
137 goto out;
138 }
139
140 int fuse_backing_close(struct fuse_conn *fc, int backing_id)
141 {
142 struct fuse_backing *fb = NULL;
143 int err;
144
145 pr_debug("%s: backing_id=%d\n", __func__, backing_id);
146
147 /* TODO: relax CAP_SYS_ADMIN once backing files are visible to lsof */
148 err = -EPERM;
149 if (!fc->passthrough || !capable(CAP_SYS_ADMIN))
150 goto out;
151
152 err = -EINVAL;
153 if (backing_id <= 0)
154 goto out;
155
156 err = -ENOENT;
157 fb = fuse_backing_id_remove(fc, backing_id);
158 if (!fb)
159 goto out;
160
161 fuse_backing_put(fb);
162 err = 0;
163 out:
164 pr_debug("%s: fb=0x%p, err=%i\n", __func__, fb, err);
165
166 return err;
167 }
168
169 /*
170 * Setup passthrough to a backing file.
171 *
172 * Returns an fb object with elevated refcount to be stored in fuse inode.
173 */
174 struct fuse_backing *fuse_passthrough_open(struct file *file,
175 struct inode *inode,
176 int backing_id)
177 {
178 struct fuse_file *ff = file->private_data;
179 struct fuse_conn *fc = ff->fm->fc;
180 struct fuse_backing *fb = NULL;
181 struct file *backing_file;
182 int err;
183
184 err = -EINVAL;
185 if (backing_id <= 0)
186 goto out;
187
188 rcu_read_lock();
189 fb = idr_find(&fc->backing_files_map, backing_id);
190 fb = fuse_backing_get(fb);
191 rcu_read_unlock();
192
193 err = -ENOENT;
194 if (!fb)
195 goto out;
196
197 /* Allocate backing file per fuse file to store fuse path */
198 backing_file = backing_file_open(&file->f_path, file->f_flags,
199 &fb->file->f_path, fb->cred);
200 err = PTR_ERR(backing_file);
201 if (IS_ERR(backing_file)) {
202 fuse_backing_put(fb);
203 goto out;
204 }
205
206 err = 0;
207 ff->passthrough = backing_file;
208 ff->cred = get_cred(fb->cred);
209 out:
210 pr_debug("%s: backing_id=%d, fb=0x%p, backing_file=0x%p, err=%i\n", __func__,
211 backing_id, fb, ff->passthrough, err);
212
213 return err ? ERR_PTR(err) : fb;
214 }
215
216 void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb)
217 {
218 pr_debug("%s: fb=0x%p, backing_file=0x%p\n", __func__,
219 fb, ff->passthrough);
220
221 fput(ff->passthrough);
222 ff->passthrough = NULL;
223 put_cred(ff->cred);
224 ff->cred = NULL;
225 }