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