]> git.sesse.net Git - bcachefs-tools-debian/blob - libbcachefs/chardev.c
Update bcachefs sources to 6a25f7a00d bcachefs: fix ioctl code
[bcachefs-tools-debian] / libbcachefs / chardev.c
1 #include "bcachefs.h"
2 #include "bcachefs_ioctl.h"
3 #include "super.h"
4 #include "super-io.h"
5
6 #include <linux/module.h>
7 #include <linux/fs.h>
8 #include <linux/major.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/ioctl.h>
12 #include <linux/uaccess.h>
13 #include <linux/slab.h>
14
15 /* returns with ref on ca->ref */
16 static struct bch_dev *bch2_device_lookup(struct bch_fs *c, u64 dev,
17                                           unsigned flags)
18 {
19         struct bch_dev *ca;
20
21         if (flags & BCH_BY_INDEX) {
22                 if (dev >= c->sb.nr_devices)
23                         return ERR_PTR(-EINVAL);
24
25                 rcu_read_lock();
26                 ca = c->devs[dev];
27                 if (ca)
28                         percpu_ref_get(&ca->ref);
29                 rcu_read_unlock();
30
31                 if (!ca)
32                         return ERR_PTR(-EINVAL);
33         } else {
34                 struct block_device *bdev;
35                 char *path;
36                 unsigned i;
37
38                 path = strndup_user((const char __user *)
39                                     (unsigned long) dev, PATH_MAX);
40                 if (IS_ERR(path))
41                         return ERR_CAST(path);
42
43                 bdev = lookup_bdev(path);
44                 kfree(path);
45                 if (IS_ERR(bdev))
46                         return ERR_CAST(bdev);
47
48                 for_each_member_device(ca, c, i)
49                         if (ca->disk_sb.bdev == bdev)
50                                 goto found;
51
52                 ca = ERR_PTR(-ENOENT);
53 found:
54                 bdput(bdev);
55         }
56
57         return ca;
58 }
59
60 static long bch2_ioctl_assemble(struct bch_ioctl_assemble __user *user_arg)
61 {
62         struct bch_ioctl_assemble arg;
63         const char *err;
64         u64 *user_devs = NULL;
65         char **devs = NULL;
66         unsigned i;
67         int ret = -EFAULT;
68
69         if (copy_from_user(&arg, user_arg, sizeof(arg)))
70                 return -EFAULT;
71
72         if (arg.flags || arg.pad)
73                 return -EINVAL;
74
75         user_devs = kmalloc_array(arg.nr_devs, sizeof(u64), GFP_KERNEL);
76         if (!user_devs)
77                 return -ENOMEM;
78
79         devs = kcalloc(arg.nr_devs, sizeof(char *), GFP_KERNEL);
80
81         if (copy_from_user(user_devs, arg.devs,
82                            sizeof(u64) * arg.nr_devs))
83                 goto err;
84
85         for (i = 0; i < arg.nr_devs; i++) {
86                 devs[i] = strndup_user((const char __user *)(unsigned long)
87                                        user_devs[i],
88                                        PATH_MAX);
89                 if (!devs[i]) {
90                         ret = -ENOMEM;
91                         goto err;
92                 }
93         }
94
95         err = bch2_fs_open(devs, arg.nr_devs, bch2_opts_empty(), NULL);
96         if (err) {
97                 pr_err("Could not open filesystem: %s", err);
98                 ret = -EINVAL;
99                 goto err;
100         }
101
102         ret = 0;
103 err:
104         if (devs)
105                 for (i = 0; i < arg.nr_devs; i++)
106                         kfree(devs[i]);
107         kfree(devs);
108         return ret;
109 }
110
111 static long bch2_ioctl_incremental(struct bch_ioctl_incremental __user *user_arg)
112 {
113         struct bch_ioctl_incremental arg;
114         const char *err;
115         char *path;
116
117         if (copy_from_user(&arg, user_arg, sizeof(arg)))
118                 return -EFAULT;
119
120         if (arg.flags || arg.pad)
121                 return -EINVAL;
122
123         path = strndup_user((const char __user *)(unsigned long) arg.dev, PATH_MAX);
124         if (!path)
125                 return -ENOMEM;
126
127         err = bch2_fs_open_incremental(path);
128         kfree(path);
129
130         if (err) {
131                 pr_err("Could not register bcachefs devices: %s", err);
132                 return -EINVAL;
133         }
134
135         return 0;
136 }
137
138 static long bch2_global_ioctl(unsigned cmd, void __user *arg)
139 {
140         switch (cmd) {
141         case BCH_IOCTL_ASSEMBLE:
142                 return bch2_ioctl_assemble(arg);
143         case BCH_IOCTL_INCREMENTAL:
144                 return bch2_ioctl_incremental(arg);
145         default:
146                 return -ENOTTY;
147         }
148 }
149
150 static long bch2_ioctl_query_uuid(struct bch_fs *c,
151                         struct bch_ioctl_query_uuid __user *user_arg)
152 {
153         return copy_to_user(&user_arg->uuid,
154                             &c->sb.user_uuid,
155                             sizeof(c->sb.user_uuid));
156 }
157
158 static long bch2_ioctl_start(struct bch_fs *c, struct bch_ioctl_start arg)
159 {
160         if (arg.flags || arg.pad)
161                 return -EINVAL;
162
163         return bch2_fs_start(c) ? -EIO : 0;
164 }
165
166 static long bch2_ioctl_stop(struct bch_fs *c)
167 {
168         bch2_fs_stop(c);
169         return 0;
170 }
171
172 static long bch2_ioctl_disk_add(struct bch_fs *c, struct bch_ioctl_disk arg)
173 {
174         char *path;
175         int ret;
176
177         if (arg.flags || arg.pad)
178                 return -EINVAL;
179
180         path = strndup_user((const char __user *)(unsigned long) arg.dev, PATH_MAX);
181         if (!path)
182                 return -ENOMEM;
183
184         ret = bch2_dev_add(c, path);
185         kfree(path);
186
187         return ret;
188 }
189
190 static long bch2_ioctl_disk_remove(struct bch_fs *c, struct bch_ioctl_disk arg)
191 {
192         struct bch_dev *ca;
193
194         if ((arg.flags & ~(BCH_FORCE_IF_DATA_LOST|
195                            BCH_FORCE_IF_METADATA_LOST|
196                            BCH_FORCE_IF_DEGRADED|
197                            BCH_BY_INDEX)) ||
198             arg.pad)
199                 return -EINVAL;
200
201         ca = bch2_device_lookup(c, arg.dev, arg.flags);
202         if (IS_ERR(ca))
203                 return PTR_ERR(ca);
204
205         return bch2_dev_remove(c, ca, arg.flags);
206 }
207
208 static long bch2_ioctl_disk_online(struct bch_fs *c, struct bch_ioctl_disk arg)
209 {
210         char *path;
211         int ret;
212
213         if (arg.flags || arg.pad)
214                 return -EINVAL;
215
216         path = strndup_user((const char __user *)(unsigned long) arg.dev, PATH_MAX);
217         if (!path)
218                 return -ENOMEM;
219
220         ret = bch2_dev_online(c, path);
221         kfree(path);
222         return ret;
223 }
224
225 static long bch2_ioctl_disk_offline(struct bch_fs *c, struct bch_ioctl_disk arg)
226 {
227         struct bch_dev *ca;
228         int ret;
229
230         if ((arg.flags & ~(BCH_FORCE_IF_DATA_LOST|
231                            BCH_FORCE_IF_METADATA_LOST|
232                            BCH_FORCE_IF_DEGRADED|
233                            BCH_BY_INDEX)) ||
234             arg.pad)
235                 return -EINVAL;
236
237         ca = bch2_device_lookup(c, arg.dev, arg.flags);
238         if (IS_ERR(ca))
239                 return PTR_ERR(ca);
240
241         ret = bch2_dev_offline(c, ca, arg.flags);
242         percpu_ref_put(&ca->ref);
243         return ret;
244 }
245
246 static long bch2_ioctl_disk_set_state(struct bch_fs *c,
247                         struct bch_ioctl_disk_set_state arg)
248 {
249         struct bch_dev *ca;
250         int ret;
251
252         if ((arg.flags & ~(BCH_FORCE_IF_DATA_LOST|
253                            BCH_FORCE_IF_METADATA_LOST|
254                            BCH_FORCE_IF_DEGRADED|
255                            BCH_BY_INDEX)) ||
256             arg.pad[0] || arg.pad[1] || arg.pad[2])
257                 return -EINVAL;
258
259         ca = bch2_device_lookup(c, arg.dev, arg.flags);
260         if (IS_ERR(ca))
261                 return PTR_ERR(ca);
262
263         ret = bch2_dev_set_state(c, ca, arg.new_state, arg.flags);
264
265         percpu_ref_put(&ca->ref);
266         return ret;
267 }
268
269 static long bch2_ioctl_disk_evacuate(struct bch_fs *c,
270                                      struct bch_ioctl_disk arg)
271 {
272         struct bch_dev *ca;
273         int ret;
274
275         if ((arg.flags & ~BCH_BY_INDEX) ||
276             arg.pad)
277                 return -EINVAL;
278
279         ca = bch2_device_lookup(c, arg.dev, arg.flags);
280         if (IS_ERR(ca))
281                 return PTR_ERR(ca);
282
283         ret = bch2_dev_evacuate(c, ca);
284
285         percpu_ref_put(&ca->ref);
286         return ret;
287 }
288
289 #define BCH_IOCTL(_name, _argtype)                                      \
290 do {                                                                    \
291         _argtype i;                                                     \
292                                                                         \
293         if (copy_from_user(&i, arg, sizeof(i)))                         \
294                 return -EFAULT;                                         \
295         return bch2_ioctl_##_name(c, i);                                \
296 } while (0)
297
298 long bch2_fs_ioctl(struct bch_fs *c, unsigned cmd, void __user *arg)
299 {
300         /* ioctls that don't require admin cap: */
301         switch (cmd) {
302         case BCH_IOCTL_QUERY_UUID:
303                 return bch2_ioctl_query_uuid(c, arg);
304         }
305
306         if (!capable(CAP_SYS_ADMIN))
307                 return -EPERM;
308
309         /* ioctls that do require admin cap: */
310         switch (cmd) {
311         case BCH_IOCTL_START:
312                 BCH_IOCTL(start, struct bch_ioctl_start);
313         case BCH_IOCTL_STOP:
314                 return bch2_ioctl_stop(c);
315
316         case BCH_IOCTL_DISK_ADD:
317                 BCH_IOCTL(disk_add, struct bch_ioctl_disk);
318         case BCH_IOCTL_DISK_REMOVE:
319                 BCH_IOCTL(disk_remove, struct bch_ioctl_disk);
320         case BCH_IOCTL_DISK_ONLINE:
321                 BCH_IOCTL(disk_online, struct bch_ioctl_disk);
322         case BCH_IOCTL_DISK_OFFLINE:
323                 BCH_IOCTL(disk_offline, struct bch_ioctl_disk);
324         case BCH_IOCTL_DISK_SET_STATE:
325                 BCH_IOCTL(disk_set_state, struct bch_ioctl_disk_set_state);
326         case BCH_IOCTL_DISK_EVACUATE:
327                 BCH_IOCTL(disk_evacuate, struct bch_ioctl_disk);
328
329         default:
330                 return -ENOTTY;
331         }
332 }
333
334 static long bch2_chardev_ioctl(struct file *filp, unsigned cmd, unsigned long v)
335 {
336         struct bch_fs *c = filp->private_data;
337         void __user *arg = (void __user *) v;
338
339         return c
340                 ? bch2_fs_ioctl(c, cmd, arg)
341                 : bch2_global_ioctl(cmd, arg);
342 }
343
344 static const struct file_operations bch_chardev_fops = {
345         .owner          = THIS_MODULE,
346         .unlocked_ioctl = bch2_chardev_ioctl,
347         .open           = nonseekable_open,
348 };
349
350 static int bch_chardev_major;
351 static struct class *bch_chardev_class;
352 static struct device *bch_chardev;
353 static DEFINE_IDR(bch_chardev_minor);
354
355 void bch2_fs_chardev_exit(struct bch_fs *c)
356 {
357         if (!IS_ERR_OR_NULL(c->chardev))
358                 device_unregister(c->chardev);
359         if (c->minor >= 0)
360                 idr_remove(&bch_chardev_minor, c->minor);
361 }
362
363 int bch2_fs_chardev_init(struct bch_fs *c)
364 {
365         c->minor = idr_alloc(&bch_chardev_minor, c, 0, 0, GFP_KERNEL);
366         if (c->minor < 0)
367                 return c->minor;
368
369         c->chardev = device_create(bch_chardev_class, NULL,
370                                    MKDEV(bch_chardev_major, c->minor), NULL,
371                                    "bcachefs%u-ctl", c->minor);
372         if (IS_ERR(c->chardev))
373                 return PTR_ERR(c->chardev);
374
375         return 0;
376 }
377
378 void bch2_chardev_exit(void)
379 {
380         if (!IS_ERR_OR_NULL(bch_chardev_class))
381                 device_destroy(bch_chardev_class,
382                                MKDEV(bch_chardev_major, 255));
383         if (!IS_ERR_OR_NULL(bch_chardev_class))
384                 class_destroy(bch_chardev_class);
385         if (bch_chardev_major > 0)
386                 unregister_chrdev(bch_chardev_major, "bcachefs");
387 }
388
389 int __init bch2_chardev_init(void)
390 {
391         bch_chardev_major = register_chrdev(0, "bcachefs-ctl", &bch_chardev_fops);
392         if (bch_chardev_major < 0)
393                 return bch_chardev_major;
394
395         bch_chardev_class = class_create(THIS_MODULE, "bcachefs");
396         if (IS_ERR(bch_chardev_class))
397                 return PTR_ERR(bch_chardev_class);
398
399         bch_chardev = device_create(bch_chardev_class, NULL,
400                                     MKDEV(bch_chardev_major, 255),
401                                     NULL, "bcachefs-ctl");
402         if (IS_ERR(bch_chardev))
403                 return PTR_ERR(bch_chardev);
404
405         return 0;
406 }