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