Commit | Line | Data |
---|---|---|
1c6fdbd8 KO |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #ifndef NO_BCACHEFS_CHARDEV | |
3 | ||
4 | #include "bcachefs.h" | |
1c6fdbd8 KO |
5 | #include "bcachefs_ioctl.h" |
6 | #include "buckets.h" | |
7 | #include "chardev.h" | |
e8c851b3 | 8 | #include "journal.h" |
1c6fdbd8 | 9 | #include "move.h" |
d2554263 | 10 | #include "recovery_passes.h" |
22502ac2 | 11 | #include "replicas.h" |
1c6fdbd8 KO |
12 | #include "super.h" |
13 | #include "super-io.h" | |
96f37eab | 14 | #include "thread_with_file.h" |
1c6fdbd8 | 15 | |
1c6fdbd8 KO |
16 | #include <linux/cdev.h> |
17 | #include <linux/device.h> | |
1c6fdbd8 KO |
18 | #include <linux/fs.h> |
19 | #include <linux/ioctl.h> | |
1c6fdbd8 KO |
20 | #include <linux/major.h> |
21 | #include <linux/sched/task.h> | |
22 | #include <linux/slab.h> | |
23 | #include <linux/uaccess.h> | |
24 | ||
25 | /* returns with ref on ca->ref */ | |
26 | static struct bch_dev *bch2_device_lookup(struct bch_fs *c, u64 dev, | |
27 | unsigned flags) | |
28 | { | |
29 | struct bch_dev *ca; | |
30 | ||
31 | if (flags & BCH_BY_INDEX) { | |
32 | if (dev >= c->sb.nr_devices) | |
33 | return ERR_PTR(-EINVAL); | |
34 | ||
4cd91e2f | 35 | ca = bch2_dev_tryget_noerror(c, dev); |
1c6fdbd8 KO |
36 | if (!ca) |
37 | return ERR_PTR(-EINVAL); | |
38 | } else { | |
39 | char *path; | |
40 | ||
41 | path = strndup_user((const char __user *) | |
42 | (unsigned long) dev, PATH_MAX); | |
43 | if (IS_ERR(path)) | |
44 | return ERR_CAST(path); | |
45 | ||
46 | ca = bch2_dev_lookup(c, path); | |
47 | kfree(path); | |
48 | } | |
49 | ||
50 | return ca; | |
51 | } | |
52 | ||
53 | #if 0 | |
54 | static long bch2_ioctl_assemble(struct bch_ioctl_assemble __user *user_arg) | |
55 | { | |
56 | struct bch_ioctl_assemble arg; | |
57 | struct bch_fs *c; | |
58 | u64 *user_devs = NULL; | |
59 | char **devs = NULL; | |
60 | unsigned i; | |
61 | int ret = -EFAULT; | |
62 | ||
63 | if (copy_from_user(&arg, user_arg, sizeof(arg))) | |
64 | return -EFAULT; | |
65 | ||
66 | if (arg.flags || arg.pad) | |
67 | return -EINVAL; | |
68 | ||
69 | user_devs = kmalloc_array(arg.nr_devs, sizeof(u64), GFP_KERNEL); | |
70 | if (!user_devs) | |
71 | return -ENOMEM; | |
72 | ||
73 | devs = kcalloc(arg.nr_devs, sizeof(char *), GFP_KERNEL); | |
74 | ||
75 | if (copy_from_user(user_devs, user_arg->devs, | |
76 | sizeof(u64) * arg.nr_devs)) | |
77 | goto err; | |
78 | ||
79 | for (i = 0; i < arg.nr_devs; i++) { | |
80 | devs[i] = strndup_user((const char __user *)(unsigned long) | |
81 | user_devs[i], | |
82 | PATH_MAX); | |
97ecc236 KO |
83 | ret= PTR_ERR_OR_ZERO(devs[i]); |
84 | if (ret) | |
1c6fdbd8 | 85 | goto err; |
1c6fdbd8 KO |
86 | } |
87 | ||
88 | c = bch2_fs_open(devs, arg.nr_devs, bch2_opts_empty()); | |
89 | ret = PTR_ERR_OR_ZERO(c); | |
90 | if (!ret) | |
91 | closure_put(&c->cl); | |
92 | err: | |
93 | if (devs) | |
94 | for (i = 0; i < arg.nr_devs; i++) | |
95 | kfree(devs[i]); | |
96 | kfree(devs); | |
97 | return ret; | |
98 | } | |
99 | ||
100 | static long bch2_ioctl_incremental(struct bch_ioctl_incremental __user *user_arg) | |
101 | { | |
102 | struct bch_ioctl_incremental arg; | |
103 | const char *err; | |
104 | char *path; | |
105 | ||
106 | if (copy_from_user(&arg, user_arg, sizeof(arg))) | |
107 | return -EFAULT; | |
108 | ||
109 | if (arg.flags || arg.pad) | |
110 | return -EINVAL; | |
111 | ||
112 | path = strndup_user((const char __user *)(unsigned long) arg.dev, PATH_MAX); | |
97ecc236 KO |
113 | ret = PTR_ERR_OR_ZERO(path); |
114 | if (ret) | |
115 | return ret; | |
1c6fdbd8 KO |
116 | |
117 | err = bch2_fs_open_incremental(path); | |
118 | kfree(path); | |
119 | ||
120 | if (err) { | |
121 | pr_err("Could not register bcachefs devices: %s", err); | |
122 | return -EINVAL; | |
123 | } | |
124 | ||
125 | return 0; | |
126 | } | |
127 | #endif | |
128 | ||
8408fa57 | 129 | struct fsck_thread { |
96f37eab | 130 | struct thread_with_stdio thr; |
267b801f | 131 | struct bch_fs *c; |
8408fa57 | 132 | struct bch_opts opts; |
8408fa57 KO |
133 | }; |
134 | ||
96f37eab | 135 | static void bch2_fsck_thread_exit(struct thread_with_stdio *_thr) |
8408fa57 | 136 | { |
96f37eab | 137 | struct fsck_thread *thr = container_of(_thr, struct fsck_thread, thr); |
8408fa57 KO |
138 | kfree(thr); |
139 | } | |
140 | ||
da23795e | 141 | static int bch2_fsck_offline_thread_fn(struct thread_with_stdio *stdio) |
8408fa57 | 142 | { |
a6777ca4 | 143 | struct fsck_thread *thr = container_of(stdio, struct fsck_thread, thr); |
374b3d38 | 144 | struct bch_fs *c = thr->c; |
8408fa57 | 145 | |
374b3d38 KO |
146 | int ret = PTR_ERR_OR_ZERO(c); |
147 | if (ret) | |
148 | return ret; | |
da23795e | 149 | |
374b3d38 KO |
150 | ret = bch2_fs_start(thr->c); |
151 | if (ret) | |
152 | goto err; | |
da23795e | 153 | |
374b3d38 | 154 | if (test_bit(BCH_FS_errors_fixed, &c->flags)) { |
da23795e | 155 | bch2_stdio_redirect_printf(&stdio->stdio, false, "%s: errors fixed\n", c->name); |
374b3d38 KO |
156 | ret |= 1; |
157 | } | |
158 | if (test_bit(BCH_FS_error, &c->flags)) { | |
da23795e | 159 | bch2_stdio_redirect_printf(&stdio->stdio, false, "%s: still has errors\n", c->name); |
374b3d38 KO |
160 | ret |= 4; |
161 | } | |
162 | err: | |
163 | bch2_fs_stop(c); | |
da23795e | 164 | return ret; |
8408fa57 KO |
165 | } |
166 | ||
ab6752e2 DW |
167 | static const struct thread_with_stdio_ops bch2_offline_fsck_ops = { |
168 | .exit = bch2_fsck_thread_exit, | |
169 | .fn = bch2_fsck_offline_thread_fn, | |
170 | }; | |
171 | ||
8408fa57 KO |
172 | static long bch2_ioctl_fsck_offline(struct bch_ioctl_fsck_offline __user *user_arg) |
173 | { | |
174 | struct bch_ioctl_fsck_offline arg; | |
175 | struct fsck_thread *thr = NULL; | |
374b3d38 | 176 | darray_str(devs) = {}; |
8408fa57 KO |
177 | long ret = 0; |
178 | ||
179 | if (copy_from_user(&arg, user_arg, sizeof(arg))) | |
180 | return -EFAULT; | |
181 | ||
182 | if (arg.flags) | |
183 | return -EINVAL; | |
184 | ||
185 | if (!capable(CAP_SYS_ADMIN)) | |
186 | return -EPERM; | |
187 | ||
374b3d38 KO |
188 | for (size_t i = 0; i < arg.nr_devs; i++) { |
189 | u64 dev_u64; | |
190 | ret = copy_from_user_errcode(&dev_u64, &user_arg->devs[i], sizeof(u64)); | |
191 | if (ret) | |
192 | goto err; | |
8408fa57 | 193 | |
374b3d38 KO |
194 | char *dev_str = strndup_user((char __user *)(unsigned long) dev_u64, PATH_MAX); |
195 | ret = PTR_ERR_OR_ZERO(dev_str); | |
196 | if (ret) | |
197 | goto err; | |
8408fa57 | 198 | |
374b3d38 KO |
199 | ret = darray_push(&devs, dev_str); |
200 | if (ret) { | |
201 | kfree(dev_str); | |
202 | goto err; | |
203 | } | |
8408fa57 KO |
204 | } |
205 | ||
374b3d38 KO |
206 | thr = kzalloc(sizeof(*thr), GFP_KERNEL); |
207 | if (!thr) { | |
208 | ret = -ENOMEM; | |
209 | goto err; | |
8408fa57 KO |
210 | } |
211 | ||
374b3d38 KO |
212 | thr->opts = bch2_opts_empty(); |
213 | ||
8408fa57 KO |
214 | if (arg.opts) { |
215 | char *optstr = strndup_user((char __user *)(unsigned long) arg.opts, 1 << 16); | |
216 | ||
217 | ret = PTR_ERR_OR_ZERO(optstr) ?: | |
218 | bch2_parse_mount_opts(NULL, &thr->opts, optstr); | |
219 | kfree(optstr); | |
220 | ||
221 | if (ret) | |
222 | goto err; | |
223 | } | |
224 | ||
96f37eab | 225 | opt_set(thr->opts, stdio, (u64)(unsigned long)&thr->thr.stdio); |
8408fa57 | 226 | |
374b3d38 KO |
227 | /* We need request_key() to be called before we punt to kthread: */ |
228 | opt_set(thr->opts, nostart, true); | |
229 | ||
02bed83d KO |
230 | bch2_thread_with_stdio_init(&thr->thr, &bch2_offline_fsck_ops); |
231 | ||
374b3d38 KO |
232 | thr->c = bch2_fs_open(devs.data, arg.nr_devs, thr->opts); |
233 | ||
05801b65 KO |
234 | if (!IS_ERR(thr->c) && |
235 | thr->c->opts.errors == BCH_ON_ERROR_panic) | |
236 | thr->c->opts.errors = BCH_ON_ERROR_ro; | |
237 | ||
02bed83d | 238 | ret = __bch2_run_thread_with_stdio(&thr->thr); |
374b3d38 KO |
239 | out: |
240 | darray_for_each(devs, i) | |
241 | kfree(*i); | |
242 | darray_exit(&devs); | |
8408fa57 | 243 | return ret; |
374b3d38 KO |
244 | err: |
245 | if (thr) | |
246 | bch2_fsck_thread_exit(&thr->thr); | |
247 | pr_err("ret %s", bch2_err_str(ret)); | |
248 | goto out; | |
8408fa57 KO |
249 | } |
250 | ||
1c6fdbd8 KO |
251 | static long bch2_global_ioctl(unsigned cmd, void __user *arg) |
252 | { | |
8408fa57 KO |
253 | long ret; |
254 | ||
1c6fdbd8 KO |
255 | switch (cmd) { |
256 | #if 0 | |
257 | case BCH_IOCTL_ASSEMBLE: | |
258 | return bch2_ioctl_assemble(arg); | |
259 | case BCH_IOCTL_INCREMENTAL: | |
260 | return bch2_ioctl_incremental(arg); | |
261 | #endif | |
8408fa57 KO |
262 | case BCH_IOCTL_FSCK_OFFLINE: { |
263 | ret = bch2_ioctl_fsck_offline(arg); | |
264 | break; | |
265 | } | |
1c6fdbd8 | 266 | default: |
8408fa57 KO |
267 | ret = -ENOTTY; |
268 | break; | |
1c6fdbd8 | 269 | } |
8408fa57 KO |
270 | |
271 | if (ret < 0) | |
272 | ret = bch2_err_class(ret); | |
273 | return ret; | |
1c6fdbd8 KO |
274 | } |
275 | ||
276 | static long bch2_ioctl_query_uuid(struct bch_fs *c, | |
277 | struct bch_ioctl_query_uuid __user *user_arg) | |
278 | { | |
e6674dec KO |
279 | return copy_to_user_errcode(&user_arg->uuid, &c->sb.user_uuid, |
280 | sizeof(c->sb.user_uuid)); | |
1c6fdbd8 KO |
281 | } |
282 | ||
283 | #if 0 | |
284 | static long bch2_ioctl_start(struct bch_fs *c, struct bch_ioctl_start arg) | |
285 | { | |
a515d0a5 TGR |
286 | if (!capable(CAP_SYS_ADMIN)) |
287 | return -EPERM; | |
288 | ||
1c6fdbd8 KO |
289 | if (arg.flags || arg.pad) |
290 | return -EINVAL; | |
291 | ||
619f5bee | 292 | return bch2_fs_start(c); |
1c6fdbd8 KO |
293 | } |
294 | ||
295 | static long bch2_ioctl_stop(struct bch_fs *c) | |
296 | { | |
a515d0a5 TGR |
297 | if (!capable(CAP_SYS_ADMIN)) |
298 | return -EPERM; | |
299 | ||
1c6fdbd8 KO |
300 | bch2_fs_stop(c); |
301 | return 0; | |
302 | } | |
303 | #endif | |
304 | ||
305 | static long bch2_ioctl_disk_add(struct bch_fs *c, struct bch_ioctl_disk arg) | |
306 | { | |
307 | char *path; | |
308 | int ret; | |
309 | ||
a515d0a5 TGR |
310 | if (!capable(CAP_SYS_ADMIN)) |
311 | return -EPERM; | |
312 | ||
1c6fdbd8 KO |
313 | if (arg.flags || arg.pad) |
314 | return -EINVAL; | |
315 | ||
316 | path = strndup_user((const char __user *)(unsigned long) arg.dev, PATH_MAX); | |
97ecc236 KO |
317 | ret = PTR_ERR_OR_ZERO(path); |
318 | if (ret) | |
319 | return ret; | |
1c6fdbd8 KO |
320 | |
321 | ret = bch2_dev_add(c, path); | |
322 | kfree(path); | |
323 | ||
324 | return ret; | |
325 | } | |
326 | ||
327 | static long bch2_ioctl_disk_remove(struct bch_fs *c, struct bch_ioctl_disk arg) | |
328 | { | |
329 | struct bch_dev *ca; | |
330 | ||
a515d0a5 TGR |
331 | if (!capable(CAP_SYS_ADMIN)) |
332 | return -EPERM; | |
333 | ||
1c6fdbd8 KO |
334 | if ((arg.flags & ~(BCH_FORCE_IF_DATA_LOST| |
335 | BCH_FORCE_IF_METADATA_LOST| | |
336 | BCH_FORCE_IF_DEGRADED| | |
337 | BCH_BY_INDEX)) || | |
338 | arg.pad) | |
339 | return -EINVAL; | |
340 | ||
341 | ca = bch2_device_lookup(c, arg.dev, arg.flags); | |
342 | if (IS_ERR(ca)) | |
343 | return PTR_ERR(ca); | |
344 | ||
345 | return bch2_dev_remove(c, ca, arg.flags); | |
346 | } | |
347 | ||
348 | static long bch2_ioctl_disk_online(struct bch_fs *c, struct bch_ioctl_disk arg) | |
349 | { | |
350 | char *path; | |
351 | int ret; | |
352 | ||
a515d0a5 TGR |
353 | if (!capable(CAP_SYS_ADMIN)) |
354 | return -EPERM; | |
355 | ||
1c6fdbd8 KO |
356 | if (arg.flags || arg.pad) |
357 | return -EINVAL; | |
358 | ||
359 | path = strndup_user((const char __user *)(unsigned long) arg.dev, PATH_MAX); | |
97ecc236 KO |
360 | ret = PTR_ERR_OR_ZERO(path); |
361 | if (ret) | |
362 | return ret; | |
1c6fdbd8 KO |
363 | |
364 | ret = bch2_dev_online(c, path); | |
365 | kfree(path); | |
366 | return ret; | |
367 | } | |
368 | ||
369 | static long bch2_ioctl_disk_offline(struct bch_fs *c, struct bch_ioctl_disk arg) | |
370 | { | |
371 | struct bch_dev *ca; | |
372 | int ret; | |
373 | ||
a515d0a5 TGR |
374 | if (!capable(CAP_SYS_ADMIN)) |
375 | return -EPERM; | |
376 | ||
1c6fdbd8 KO |
377 | if ((arg.flags & ~(BCH_FORCE_IF_DATA_LOST| |
378 | BCH_FORCE_IF_METADATA_LOST| | |
379 | BCH_FORCE_IF_DEGRADED| | |
380 | BCH_BY_INDEX)) || | |
381 | arg.pad) | |
382 | return -EINVAL; | |
383 | ||
384 | ca = bch2_device_lookup(c, arg.dev, arg.flags); | |
385 | if (IS_ERR(ca)) | |
386 | return PTR_ERR(ca); | |
387 | ||
388 | ret = bch2_dev_offline(c, ca, arg.flags); | |
f295298b | 389 | bch2_dev_put(ca); |
1c6fdbd8 KO |
390 | return ret; |
391 | } | |
392 | ||
393 | static long bch2_ioctl_disk_set_state(struct bch_fs *c, | |
394 | struct bch_ioctl_disk_set_state arg) | |
395 | { | |
396 | struct bch_dev *ca; | |
397 | int ret; | |
398 | ||
a515d0a5 TGR |
399 | if (!capable(CAP_SYS_ADMIN)) |
400 | return -EPERM; | |
401 | ||
1c6fdbd8 KO |
402 | if ((arg.flags & ~(BCH_FORCE_IF_DATA_LOST| |
403 | BCH_FORCE_IF_METADATA_LOST| | |
404 | BCH_FORCE_IF_DEGRADED| | |
405 | BCH_BY_INDEX)) || | |
f0412b6e DR |
406 | arg.pad[0] || arg.pad[1] || arg.pad[2] || |
407 | arg.new_state >= BCH_MEMBER_STATE_NR) | |
1c6fdbd8 KO |
408 | return -EINVAL; |
409 | ||
410 | ca = bch2_device_lookup(c, arg.dev, arg.flags); | |
411 | if (IS_ERR(ca)) | |
412 | return PTR_ERR(ca); | |
413 | ||
414 | ret = bch2_dev_set_state(c, ca, arg.new_state, arg.flags); | |
e2fcf7f6 KO |
415 | if (ret) |
416 | bch_err(c, "Error setting device state: %s", bch2_err_str(ret)); | |
1c6fdbd8 | 417 | |
f295298b | 418 | bch2_dev_put(ca); |
1c6fdbd8 KO |
419 | return ret; |
420 | } | |
421 | ||
422 | struct bch_data_ctx { | |
bbefcd91 KO |
423 | struct thread_with_file thr; |
424 | ||
1c6fdbd8 KO |
425 | struct bch_fs *c; |
426 | struct bch_ioctl_data arg; | |
427 | struct bch_move_stats stats; | |
1c6fdbd8 KO |
428 | }; |
429 | ||
430 | static int bch2_data_thread(void *arg) | |
431 | { | |
bbefcd91 | 432 | struct bch_data_ctx *ctx = container_of(arg, struct bch_data_ctx, thr); |
1c6fdbd8 | 433 | |
bbefcd91 | 434 | ctx->thr.ret = bch2_data_job(ctx->c, &ctx->stats, ctx->arg); |
1c6fdbd8 KO |
435 | ctx->stats.data_type = U8_MAX; |
436 | return 0; | |
437 | } | |
438 | ||
439 | static int bch2_data_job_release(struct inode *inode, struct file *file) | |
440 | { | |
bbefcd91 | 441 | struct bch_data_ctx *ctx = container_of(file->private_data, struct bch_data_ctx, thr); |
1c6fdbd8 | 442 | |
96f37eab | 443 | bch2_thread_with_file_exit(&ctx->thr); |
1c6fdbd8 KO |
444 | kfree(ctx); |
445 | return 0; | |
446 | } | |
447 | ||
448 | static ssize_t bch2_data_job_read(struct file *file, char __user *buf, | |
449 | size_t len, loff_t *ppos) | |
450 | { | |
bbefcd91 | 451 | struct bch_data_ctx *ctx = container_of(file->private_data, struct bch_data_ctx, thr); |
1c6fdbd8 KO |
452 | struct bch_fs *c = ctx->c; |
453 | struct bch_ioctl_data_event e = { | |
454 | .type = BCH_DATA_EVENT_PROGRESS, | |
455 | .p.data_type = ctx->stats.data_type, | |
d5eade93 KO |
456 | .p.btree_id = ctx->stats.pos.btree, |
457 | .p.pos = ctx->stats.pos.pos, | |
1c6fdbd8 | 458 | .p.sectors_done = atomic64_read(&ctx->stats.sectors_seen), |
5663a415 | 459 | .p.sectors_total = bch2_fs_usage_read_short(c).used, |
1c6fdbd8 KO |
460 | }; |
461 | ||
462 | if (len < sizeof(e)) | |
463 | return -EINVAL; | |
464 | ||
e6674dec | 465 | return copy_to_user_errcode(buf, &e, sizeof(e)) ?: sizeof(e); |
1c6fdbd8 KO |
466 | } |
467 | ||
468 | static const struct file_operations bcachefs_data_ops = { | |
469 | .release = bch2_data_job_release, | |
470 | .read = bch2_data_job_read, | |
471 | .llseek = no_llseek, | |
472 | }; | |
473 | ||
474 | static long bch2_ioctl_data(struct bch_fs *c, | |
475 | struct bch_ioctl_data arg) | |
476 | { | |
bbefcd91 KO |
477 | struct bch_data_ctx *ctx; |
478 | int ret; | |
1c6fdbd8 | 479 | |
a515d0a5 TGR |
480 | if (!capable(CAP_SYS_ADMIN)) |
481 | return -EPERM; | |
482 | ||
1c6fdbd8 KO |
483 | if (arg.op >= BCH_DATA_OP_NR || arg.flags) |
484 | return -EINVAL; | |
485 | ||
486 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); | |
487 | if (!ctx) | |
488 | return -ENOMEM; | |
489 | ||
490 | ctx->c = c; | |
491 | ctx->arg = arg; | |
492 | ||
96f37eab KO |
493 | ret = bch2_run_thread_with_file(&ctx->thr, |
494 | &bcachefs_data_ops, | |
495 | bch2_data_thread); | |
1c6fdbd8 | 496 | if (ret < 0) |
bbefcd91 | 497 | kfree(ctx); |
1c6fdbd8 KO |
498 | return ret; |
499 | } | |
500 | ||
22502ac2 KO |
501 | static long bch2_ioctl_fs_usage(struct bch_fs *c, |
502 | struct bch_ioctl_fs_usage __user *user_arg) | |
1c6fdbd8 | 503 | { |
22502ac2 KO |
504 | struct bch_ioctl_fs_usage *arg = NULL; |
505 | struct bch_replicas_usage *dst_e, *dst_end; | |
506 | struct bch_fs_usage_online *src; | |
507 | u32 replica_entries_bytes; | |
508 | unsigned i; | |
509 | int ret = 0; | |
1c6fdbd8 | 510 | |
3c471b65 | 511 | if (!test_bit(BCH_FS_started, &c->flags)) |
1c6fdbd8 KO |
512 | return -EINVAL; |
513 | ||
22502ac2 | 514 | if (get_user(replica_entries_bytes, &user_arg->replica_entries_bytes)) |
1c6fdbd8 KO |
515 | return -EFAULT; |
516 | ||
4ba985b8 | 517 | arg = kzalloc(size_add(sizeof(*arg), replica_entries_bytes), GFP_KERNEL); |
22502ac2 KO |
518 | if (!arg) |
519 | return -ENOMEM; | |
1c6fdbd8 | 520 | |
22502ac2 KO |
521 | src = bch2_fs_usage_read(c); |
522 | if (!src) { | |
523 | ret = -ENOMEM; | |
524 | goto err; | |
1c6fdbd8 KO |
525 | } |
526 | ||
22502ac2 KO |
527 | arg->capacity = c->capacity; |
528 | arg->used = bch2_fs_sectors_used(c, src); | |
529 | arg->online_reserved = src->online_reserved; | |
1c6fdbd8 | 530 | |
22502ac2 KO |
531 | for (i = 0; i < BCH_REPLICAS_MAX; i++) |
532 | arg->persistent_reserved[i] = src->u.persistent_reserved[i]; | |
7ef2a73a | 533 | |
22502ac2 KO |
534 | dst_e = arg->replicas; |
535 | dst_end = (void *) arg->replicas + replica_entries_bytes; | |
7ef2a73a | 536 | |
22502ac2 | 537 | for (i = 0; i < c->replicas.nr; i++) { |
086a52f7 | 538 | struct bch_replicas_entry_v1 *src_e = |
22502ac2 | 539 | cpu_replicas_entry(&c->replicas, i); |
7ef2a73a | 540 | |
faf1a5f4 DR |
541 | /* check that we have enough space for one replicas entry */ |
542 | if (dst_e + 1 > dst_end) { | |
22502ac2 KO |
543 | ret = -ERANGE; |
544 | break; | |
1c6fdbd8 KO |
545 | } |
546 | ||
22502ac2 KO |
547 | dst_e->sectors = src->u.replicas[i]; |
548 | dst_e->r = *src_e; | |
549 | ||
550 | /* recheck after setting nr_devs: */ | |
551 | if (replicas_usage_next(dst_e) > dst_end) { | |
552 | ret = -ERANGE; | |
553 | break; | |
554 | } | |
7ef2a73a | 555 | |
22502ac2 KO |
556 | memcpy(dst_e->r.devs, src_e->devs, src_e->nr_devs); |
557 | ||
558 | dst_e = replicas_usage_next(dst_e); | |
1c6fdbd8 KO |
559 | } |
560 | ||
22502ac2 | 561 | arg->replica_entries_bytes = (void *) dst_e - (void *) arg->replicas; |
1c6fdbd8 | 562 | |
22502ac2 KO |
563 | percpu_up_read(&c->mark_lock); |
564 | kfree(src); | |
1c6fdbd8 | 565 | |
301e0237 DC |
566 | if (ret) |
567 | goto err; | |
e6674dec KO |
568 | |
569 | ret = copy_to_user_errcode(user_arg, arg, | |
570 | sizeof(*arg) + arg->replica_entries_bytes); | |
22502ac2 KO |
571 | err: |
572 | kfree(arg); | |
573 | return ret; | |
574 | } | |
575 | ||
e6674dec | 576 | /* obsolete, didn't allow for new data types: */ |
22502ac2 KO |
577 | static long bch2_ioctl_dev_usage(struct bch_fs *c, |
578 | struct bch_ioctl_dev_usage __user *user_arg) | |
579 | { | |
580 | struct bch_ioctl_dev_usage arg; | |
581 | struct bch_dev_usage src; | |
582 | struct bch_dev *ca; | |
583 | unsigned i; | |
584 | ||
3c471b65 | 585 | if (!test_bit(BCH_FS_started, &c->flags)) |
22502ac2 | 586 | return -EINVAL; |
1c6fdbd8 | 587 | |
22502ac2 KO |
588 | if (copy_from_user(&arg, user_arg, sizeof(arg))) |
589 | return -EFAULT; | |
590 | ||
591 | if ((arg.flags & ~BCH_BY_INDEX) || | |
592 | arg.pad[0] || | |
593 | arg.pad[1] || | |
594 | arg.pad[2]) | |
595 | return -EINVAL; | |
596 | ||
597 | ca = bch2_device_lookup(c, arg.dev, arg.flags); | |
598 | if (IS_ERR(ca)) | |
599 | return PTR_ERR(ca); | |
600 | ||
3d080aa5 | 601 | src = bch2_dev_usage_read(ca); |
22502ac2 | 602 | |
649a9b68 KO |
603 | arg.state = ca->mi.state; |
604 | arg.bucket_size = ca->mi.bucket_size; | |
605 | arg.nr_buckets = ca->mi.nbuckets - ca->mi.first_bucket; | |
22502ac2 KO |
606 | |
607 | for (i = 0; i < BCH_DATA_NR; i++) { | |
822835ff KO |
608 | arg.d[i].buckets = src.d[i].buckets; |
609 | arg.d[i].sectors = src.d[i].sectors; | |
610 | arg.d[i].fragmented = src.d[i].fragmented; | |
1c6fdbd8 KO |
611 | } |
612 | ||
f295298b | 613 | bch2_dev_put(ca); |
22502ac2 | 614 | |
e6674dec KO |
615 | return copy_to_user_errcode(user_arg, &arg, sizeof(arg)); |
616 | } | |
617 | ||
618 | static long bch2_ioctl_dev_usage_v2(struct bch_fs *c, | |
619 | struct bch_ioctl_dev_usage_v2 __user *user_arg) | |
620 | { | |
621 | struct bch_ioctl_dev_usage_v2 arg; | |
622 | struct bch_dev_usage src; | |
623 | struct bch_dev *ca; | |
624 | int ret = 0; | |
625 | ||
3c471b65 | 626 | if (!test_bit(BCH_FS_started, &c->flags)) |
e6674dec KO |
627 | return -EINVAL; |
628 | ||
629 | if (copy_from_user(&arg, user_arg, sizeof(arg))) | |
301e0237 DC |
630 | return -EFAULT; |
631 | ||
e6674dec KO |
632 | if ((arg.flags & ~BCH_BY_INDEX) || |
633 | arg.pad[0] || | |
634 | arg.pad[1] || | |
635 | arg.pad[2]) | |
636 | return -EINVAL; | |
637 | ||
638 | ca = bch2_device_lookup(c, arg.dev, arg.flags); | |
639 | if (IS_ERR(ca)) | |
640 | return PTR_ERR(ca); | |
641 | ||
642 | src = bch2_dev_usage_read(ca); | |
643 | ||
644 | arg.state = ca->mi.state; | |
645 | arg.bucket_size = ca->mi.bucket_size; | |
646 | arg.nr_data_types = min(arg.nr_data_types, BCH_DATA_NR); | |
647 | arg.nr_buckets = ca->mi.nbuckets - ca->mi.first_bucket; | |
648 | ||
649 | ret = copy_to_user_errcode(user_arg, &arg, sizeof(arg)); | |
650 | if (ret) | |
651 | goto err; | |
652 | ||
653 | for (unsigned i = 0; i < arg.nr_data_types; i++) { | |
654 | struct bch_ioctl_dev_usage_type t = { | |
655 | .buckets = src.d[i].buckets, | |
656 | .sectors = src.d[i].sectors, | |
657 | .fragmented = src.d[i].fragmented, | |
658 | }; | |
659 | ||
660 | ret = copy_to_user_errcode(&user_arg->d[i], &t, sizeof(t)); | |
661 | if (ret) | |
662 | goto err; | |
663 | } | |
664 | err: | |
f295298b | 665 | bch2_dev_put(ca); |
e6674dec | 666 | return ret; |
1c6fdbd8 KO |
667 | } |
668 | ||
669 | static long bch2_ioctl_read_super(struct bch_fs *c, | |
670 | struct bch_ioctl_read_super arg) | |
671 | { | |
672 | struct bch_dev *ca = NULL; | |
673 | struct bch_sb *sb; | |
674 | int ret = 0; | |
675 | ||
a515d0a5 TGR |
676 | if (!capable(CAP_SYS_ADMIN)) |
677 | return -EPERM; | |
678 | ||
1c6fdbd8 KO |
679 | if ((arg.flags & ~(BCH_BY_INDEX|BCH_READ_DEV)) || |
680 | arg.pad) | |
681 | return -EINVAL; | |
682 | ||
683 | mutex_lock(&c->sb_lock); | |
684 | ||
685 | if (arg.flags & BCH_READ_DEV) { | |
686 | ca = bch2_device_lookup(c, arg.dev, arg.flags); | |
f295298b KO |
687 | ret = PTR_ERR_OR_ZERO(ca); |
688 | if (ret) | |
689 | goto err_unlock; | |
1c6fdbd8 KO |
690 | |
691 | sb = ca->disk_sb.sb; | |
692 | } else { | |
693 | sb = c->disk_sb.sb; | |
694 | } | |
695 | ||
696 | if (vstruct_bytes(sb) > arg.size) { | |
697 | ret = -ERANGE; | |
698 | goto err; | |
699 | } | |
700 | ||
e6674dec KO |
701 | ret = copy_to_user_errcode((void __user *)(unsigned long)arg.sb, sb, |
702 | vstruct_bytes(sb)); | |
1c6fdbd8 | 703 | err: |
f295298b KO |
704 | bch2_dev_put(ca); |
705 | err_unlock: | |
1c6fdbd8 KO |
706 | mutex_unlock(&c->sb_lock); |
707 | return ret; | |
708 | } | |
709 | ||
710 | static long bch2_ioctl_disk_get_idx(struct bch_fs *c, | |
711 | struct bch_ioctl_disk_get_idx arg) | |
712 | { | |
713 | dev_t dev = huge_decode_dev(arg.dev); | |
1c6fdbd8 | 714 | |
a515d0a5 TGR |
715 | if (!capable(CAP_SYS_ADMIN)) |
716 | return -EPERM; | |
717 | ||
eacb2574 KO |
718 | if (!dev) |
719 | return -EINVAL; | |
720 | ||
9fea2274 | 721 | for_each_online_member(c, ca) |
eacb2574 | 722 | if (ca->dev == dev) { |
1c6fdbd8 | 723 | percpu_ref_put(&ca->io_ref); |
9fea2274 | 724 | return ca->dev_idx; |
1c6fdbd8 KO |
725 | } |
726 | ||
e47a390a | 727 | return -BCH_ERR_ENOENT_dev_idx_not_found; |
1c6fdbd8 KO |
728 | } |
729 | ||
730 | static long bch2_ioctl_disk_resize(struct bch_fs *c, | |
731 | struct bch_ioctl_disk_resize arg) | |
732 | { | |
733 | struct bch_dev *ca; | |
734 | int ret; | |
735 | ||
a515d0a5 TGR |
736 | if (!capable(CAP_SYS_ADMIN)) |
737 | return -EPERM; | |
738 | ||
1c6fdbd8 KO |
739 | if ((arg.flags & ~BCH_BY_INDEX) || |
740 | arg.pad) | |
741 | return -EINVAL; | |
742 | ||
743 | ca = bch2_device_lookup(c, arg.dev, arg.flags); | |
744 | if (IS_ERR(ca)) | |
745 | return PTR_ERR(ca); | |
746 | ||
747 | ret = bch2_dev_resize(c, ca, arg.nbuckets); | |
748 | ||
f295298b | 749 | bch2_dev_put(ca); |
1c6fdbd8 KO |
750 | return ret; |
751 | } | |
752 | ||
e8c851b3 KO |
753 | static long bch2_ioctl_disk_resize_journal(struct bch_fs *c, |
754 | struct bch_ioctl_disk_resize_journal arg) | |
755 | { | |
756 | struct bch_dev *ca; | |
757 | int ret; | |
758 | ||
a515d0a5 TGR |
759 | if (!capable(CAP_SYS_ADMIN)) |
760 | return -EPERM; | |
761 | ||
e8c851b3 KO |
762 | if ((arg.flags & ~BCH_BY_INDEX) || |
763 | arg.pad) | |
764 | return -EINVAL; | |
765 | ||
4b33a191 KO |
766 | if (arg.nbuckets > U32_MAX) |
767 | return -EINVAL; | |
768 | ||
e8c851b3 KO |
769 | ca = bch2_device_lookup(c, arg.dev, arg.flags); |
770 | if (IS_ERR(ca)) | |
771 | return PTR_ERR(ca); | |
772 | ||
773 | ret = bch2_set_nr_journal_buckets(c, ca, arg.nbuckets); | |
774 | ||
f295298b | 775 | bch2_dev_put(ca); |
e8c851b3 KO |
776 | return ret; |
777 | } | |
778 | ||
da23795e | 779 | static int bch2_fsck_online_thread_fn(struct thread_with_stdio *stdio) |
267b801f | 780 | { |
a6777ca4 | 781 | struct fsck_thread *thr = container_of(stdio, struct fsck_thread, thr); |
267b801f KO |
782 | struct bch_fs *c = thr->c; |
783 | ||
96f37eab KO |
784 | c->stdio_filter = current; |
785 | c->stdio = &thr->thr.stdio; | |
267b801f KO |
786 | |
787 | /* | |
788 | * XXX: can we figure out a way to do this without mucking with c->opts? | |
789 | */ | |
d55ddf6e | 790 | unsigned old_fix_errors = c->opts.fix_errors; |
267b801f KO |
791 | if (opt_defined(thr->opts, fix_errors)) |
792 | c->opts.fix_errors = thr->opts.fix_errors; | |
d55ddf6e KO |
793 | else |
794 | c->opts.fix_errors = FSCK_FIX_ask; | |
795 | ||
267b801f | 796 | c->opts.fsck = true; |
d55ddf6e | 797 | set_bit(BCH_FS_fsck_running, &c->flags); |
267b801f KO |
798 | |
799 | c->curr_recovery_pass = BCH_RECOVERY_PASS_check_alloc_info; | |
d55ddf6e KO |
800 | int ret = bch2_run_online_recovery_passes(c); |
801 | ||
802 | clear_bit(BCH_FS_fsck_running, &c->flags); | |
803 | bch_err_fn(c, ret); | |
267b801f | 804 | |
96f37eab KO |
805 | c->stdio = NULL; |
806 | c->stdio_filter = NULL; | |
d55ddf6e | 807 | c->opts.fix_errors = old_fix_errors; |
267b801f | 808 | |
267b801f KO |
809 | up(&c->online_fsck_mutex); |
810 | bch2_ro_ref_put(c); | |
da23795e | 811 | return ret; |
267b801f KO |
812 | } |
813 | ||
ab6752e2 DW |
814 | static const struct thread_with_stdio_ops bch2_online_fsck_ops = { |
815 | .exit = bch2_fsck_thread_exit, | |
816 | .fn = bch2_fsck_online_thread_fn, | |
817 | }; | |
818 | ||
267b801f KO |
819 | static long bch2_ioctl_fsck_online(struct bch_fs *c, |
820 | struct bch_ioctl_fsck_online arg) | |
821 | { | |
822 | struct fsck_thread *thr = NULL; | |
823 | long ret = 0; | |
824 | ||
825 | if (arg.flags) | |
826 | return -EINVAL; | |
827 | ||
828 | if (!capable(CAP_SYS_ADMIN)) | |
829 | return -EPERM; | |
830 | ||
831 | if (!bch2_ro_ref_tryget(c)) | |
832 | return -EROFS; | |
833 | ||
834 | if (down_trylock(&c->online_fsck_mutex)) { | |
835 | bch2_ro_ref_put(c); | |
836 | return -EAGAIN; | |
837 | } | |
838 | ||
839 | thr = kzalloc(sizeof(*thr), GFP_KERNEL); | |
840 | if (!thr) { | |
841 | ret = -ENOMEM; | |
842 | goto err; | |
843 | } | |
844 | ||
845 | thr->c = c; | |
846 | thr->opts = bch2_opts_empty(); | |
267b801f KO |
847 | |
848 | if (arg.opts) { | |
849 | char *optstr = strndup_user((char __user *)(unsigned long) arg.opts, 1 << 16); | |
850 | ||
851 | ret = PTR_ERR_OR_ZERO(optstr) ?: | |
852 | bch2_parse_mount_opts(c, &thr->opts, optstr); | |
853 | kfree(optstr); | |
854 | ||
855 | if (ret) | |
856 | goto err; | |
857 | } | |
858 | ||
ab6752e2 | 859 | ret = bch2_run_thread_with_stdio(&thr->thr, &bch2_online_fsck_ops); |
267b801f KO |
860 | err: |
861 | if (ret < 0) { | |
862 | bch_err_fn(c, ret); | |
863 | if (thr) | |
96f37eab | 864 | bch2_fsck_thread_exit(&thr->thr); |
267b801f KO |
865 | up(&c->online_fsck_mutex); |
866 | bch2_ro_ref_put(c); | |
867 | } | |
868 | return ret; | |
869 | } | |
870 | ||
1c6fdbd8 KO |
871 | #define BCH_IOCTL(_name, _argtype) \ |
872 | do { \ | |
873 | _argtype i; \ | |
874 | \ | |
875 | if (copy_from_user(&i, arg, sizeof(i))) \ | |
876 | return -EFAULT; \ | |
e2fcf7f6 KO |
877 | ret = bch2_ioctl_##_name(c, i); \ |
878 | goto out; \ | |
1c6fdbd8 KO |
879 | } while (0) |
880 | ||
881 | long bch2_fs_ioctl(struct bch_fs *c, unsigned cmd, void __user *arg) | |
882 | { | |
e2fcf7f6 KO |
883 | long ret; |
884 | ||
1c6fdbd8 KO |
885 | switch (cmd) { |
886 | case BCH_IOCTL_QUERY_UUID: | |
887 | return bch2_ioctl_query_uuid(c, arg); | |
22502ac2 KO |
888 | case BCH_IOCTL_FS_USAGE: |
889 | return bch2_ioctl_fs_usage(c, arg); | |
890 | case BCH_IOCTL_DEV_USAGE: | |
891 | return bch2_ioctl_dev_usage(c, arg); | |
e6674dec KO |
892 | case BCH_IOCTL_DEV_USAGE_V2: |
893 | return bch2_ioctl_dev_usage_v2(c, arg); | |
1c6fdbd8 KO |
894 | #if 0 |
895 | case BCH_IOCTL_START: | |
896 | BCH_IOCTL(start, struct bch_ioctl_start); | |
897 | case BCH_IOCTL_STOP: | |
898 | return bch2_ioctl_stop(c); | |
899 | #endif | |
900 | case BCH_IOCTL_READ_SUPER: | |
901 | BCH_IOCTL(read_super, struct bch_ioctl_read_super); | |
902 | case BCH_IOCTL_DISK_GET_IDX: | |
903 | BCH_IOCTL(disk_get_idx, struct bch_ioctl_disk_get_idx); | |
904 | } | |
905 | ||
3c471b65 | 906 | if (!test_bit(BCH_FS_started, &c->flags)) |
1c6fdbd8 KO |
907 | return -EINVAL; |
908 | ||
1c6fdbd8 KO |
909 | switch (cmd) { |
910 | case BCH_IOCTL_DISK_ADD: | |
911 | BCH_IOCTL(disk_add, struct bch_ioctl_disk); | |
912 | case BCH_IOCTL_DISK_REMOVE: | |
913 | BCH_IOCTL(disk_remove, struct bch_ioctl_disk); | |
914 | case BCH_IOCTL_DISK_ONLINE: | |
915 | BCH_IOCTL(disk_online, struct bch_ioctl_disk); | |
916 | case BCH_IOCTL_DISK_OFFLINE: | |
917 | BCH_IOCTL(disk_offline, struct bch_ioctl_disk); | |
918 | case BCH_IOCTL_DISK_SET_STATE: | |
919 | BCH_IOCTL(disk_set_state, struct bch_ioctl_disk_set_state); | |
920 | case BCH_IOCTL_DATA: | |
921 | BCH_IOCTL(data, struct bch_ioctl_data); | |
922 | case BCH_IOCTL_DISK_RESIZE: | |
923 | BCH_IOCTL(disk_resize, struct bch_ioctl_disk_resize); | |
e8c851b3 KO |
924 | case BCH_IOCTL_DISK_RESIZE_JOURNAL: |
925 | BCH_IOCTL(disk_resize_journal, struct bch_ioctl_disk_resize_journal); | |
267b801f KO |
926 | case BCH_IOCTL_FSCK_ONLINE: |
927 | BCH_IOCTL(fsck_online, struct bch_ioctl_fsck_online); | |
1c6fdbd8 KO |
928 | default: |
929 | return -ENOTTY; | |
930 | } | |
e2fcf7f6 KO |
931 | out: |
932 | if (ret < 0) | |
933 | ret = bch2_err_class(ret); | |
934 | return ret; | |
1c6fdbd8 KO |
935 | } |
936 | ||
937 | static DEFINE_IDR(bch_chardev_minor); | |
938 | ||
939 | static long bch2_chardev_ioctl(struct file *filp, unsigned cmd, unsigned long v) | |
940 | { | |
941 | unsigned minor = iminor(file_inode(filp)); | |
942 | struct bch_fs *c = minor < U8_MAX ? idr_find(&bch_chardev_minor, minor) : NULL; | |
943 | void __user *arg = (void __user *) v; | |
944 | ||
945 | return c | |
946 | ? bch2_fs_ioctl(c, cmd, arg) | |
947 | : bch2_global_ioctl(cmd, arg); | |
948 | } | |
949 | ||
950 | static const struct file_operations bch_chardev_fops = { | |
951 | .owner = THIS_MODULE, | |
952 | .unlocked_ioctl = bch2_chardev_ioctl, | |
953 | .open = nonseekable_open, | |
954 | }; | |
955 | ||
956 | static int bch_chardev_major; | |
af3b39b4 RM |
957 | static const struct class bch_chardev_class = { |
958 | .name = "bcachefs", | |
959 | }; | |
1c6fdbd8 KO |
960 | static struct device *bch_chardev; |
961 | ||
962 | void bch2_fs_chardev_exit(struct bch_fs *c) | |
963 | { | |
964 | if (!IS_ERR_OR_NULL(c->chardev)) | |
965 | device_unregister(c->chardev); | |
966 | if (c->minor >= 0) | |
967 | idr_remove(&bch_chardev_minor, c->minor); | |
968 | } | |
969 | ||
970 | int bch2_fs_chardev_init(struct bch_fs *c) | |
971 | { | |
972 | c->minor = idr_alloc(&bch_chardev_minor, c, 0, 0, GFP_KERNEL); | |
973 | if (c->minor < 0) | |
974 | return c->minor; | |
975 | ||
af3b39b4 | 976 | c->chardev = device_create(&bch_chardev_class, NULL, |
1c6fdbd8 KO |
977 | MKDEV(bch_chardev_major, c->minor), c, |
978 | "bcachefs%u-ctl", c->minor); | |
979 | if (IS_ERR(c->chardev)) | |
980 | return PTR_ERR(c->chardev); | |
981 | ||
982 | return 0; | |
983 | } | |
984 | ||
985 | void bch2_chardev_exit(void) | |
986 | { | |
af3b39b4 RM |
987 | device_destroy(&bch_chardev_class, MKDEV(bch_chardev_major, U8_MAX)); |
988 | class_unregister(&bch_chardev_class); | |
1c6fdbd8 KO |
989 | if (bch_chardev_major > 0) |
990 | unregister_chrdev(bch_chardev_major, "bcachefs"); | |
991 | } | |
992 | ||
993 | int __init bch2_chardev_init(void) | |
994 | { | |
af3b39b4 RM |
995 | int ret; |
996 | ||
1c6fdbd8 KO |
997 | bch_chardev_major = register_chrdev(0, "bcachefs-ctl", &bch_chardev_fops); |
998 | if (bch_chardev_major < 0) | |
999 | return bch_chardev_major; | |
1000 | ||
af3b39b4 RM |
1001 | ret = class_register(&bch_chardev_class); |
1002 | if (ret) | |
1003 | goto major_out; | |
1c6fdbd8 | 1004 | |
af3b39b4 | 1005 | bch_chardev = device_create(&bch_chardev_class, NULL, |
1c6fdbd8 KO |
1006 | MKDEV(bch_chardev_major, U8_MAX), |
1007 | NULL, "bcachefs-ctl"); | |
af3b39b4 RM |
1008 | if (IS_ERR(bch_chardev)) { |
1009 | ret = PTR_ERR(bch_chardev); | |
1010 | goto class_out; | |
1011 | } | |
1c6fdbd8 KO |
1012 | |
1013 | return 0; | |
af3b39b4 RM |
1014 | |
1015 | class_out: | |
1016 | class_unregister(&bch_chardev_class); | |
1017 | major_out: | |
1018 | unregister_chrdev(bch_chardev_major, "bcachefs-ctl"); | |
1019 | return ret; | |
1c6fdbd8 KO |
1020 | } |
1021 | ||
1022 | #endif /* NO_BCACHEFS_CHARDEV */ |