]> git.sesse.net Git - bcachefs-tools-debian/blob - libbcache/chardev.c
bcache in userspace; userspace fsck
[bcachefs-tools-debian] / libbcache / chardev.c
1 /*
2  * This file adds support for a character device /dev/bcache that is used to
3  * atomically register a list of devices, remove a device from a cache_set
4  * and add a device to a cache set.
5  *
6  * Copyright (c) 2014 Datera, Inc.
7  *
8  */
9
10 #include "bcache.h"
11 #include "super.h"
12
13 #include <linux/module.h>
14 #include <linux/fs.h>
15 #include <linux/major.h>
16 #include <linux/cdev.h>
17 #include <linux/device.h>
18 #include <linux/ioctl.h>
19 #include <linux/uaccess.h>
20 #include <linux/slab.h>
21 #include <linux/bcache-ioctl.h>
22
23 static long bch_ioctl_assemble(struct bch_ioctl_assemble __user *user_arg)
24 {
25         struct bch_ioctl_assemble arg;
26         const char *err;
27         u64 *user_devs = NULL;
28         char **devs = NULL;
29         unsigned i;
30         int ret = -EFAULT;
31
32         if (copy_from_user(&arg, user_arg, sizeof(arg)))
33                 return -EFAULT;
34
35         user_devs = kmalloc_array(arg.nr_devs, sizeof(u64), GFP_KERNEL);
36         if (!devs)
37                 return -ENOMEM;
38
39         devs = kcalloc(arg.nr_devs, sizeof(char *), GFP_KERNEL);
40
41         if (copy_from_user(user_devs, user_arg->devs,
42                            sizeof(u64) * arg.nr_devs))
43                 goto err;
44
45         for (i = 0; i < arg.nr_devs; i++) {
46                 devs[i] = strndup_user((const char __user *)(unsigned long)
47                                        user_devs[i],
48                                        PATH_MAX);
49                 if (!devs[i]) {
50                         ret = -ENOMEM;
51                         goto err;
52                 }
53         }
54
55         err = bch_register_cache_set(devs, arg.nr_devs,
56                                      cache_set_opts_empty(),
57                                      NULL);
58         if (err) {
59                 pr_err("Could not register cache set: %s", err);
60                 ret = -EINVAL;
61                 goto err;
62         }
63
64         ret = 0;
65 err:
66         if (devs)
67                 for (i = 0; i < arg.nr_devs; i++)
68                         kfree(devs[i]);
69         kfree(devs);
70         return ret;
71 }
72
73 static long bch_ioctl_incremental(struct bch_ioctl_incremental __user *user_arg)
74 {
75         struct bch_ioctl_incremental arg;
76         const char *err;
77         char *path;
78
79         if (copy_from_user(&arg, user_arg, sizeof(arg)))
80                 return -EFAULT;
81
82         path = strndup_user((const char __user *)(unsigned long) arg.dev, PATH_MAX);
83         if (!path)
84                 return -ENOMEM;
85
86         err = bch_register_one(path);
87         kfree(path);
88
89         if (err) {
90                 pr_err("Could not register bcache devices: %s", err);
91                 return -EINVAL;
92         }
93
94         return 0;
95 }
96
97 static long bch_global_ioctl(unsigned cmd, void __user *arg)
98 {
99         switch (cmd) {
100         case BCH_IOCTL_ASSEMBLE:
101                 return bch_ioctl_assemble(arg);
102         case BCH_IOCTL_INCREMENTAL:
103                 return bch_ioctl_incremental(arg);
104         default:
105                 return -ENOTTY;
106         }
107 }
108
109 static long bch_ioctl_stop(struct cache_set *c)
110 {
111         bch_cache_set_stop(c);
112         return 0;
113 }
114
115 static long bch_ioctl_disk_add(struct cache_set *c,
116                                struct bch_ioctl_disk_add __user *user_arg)
117 {
118         struct bch_ioctl_disk_add arg;
119         char *path;
120         int ret;
121
122         if (copy_from_user(&arg, user_arg, sizeof(arg)))
123                 return -EFAULT;
124
125         path = strndup_user((const char __user *)(unsigned long) arg.dev, PATH_MAX);
126         if (!path)
127                 return -ENOMEM;
128
129         ret = bch_cache_set_add_cache(c, path);
130         kfree(path);
131
132         return ret;
133 }
134
135 /* returns with ref on ca->ref */
136 static struct cache *bch_device_lookup(struct cache_set *c,
137                                        const char __user *dev)
138 {
139         struct block_device *bdev;
140         struct cache *ca;
141         char *path;
142         unsigned i;
143
144         path = strndup_user(dev, PATH_MAX);
145         if (!path)
146                 return ERR_PTR(-ENOMEM);
147
148         bdev = lookup_bdev(strim(path));
149         kfree(path);
150         if (IS_ERR(bdev))
151                 return ERR_CAST(bdev);
152
153         for_each_cache(ca, c, i)
154                 if (ca->disk_sb.bdev == bdev)
155                         goto found;
156
157         ca = NULL;
158 found:
159         bdput(bdev);
160         return ca;
161 }
162
163 static long bch_ioctl_disk_remove(struct cache_set *c,
164                                   struct bch_ioctl_disk_remove __user *user_arg)
165 {
166         struct bch_ioctl_disk_remove arg;
167         struct cache *ca;
168         int ret;
169
170         if (copy_from_user(&arg, user_arg, sizeof(arg)))
171                 return -EFAULT;
172
173         ca = bch_device_lookup(c, (const char __user *)(unsigned long) arg.dev);
174         if (IS_ERR(ca))
175                 return PTR_ERR(ca);
176
177         ret = bch_cache_remove(ca, arg.flags & BCH_FORCE_IF_DATA_MISSING)
178                 ? 0 : -EBUSY;
179
180         percpu_ref_put(&ca->ref);
181         return ret;
182 }
183
184 static long bch_ioctl_disk_fail(struct cache_set *c,
185                                 struct bch_ioctl_disk_fail __user *user_arg)
186 {
187         struct bch_ioctl_disk_fail arg;
188         struct cache *ca;
189         int ret;
190
191         if (copy_from_user(&arg, user_arg, sizeof(arg)))
192                 return -EFAULT;
193
194         ca = bch_device_lookup(c, (const char __user *)(unsigned long) arg.dev);
195         if (IS_ERR(ca))
196                 return PTR_ERR(ca);
197
198         /* XXX: failed not actually implemented yet */
199         ret = bch_cache_remove(ca, true);
200
201         percpu_ref_put(&ca->ref);
202         return ret;
203 }
204
205 static struct cache_member *bch_uuid_lookup(struct cache_set *c, uuid_le uuid)
206 {
207         struct cache_member *mi = c->disk_mi;
208         unsigned i;
209
210         lockdep_assert_held(&bch_register_lock);
211
212         for (i = 0; i < c->disk_sb.nr_in_set; i++)
213                 if (!memcmp(&mi[i].uuid, &uuid, sizeof(uuid)))
214                         return &mi[i];
215
216         return NULL;
217 }
218
219 static long bch_ioctl_disk_remove_by_uuid(struct cache_set *c,
220                         struct bch_ioctl_disk_remove_by_uuid __user *user_arg)
221 {
222         struct bch_ioctl_disk_fail_by_uuid arg;
223         struct cache_member *m;
224         int ret = -ENOENT;
225
226         if (copy_from_user(&arg, user_arg, sizeof(arg)))
227                 return -EFAULT;
228
229         mutex_lock(&bch_register_lock);
230         if ((m = bch_uuid_lookup(c, arg.dev))) {
231                 /* XXX: */
232                 SET_CACHE_STATE(m, CACHE_FAILED);
233                 bcache_write_super(c);
234                 ret = 0;
235         }
236         mutex_unlock(&bch_register_lock);
237
238         return ret;
239 }
240
241 static long bch_ioctl_disk_fail_by_uuid(struct cache_set *c,
242                         struct bch_ioctl_disk_fail_by_uuid __user *user_arg)
243 {
244         struct bch_ioctl_disk_fail_by_uuid arg;
245         struct cache_member *m;
246         int ret = -ENOENT;
247
248         if (copy_from_user(&arg, user_arg, sizeof(arg)))
249                 return -EFAULT;
250
251         mutex_lock(&bch_register_lock);
252         if ((m = bch_uuid_lookup(c, arg.dev))) {
253                 SET_CACHE_STATE(m, CACHE_FAILED);
254                 bcache_write_super(c);
255                 ret = 0;
256         }
257         mutex_unlock(&bch_register_lock);
258
259         return ret;
260 }
261
262 static long bch_ioctl_query_uuid(struct cache_set *c,
263                         struct bch_ioctl_query_uuid __user *user_arg)
264 {
265         return copy_to_user(&user_arg->uuid,
266                             &c->disk_sb.user_uuid,
267                             sizeof(c->disk_sb.user_uuid));
268 }
269
270 long bch_cache_set_ioctl(struct cache_set *c, unsigned cmd, void __user *arg)
271 {
272         /* ioctls that don't require admin cap: */
273         switch (cmd) {
274         case BCH_IOCTL_QUERY_UUID:
275                 return bch_ioctl_query_uuid(c, arg);
276         }
277
278         if (!capable(CAP_SYS_ADMIN))
279                 return -EPERM;
280
281         /* ioctls that do require admin cap: */
282         switch (cmd) {
283         case BCH_IOCTL_RUN:
284                 return -ENOTTY;
285         case BCH_IOCTL_STOP:
286                 return bch_ioctl_stop(c);
287
288         case BCH_IOCTL_DISK_ADD:
289                 return bch_ioctl_disk_add(c, arg);
290         case BCH_IOCTL_DISK_REMOVE:
291                 return bch_ioctl_disk_remove(c, arg);
292         case BCH_IOCTL_DISK_FAIL:
293                 return bch_ioctl_disk_fail(c, arg);
294
295         case BCH_IOCTL_DISK_REMOVE_BY_UUID:
296                 return bch_ioctl_disk_remove_by_uuid(c, arg);
297         case BCH_IOCTL_DISK_FAIL_BY_UUID:
298                 return bch_ioctl_disk_fail_by_uuid(c, arg);
299
300         default:
301                 return -ENOTTY;
302         }
303 }
304
305 static long bch_chardev_ioctl(struct file *filp, unsigned cmd, unsigned long v)
306 {
307         struct cache_set *c = filp->private_data;
308         void __user *arg = (void __user *) v;
309
310         return c
311                 ? bch_cache_set_ioctl(c, cmd, arg)
312                 : bch_global_ioctl(cmd, arg);
313 }
314
315 const struct file_operations bch_chardev_fops = {
316         .owner          = THIS_MODULE,
317         .unlocked_ioctl = bch_chardev_ioctl,
318         .open           = nonseekable_open,
319 };