1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include "bcachefs.h" |
3 | #include "disk_groups.h" |
4 | #include "sb-members.h" |
5 | #include "super-io.h" |
6 | |
7 | #include <linux/sort.h> |
8 | |
9 | static int group_cmp(const void *_l, const void *_r) |
10 | { |
11 | const struct bch_disk_group *l = _l; |
12 | const struct bch_disk_group *r = _r; |
13 | |
14 | return ((BCH_GROUP_DELETED(k: l) > BCH_GROUP_DELETED(k: r)) - |
15 | (BCH_GROUP_DELETED(k: l) < BCH_GROUP_DELETED(k: r))) ?: |
16 | ((BCH_GROUP_PARENT(k: l) > BCH_GROUP_PARENT(k: r)) - |
17 | (BCH_GROUP_PARENT(k: l) < BCH_GROUP_PARENT(k: r))) ?: |
18 | strncmp(l->label, r->label, sizeof(l->label)); |
19 | } |
20 | |
21 | static int bch2_sb_disk_groups_validate(struct bch_sb *sb, |
22 | struct bch_sb_field *f, |
23 | struct printbuf *err) |
24 | { |
25 | struct bch_sb_field_disk_groups *groups = |
26 | field_to_type(f, disk_groups); |
27 | struct bch_disk_group *g, *sorted = NULL; |
28 | unsigned nr_groups = disk_groups_nr(groups); |
29 | unsigned i, len; |
30 | int ret = 0; |
31 | |
32 | for (i = 0; i < sb->nr_devices; i++) { |
33 | struct bch_member m = bch2_sb_member_get(sb, i); |
34 | unsigned group_id; |
35 | |
36 | if (!BCH_MEMBER_GROUP(k: &m)) |
37 | continue; |
38 | |
39 | group_id = BCH_MEMBER_GROUP(k: &m) - 1; |
40 | |
41 | if (group_id >= nr_groups) { |
42 | prt_printf(err, "disk %u has invalid label %u (have %u)" , |
43 | i, group_id, nr_groups); |
44 | return -BCH_ERR_invalid_sb_disk_groups; |
45 | } |
46 | |
47 | if (BCH_GROUP_DELETED(k: &groups->entries[group_id])) { |
48 | prt_printf(err, "disk %u has deleted label %u" , i, group_id); |
49 | return -BCH_ERR_invalid_sb_disk_groups; |
50 | } |
51 | } |
52 | |
53 | if (!nr_groups) |
54 | return 0; |
55 | |
56 | for (i = 0; i < nr_groups; i++) { |
57 | g = groups->entries + i; |
58 | |
59 | if (BCH_GROUP_DELETED(k: g)) |
60 | continue; |
61 | |
62 | len = strnlen(p: g->label, maxlen: sizeof(g->label)); |
63 | if (!len) { |
64 | prt_printf(err, "label %u empty" , i); |
65 | return -BCH_ERR_invalid_sb_disk_groups; |
66 | } |
67 | } |
68 | |
69 | sorted = kmalloc_array(n: nr_groups, size: sizeof(*sorted), GFP_KERNEL); |
70 | if (!sorted) |
71 | return -BCH_ERR_ENOMEM_disk_groups_validate; |
72 | |
73 | memcpy(sorted, groups->entries, nr_groups * sizeof(*sorted)); |
74 | sort(base: sorted, num: nr_groups, size: sizeof(*sorted), cmp_func: group_cmp, NULL); |
75 | |
76 | for (g = sorted; g + 1 < sorted + nr_groups; g++) |
77 | if (!BCH_GROUP_DELETED(k: g) && |
78 | !group_cmp(l: &g[0], r: &g[1])) { |
79 | prt_printf(err, "duplicate label %llu.%.*s" , |
80 | BCH_GROUP_PARENT(g), |
81 | (int) sizeof(g->label), g->label); |
82 | ret = -BCH_ERR_invalid_sb_disk_groups; |
83 | goto err; |
84 | } |
85 | err: |
86 | kfree(objp: sorted); |
87 | return ret; |
88 | } |
89 | |
90 | void bch2_disk_groups_to_text(struct printbuf *out, struct bch_fs *c) |
91 | { |
92 | out->atomic++; |
93 | rcu_read_lock(); |
94 | |
95 | struct bch_disk_groups_cpu *g = rcu_dereference(c->disk_groups); |
96 | if (!g) |
97 | goto out; |
98 | |
99 | for (unsigned i = 0; i < g->nr; i++) { |
100 | if (i) |
101 | prt_printf(out, " " ); |
102 | |
103 | if (g->entries[i].deleted) { |
104 | prt_printf(out, "[deleted]" ); |
105 | continue; |
106 | } |
107 | |
108 | prt_printf(out, "[parent %d devs" , g->entries[i].parent); |
109 | for_each_member_device_rcu(c, ca, &g->entries[i].devs) |
110 | prt_printf(out, " %s" , ca->name); |
111 | prt_printf(out, "]" ); |
112 | } |
113 | |
114 | out: |
115 | rcu_read_unlock(); |
116 | out->atomic--; |
117 | } |
118 | |
119 | static void bch2_sb_disk_groups_to_text(struct printbuf *out, |
120 | struct bch_sb *sb, |
121 | struct bch_sb_field *f) |
122 | { |
123 | struct bch_sb_field_disk_groups *groups = |
124 | field_to_type(f, disk_groups); |
125 | struct bch_disk_group *g; |
126 | unsigned nr_groups = disk_groups_nr(groups); |
127 | |
128 | for (g = groups->entries; |
129 | g < groups->entries + nr_groups; |
130 | g++) { |
131 | if (g != groups->entries) |
132 | prt_printf(out, " " ); |
133 | |
134 | if (BCH_GROUP_DELETED(k: g)) |
135 | prt_printf(out, "[deleted]" ); |
136 | else |
137 | prt_printf(out, "[parent %llu name %s]" , |
138 | BCH_GROUP_PARENT(g), g->label); |
139 | } |
140 | } |
141 | |
142 | const struct bch_sb_field_ops bch_sb_field_ops_disk_groups = { |
143 | .validate = bch2_sb_disk_groups_validate, |
144 | .to_text = bch2_sb_disk_groups_to_text |
145 | }; |
146 | |
147 | int bch2_sb_disk_groups_to_cpu(struct bch_fs *c) |
148 | { |
149 | struct bch_sb_field_disk_groups *groups; |
150 | struct bch_disk_groups_cpu *cpu_g, *old_g; |
151 | unsigned i, g, nr_groups; |
152 | |
153 | lockdep_assert_held(&c->sb_lock); |
154 | |
155 | groups = bch2_sb_field_get(c->disk_sb.sb, disk_groups); |
156 | nr_groups = disk_groups_nr(groups); |
157 | |
158 | if (!groups) |
159 | return 0; |
160 | |
161 | cpu_g = kzalloc(struct_size(cpu_g, entries, nr_groups), GFP_KERNEL); |
162 | if (!cpu_g) |
163 | return -BCH_ERR_ENOMEM_disk_groups_to_cpu; |
164 | |
165 | cpu_g->nr = nr_groups; |
166 | |
167 | for (i = 0; i < nr_groups; i++) { |
168 | struct bch_disk_group *src = &groups->entries[i]; |
169 | struct bch_disk_group_cpu *dst = &cpu_g->entries[i]; |
170 | |
171 | dst->deleted = BCH_GROUP_DELETED(k: src); |
172 | dst->parent = BCH_GROUP_PARENT(k: src); |
173 | memcpy(dst->label, src->label, sizeof(dst->label)); |
174 | } |
175 | |
176 | for (i = 0; i < c->disk_sb.sb->nr_devices; i++) { |
177 | struct bch_member m = bch2_sb_member_get(sb: c->disk_sb.sb, i); |
178 | struct bch_disk_group_cpu *dst; |
179 | |
180 | if (!bch2_member_exists(m: &m)) |
181 | continue; |
182 | |
183 | g = BCH_MEMBER_GROUP(k: &m); |
184 | while (g) { |
185 | dst = &cpu_g->entries[g - 1]; |
186 | __set_bit(i, dst->devs.d); |
187 | g = dst->parent; |
188 | } |
189 | } |
190 | |
191 | old_g = rcu_dereference_protected(c->disk_groups, |
192 | lockdep_is_held(&c->sb_lock)); |
193 | rcu_assign_pointer(c->disk_groups, cpu_g); |
194 | if (old_g) |
195 | kfree_rcu(old_g, rcu); |
196 | |
197 | return 0; |
198 | } |
199 | |
200 | const struct bch_devs_mask *bch2_target_to_mask(struct bch_fs *c, unsigned target) |
201 | { |
202 | struct target t = target_decode(target); |
203 | struct bch_devs_mask *devs; |
204 | |
205 | rcu_read_lock(); |
206 | |
207 | switch (t.type) { |
208 | case TARGET_NULL: |
209 | devs = NULL; |
210 | break; |
211 | case TARGET_DEV: { |
212 | struct bch_dev *ca = t.dev < c->sb.nr_devices |
213 | ? rcu_dereference(c->devs[t.dev]) |
214 | : NULL; |
215 | devs = ca ? &ca->self : NULL; |
216 | break; |
217 | } |
218 | case TARGET_GROUP: { |
219 | struct bch_disk_groups_cpu *g = rcu_dereference(c->disk_groups); |
220 | |
221 | devs = g && t.group < g->nr && !g->entries[t.group].deleted |
222 | ? &g->entries[t.group].devs |
223 | : NULL; |
224 | break; |
225 | } |
226 | default: |
227 | BUG(); |
228 | } |
229 | |
230 | rcu_read_unlock(); |
231 | |
232 | return devs; |
233 | } |
234 | |
235 | bool bch2_dev_in_target(struct bch_fs *c, unsigned dev, unsigned target) |
236 | { |
237 | struct target t = target_decode(target); |
238 | |
239 | switch (t.type) { |
240 | case TARGET_NULL: |
241 | return false; |
242 | case TARGET_DEV: |
243 | return dev == t.dev; |
244 | case TARGET_GROUP: { |
245 | struct bch_disk_groups_cpu *g; |
246 | const struct bch_devs_mask *m; |
247 | bool ret; |
248 | |
249 | rcu_read_lock(); |
250 | g = rcu_dereference(c->disk_groups); |
251 | m = g && t.group < g->nr && !g->entries[t.group].deleted |
252 | ? &g->entries[t.group].devs |
253 | : NULL; |
254 | |
255 | ret = m ? test_bit(dev, m->d) : false; |
256 | rcu_read_unlock(); |
257 | |
258 | return ret; |
259 | } |
260 | default: |
261 | BUG(); |
262 | } |
263 | } |
264 | |
265 | static int __bch2_disk_group_find(struct bch_sb_field_disk_groups *groups, |
266 | unsigned parent, |
267 | const char *name, unsigned namelen) |
268 | { |
269 | unsigned i, nr_groups = disk_groups_nr(groups); |
270 | |
271 | if (!namelen || namelen > BCH_SB_LABEL_SIZE) |
272 | return -EINVAL; |
273 | |
274 | for (i = 0; i < nr_groups; i++) { |
275 | struct bch_disk_group *g = groups->entries + i; |
276 | |
277 | if (BCH_GROUP_DELETED(k: g)) |
278 | continue; |
279 | |
280 | if (!BCH_GROUP_DELETED(k: g) && |
281 | BCH_GROUP_PARENT(k: g) == parent && |
282 | strnlen(p: g->label, maxlen: sizeof(g->label)) == namelen && |
283 | !memcmp(p: name, q: g->label, size: namelen)) |
284 | return i; |
285 | } |
286 | |
287 | return -1; |
288 | } |
289 | |
290 | static int __bch2_disk_group_add(struct bch_sb_handle *sb, unsigned parent, |
291 | const char *name, unsigned namelen) |
292 | { |
293 | struct bch_sb_field_disk_groups *groups = |
294 | bch2_sb_field_get(sb->sb, disk_groups); |
295 | unsigned i, nr_groups = disk_groups_nr(groups); |
296 | struct bch_disk_group *g; |
297 | |
298 | if (!namelen || namelen > BCH_SB_LABEL_SIZE) |
299 | return -EINVAL; |
300 | |
301 | for (i = 0; |
302 | i < nr_groups && !BCH_GROUP_DELETED(k: &groups->entries[i]); |
303 | i++) |
304 | ; |
305 | |
306 | if (i == nr_groups) { |
307 | unsigned u64s = |
308 | (sizeof(struct bch_sb_field_disk_groups) + |
309 | sizeof(struct bch_disk_group) * (nr_groups + 1)) / |
310 | sizeof(u64); |
311 | |
312 | groups = bch2_sb_field_resize(sb, disk_groups, u64s); |
313 | if (!groups) |
314 | return -BCH_ERR_ENOSPC_disk_label_add; |
315 | |
316 | nr_groups = disk_groups_nr(groups); |
317 | } |
318 | |
319 | BUG_ON(i >= nr_groups); |
320 | |
321 | g = &groups->entries[i]; |
322 | |
323 | memcpy(g->label, name, namelen); |
324 | if (namelen < sizeof(g->label)) |
325 | g->label[namelen] = '\0'; |
326 | SET_BCH_GROUP_DELETED(k: g, v: 0); |
327 | SET_BCH_GROUP_PARENT(k: g, v: parent); |
328 | SET_BCH_GROUP_DATA_ALLOWED(k: g, v: ~0); |
329 | |
330 | return i; |
331 | } |
332 | |
333 | int bch2_disk_path_find(struct bch_sb_handle *sb, const char *name) |
334 | { |
335 | struct bch_sb_field_disk_groups *groups = |
336 | bch2_sb_field_get(sb->sb, disk_groups); |
337 | int v = -1; |
338 | |
339 | do { |
340 | const char *next = strchrnul(name, '.'); |
341 | unsigned len = next - name; |
342 | |
343 | if (*next == '.') |
344 | next++; |
345 | |
346 | v = __bch2_disk_group_find(groups, parent: v + 1, name, namelen: len); |
347 | name = next; |
348 | } while (*name && v >= 0); |
349 | |
350 | return v; |
351 | } |
352 | |
353 | int bch2_disk_path_find_or_create(struct bch_sb_handle *sb, const char *name) |
354 | { |
355 | struct bch_sb_field_disk_groups *groups; |
356 | unsigned parent = 0; |
357 | int v = -1; |
358 | |
359 | do { |
360 | const char *next = strchrnul(name, '.'); |
361 | unsigned len = next - name; |
362 | |
363 | if (*next == '.') |
364 | next++; |
365 | |
366 | groups = bch2_sb_field_get(sb->sb, disk_groups); |
367 | |
368 | v = __bch2_disk_group_find(groups, parent, name, namelen: len); |
369 | if (v < 0) |
370 | v = __bch2_disk_group_add(sb, parent, name, namelen: len); |
371 | if (v < 0) |
372 | return v; |
373 | |
374 | parent = v + 1; |
375 | name = next; |
376 | } while (*name && v >= 0); |
377 | |
378 | return v; |
379 | } |
380 | |
381 | void bch2_disk_path_to_text(struct printbuf *out, struct bch_fs *c, unsigned v) |
382 | { |
383 | struct bch_disk_groups_cpu *groups; |
384 | struct bch_disk_group_cpu *g; |
385 | unsigned nr = 0; |
386 | u16 path[32]; |
387 | |
388 | out->atomic++; |
389 | rcu_read_lock(); |
390 | groups = rcu_dereference(c->disk_groups); |
391 | if (!groups) |
392 | goto invalid; |
393 | |
394 | while (1) { |
395 | if (nr == ARRAY_SIZE(path)) |
396 | goto invalid; |
397 | |
398 | if (v >= groups->nr) |
399 | goto invalid; |
400 | |
401 | g = groups->entries + v; |
402 | |
403 | if (g->deleted) |
404 | goto invalid; |
405 | |
406 | path[nr++] = v; |
407 | |
408 | if (!g->parent) |
409 | break; |
410 | |
411 | v = g->parent - 1; |
412 | } |
413 | |
414 | while (nr) { |
415 | v = path[--nr]; |
416 | g = groups->entries + v; |
417 | |
418 | prt_printf(out, "%.*s" , (int) sizeof(g->label), g->label); |
419 | if (nr) |
420 | prt_printf(out, "." ); |
421 | } |
422 | out: |
423 | rcu_read_unlock(); |
424 | out->atomic--; |
425 | return; |
426 | invalid: |
427 | prt_printf(out, "invalid label %u" , v); |
428 | goto out; |
429 | } |
430 | |
431 | void bch2_disk_path_to_text_sb(struct printbuf *out, struct bch_sb *sb, unsigned v) |
432 | { |
433 | struct bch_sb_field_disk_groups *groups = |
434 | bch2_sb_field_get(sb, disk_groups); |
435 | struct bch_disk_group *g; |
436 | unsigned nr = 0; |
437 | u16 path[32]; |
438 | |
439 | while (1) { |
440 | if (nr == ARRAY_SIZE(path)) |
441 | goto inval; |
442 | |
443 | if (v >= disk_groups_nr(groups)) |
444 | goto inval; |
445 | |
446 | g = groups->entries + v; |
447 | |
448 | if (BCH_GROUP_DELETED(k: g)) |
449 | goto inval; |
450 | |
451 | path[nr++] = v; |
452 | |
453 | if (!BCH_GROUP_PARENT(k: g)) |
454 | break; |
455 | |
456 | v = BCH_GROUP_PARENT(k: g) - 1; |
457 | } |
458 | |
459 | while (nr) { |
460 | v = path[--nr]; |
461 | g = groups->entries + v; |
462 | |
463 | prt_printf(out, "%.*s" , (int) sizeof(g->label), g->label); |
464 | if (nr) |
465 | prt_printf(out, "." ); |
466 | } |
467 | return; |
468 | inval: |
469 | prt_printf(out, "invalid label %u" , v); |
470 | } |
471 | |
472 | int __bch2_dev_group_set(struct bch_fs *c, struct bch_dev *ca, const char *name) |
473 | { |
474 | struct bch_member *mi; |
475 | int ret, v = -1; |
476 | |
477 | if (!strlen(name) || !strcmp(name, "none" )) |
478 | return 0; |
479 | |
480 | v = bch2_disk_path_find_or_create(sb: &c->disk_sb, name); |
481 | if (v < 0) |
482 | return v; |
483 | |
484 | ret = bch2_sb_disk_groups_to_cpu(c); |
485 | if (ret) |
486 | return ret; |
487 | |
488 | mi = bch2_members_v2_get_mut(sb: c->disk_sb.sb, i: ca->dev_idx); |
489 | SET_BCH_MEMBER_GROUP(k: mi, v: v + 1); |
490 | return 0; |
491 | } |
492 | |
493 | int bch2_dev_group_set(struct bch_fs *c, struct bch_dev *ca, const char *name) |
494 | { |
495 | int ret; |
496 | |
497 | mutex_lock(&c->sb_lock); |
498 | ret = __bch2_dev_group_set(c, ca, name) ?: |
499 | bch2_write_super(c); |
500 | mutex_unlock(lock: &c->sb_lock); |
501 | |
502 | return ret; |
503 | } |
504 | |
505 | int bch2_opt_target_parse(struct bch_fs *c, const char *val, u64 *res, |
506 | struct printbuf *err) |
507 | { |
508 | struct bch_dev *ca; |
509 | int g; |
510 | |
511 | if (!val) |
512 | return -EINVAL; |
513 | |
514 | if (!c) |
515 | return 0; |
516 | |
517 | if (!strlen(val) || !strcmp(val, "none" )) { |
518 | *res = 0; |
519 | return 0; |
520 | } |
521 | |
522 | /* Is it a device? */ |
523 | ca = bch2_dev_lookup(c, val); |
524 | if (!IS_ERR(ptr: ca)) { |
525 | *res = dev_to_target(dev: ca->dev_idx); |
526 | percpu_ref_put(ref: &ca->ref); |
527 | return 0; |
528 | } |
529 | |
530 | mutex_lock(&c->sb_lock); |
531 | g = bch2_disk_path_find(sb: &c->disk_sb, name: val); |
532 | mutex_unlock(lock: &c->sb_lock); |
533 | |
534 | if (g >= 0) { |
535 | *res = group_to_target(group: g); |
536 | return 0; |
537 | } |
538 | |
539 | return -EINVAL; |
540 | } |
541 | |
542 | void bch2_target_to_text(struct printbuf *out, struct bch_fs *c, unsigned v) |
543 | { |
544 | struct target t = target_decode(target: v); |
545 | |
546 | switch (t.type) { |
547 | case TARGET_NULL: |
548 | prt_printf(out, "none" ); |
549 | break; |
550 | case TARGET_DEV: { |
551 | struct bch_dev *ca; |
552 | |
553 | out->atomic++; |
554 | rcu_read_lock(); |
555 | ca = t.dev < c->sb.nr_devices |
556 | ? rcu_dereference(c->devs[t.dev]) |
557 | : NULL; |
558 | |
559 | if (ca && percpu_ref_tryget(ref: &ca->io_ref)) { |
560 | prt_printf(out, "/dev/%s" , ca->name); |
561 | percpu_ref_put(ref: &ca->io_ref); |
562 | } else if (ca) { |
563 | prt_printf(out, "offline device %u" , t.dev); |
564 | } else { |
565 | prt_printf(out, "invalid device %u" , t.dev); |
566 | } |
567 | |
568 | rcu_read_unlock(); |
569 | out->atomic--; |
570 | break; |
571 | } |
572 | case TARGET_GROUP: |
573 | bch2_disk_path_to_text(out, c, v: t.group); |
574 | break; |
575 | default: |
576 | BUG(); |
577 | } |
578 | } |
579 | |
580 | static void bch2_target_to_text_sb(struct printbuf *out, struct bch_sb *sb, unsigned v) |
581 | { |
582 | struct target t = target_decode(target: v); |
583 | |
584 | switch (t.type) { |
585 | case TARGET_NULL: |
586 | prt_printf(out, "none" ); |
587 | break; |
588 | case TARGET_DEV: { |
589 | struct bch_member m = bch2_sb_member_get(sb, i: t.dev); |
590 | |
591 | if (bch2_dev_exists(sb, dev: t.dev)) { |
592 | prt_printf(out, "Device " ); |
593 | pr_uuid(out, uuid: m.uuid.b); |
594 | prt_printf(out, " (%u)" , t.dev); |
595 | } else { |
596 | prt_printf(out, "Bad device %u" , t.dev); |
597 | } |
598 | break; |
599 | } |
600 | case TARGET_GROUP: |
601 | bch2_disk_path_to_text_sb(out, sb, v: t.group); |
602 | break; |
603 | default: |
604 | BUG(); |
605 | } |
606 | } |
607 | |
608 | void bch2_opt_target_to_text(struct printbuf *out, |
609 | struct bch_fs *c, |
610 | struct bch_sb *sb, |
611 | u64 v) |
612 | { |
613 | if (c) |
614 | bch2_target_to_text(out, c, v); |
615 | else |
616 | bch2_target_to_text_sb(out, sb, v); |
617 | } |
618 | |