]> git.sesse.net Git - bcachefs-tools-debian/blob - libbcachefs.c
cmd_dump: Use buffered IO
[bcachefs-tools-debian] / libbcachefs.c
1 #include <ctype.h>
2 #include <dirent.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <libgen.h>
6 #include <stdbool.h>
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/stat.h>
12 #include <sys/sysmacros.h>
13 #include <sys/types.h>
14 #include <time.h>
15 #include <unistd.h>
16
17 #include <uuid/uuid.h>
18
19 #include "libbcachefs.h"
20 #include "crypto.h"
21 #include "libbcachefs/bcachefs_format.h"
22 #include "libbcachefs/btree_cache.h"
23 #include "libbcachefs/checksum.h"
24 #include "libbcachefs/disk_groups.h"
25 #include "libbcachefs/journal_seq_blacklist.h"
26 #include "libbcachefs/opts.h"
27 #include "libbcachefs/replicas.h"
28 #include "libbcachefs/super-io.h"
29 #include "tools-util.h"
30
31 #define NSEC_PER_SEC    1000000000L
32
33 static void init_layout(struct bch_sb_layout *l,
34                         unsigned block_size,
35                         unsigned sb_size,
36                         u64 sb_start, u64 sb_end)
37 {
38         u64 sb_pos = sb_start;
39         unsigned i;
40
41         memset(l, 0, sizeof(*l));
42
43         l->magic                = BCHFS_MAGIC;
44         l->layout_type          = 0;
45         l->nr_superblocks       = 2;
46         l->sb_max_size_bits     = ilog2(sb_size);
47
48         /* Create two superblocks in the allowed range: */
49         for (i = 0; i < l->nr_superblocks; i++) {
50                 if (sb_pos != BCH_SB_SECTOR)
51                         sb_pos = round_up(sb_pos, block_size >> 9);
52
53                 l->sb_offset[i] = cpu_to_le64(sb_pos);
54                 sb_pos += sb_size;
55         }
56
57         if (sb_pos > sb_end)
58                 die("insufficient space for superblocks: start %llu end %llu > %llu size %u",
59                     sb_start, sb_pos, sb_end, sb_size);
60 }
61
62 /* minimum size filesystem we can create, given a bucket size: */
63 static u64 min_size(unsigned bucket_size)
64 {
65         return BCH_MIN_NR_NBUCKETS * bucket_size;
66 }
67
68 u64 bch2_pick_bucket_size(struct bch_opts opts, struct dev_opts *dev)
69 {
70         u64 bucket_size;
71
72         if (dev->size < min_size(opts.block_size))
73                 die("cannot format %s, too small (%llu bytes, min %llu)",
74                     dev->path, dev->size, min_size(opts.block_size));
75
76         /* Bucket size must be >= block size: */
77         bucket_size = opts.block_size;
78
79         /* Bucket size must be >= btree node size: */
80         if (opt_defined(opts, btree_node_size))
81                 bucket_size = max_t(unsigned, bucket_size,
82                                          opts.btree_node_size);
83
84         /* Want a bucket size of at least 128k, if possible: */
85         bucket_size = max(bucket_size, 128ULL << 10);
86
87         if (dev->size >= min_size(bucket_size)) {
88                 unsigned scale = max(1,
89                         ilog2(dev->size / min_size(bucket_size)) / 4);
90
91                 scale = rounddown_pow_of_two(scale);
92
93                 /* max bucket size 1 mb */
94                 bucket_size = min(bucket_size * scale, 1ULL << 20);
95         } else {
96                 do {
97                         bucket_size /= 2;
98                 } while (dev->size < min_size(bucket_size));
99         }
100
101         return bucket_size;
102 }
103
104 void bch2_check_bucket_size(struct bch_opts opts, struct dev_opts *dev)
105 {
106         if (dev->bucket_size < opts.block_size)
107                 die("Bucket size (%llu) cannot be smaller than block size (%u)",
108                     dev->bucket_size, opts.block_size);
109
110         if (opt_defined(opts, btree_node_size) &&
111             dev->bucket_size < opts.btree_node_size)
112                 die("Bucket size (%llu) cannot be smaller than btree node size (%u)",
113                     dev->bucket_size, opts.btree_node_size);
114
115         if (dev->nbuckets < BCH_MIN_NR_NBUCKETS)
116                 die("Not enough buckets: %llu, need %u (bucket size %llu)",
117                     dev->nbuckets, BCH_MIN_NR_NBUCKETS, dev->bucket_size);
118
119         if (dev->bucket_size > (u32) U16_MAX << 9)
120                 die("Bucket size (%llu) too big (max %u)",
121                     dev->bucket_size, (u32) U16_MAX << 9);
122 }
123
124 static unsigned parse_target(struct bch_sb_handle *sb,
125                              struct dev_opts *devs, size_t nr_devs,
126                              const char *s)
127 {
128         struct dev_opts *i;
129         int idx;
130
131         if (!s)
132                 return 0;
133
134         for (i = devs; i < devs + nr_devs; i++)
135                 if (!strcmp(s, i->path))
136                         return dev_to_target(i - devs);
137
138         idx = bch2_disk_path_find(sb, s);
139         if (idx >= 0)
140                 return group_to_target(idx);
141
142         die("Invalid target %s", s);
143         return 0;
144 }
145
146 struct bch_sb *bch2_format(struct bch_opt_strs  fs_opt_strs,
147                            struct bch_opts      fs_opts,
148                            struct format_opts   opts,
149                            struct dev_opts      *devs,
150                            size_t               nr_devs)
151 {
152         struct bch_sb_handle sb = { NULL };
153         struct dev_opts *i;
154         unsigned max_dev_block_size = 0;
155         unsigned opt_id;
156         u64 min_bucket_size = U64_MAX;
157
158         for (i = devs; i < devs + nr_devs; i++)
159                 max_dev_block_size = max(max_dev_block_size, get_blocksize(i->bdev->bd_fd));
160
161         /* calculate block size: */
162         if (!opt_defined(fs_opts, block_size)) {
163                 opt_set(fs_opts, block_size, max_dev_block_size);
164         } else if (fs_opts.block_size < max_dev_block_size)
165                 die("blocksize too small: %u, must be greater than device blocksize %u",
166                     fs_opts.block_size, max_dev_block_size);
167
168         /* get device size, if it wasn't specified: */
169         for (i = devs; i < devs + nr_devs; i++)
170                 if (!i->size)
171                         i->size = get_size(i->bdev->bd_fd);
172
173         /* calculate bucket sizes: */
174         for (i = devs; i < devs + nr_devs; i++)
175                 min_bucket_size = min(min_bucket_size,
176                         i->bucket_size ?: bch2_pick_bucket_size(fs_opts, i));
177
178         for (i = devs; i < devs + nr_devs; i++)
179                 if (!i->bucket_size)
180                         i->bucket_size = min_bucket_size;
181
182         for (i = devs; i < devs + nr_devs; i++) {
183                 i->nbuckets = i->size / i->bucket_size;
184                 bch2_check_bucket_size(fs_opts, i);
185         }
186
187         /* calculate btree node size: */
188         if (!opt_defined(fs_opts, btree_node_size)) {
189                 /* 256k default btree node size */
190                 opt_set(fs_opts, btree_node_size, 256 << 10);
191
192                 for (i = devs; i < devs + nr_devs; i++)
193                         fs_opts.btree_node_size =
194                                 min_t(unsigned, fs_opts.btree_node_size,
195                                       i->bucket_size);
196         }
197
198         if (uuid_is_null(opts.uuid.b))
199                 uuid_generate(opts.uuid.b);
200
201         if (bch2_sb_realloc(&sb, 0))
202                 die("insufficient memory");
203
204         sb.sb->version          = le16_to_cpu(opts.version);
205         sb.sb->version_min      = le16_to_cpu(opts.version);
206         sb.sb->magic            = BCHFS_MAGIC;
207         sb.sb->user_uuid        = opts.uuid;
208         sb.sb->nr_devices       = nr_devs;
209
210         if (opts.version == bcachefs_metadata_version_current)
211                 sb.sb->features[0] |= cpu_to_le64(BCH_SB_FEATURES_ALL);
212
213         uuid_generate(sb.sb->uuid.b);
214
215         if (opts.label)
216                 memcpy(sb.sb->label,
217                        opts.label,
218                        min(strlen(opts.label), sizeof(sb.sb->label)));
219
220         for (opt_id = 0;
221              opt_id < bch2_opts_nr;
222              opt_id++) {
223                 u64 v;
224
225                 v = bch2_opt_defined_by_id(&fs_opts, opt_id)
226                         ? bch2_opt_get_by_id(&fs_opts, opt_id)
227                         : bch2_opt_get_by_id(&bch2_opts_default, opt_id);
228
229                 __bch2_opt_set_sb(sb.sb, &bch2_opt_table[opt_id], v);
230         }
231
232         struct timespec now;
233         if (clock_gettime(CLOCK_REALTIME, &now))
234                 die("error getting current time: %m");
235
236         sb.sb->time_base_lo     = cpu_to_le64(now.tv_sec * NSEC_PER_SEC + now.tv_nsec);
237         sb.sb->time_precision   = cpu_to_le32(1);
238
239         /* Member info: */
240         struct bch_sb_field_members_v2 *mi =
241                 bch2_sb_field_resize(&sb, members_v2,
242                         (sizeof(*mi) + sizeof(struct bch_member) *
243                         nr_devs) / sizeof(u64));
244         mi->member_bytes = cpu_to_le16(sizeof(struct bch_member));
245         for (i = devs; i < devs + nr_devs; i++) {
246                 struct bch_member *m = bch2_members_v2_get_mut(sb.sb, (i - devs));
247
248                 uuid_generate(m->uuid.b);
249                 m->nbuckets     = cpu_to_le64(i->nbuckets);
250                 m->first_bucket = 0;
251                 m->bucket_size  = cpu_to_le16(i->bucket_size >> 9);
252
253                 SET_BCH_MEMBER_DISCARD(m,       i->discard);
254                 SET_BCH_MEMBER_DATA_ALLOWED(m,  i->data_allowed);
255                 SET_BCH_MEMBER_DURABILITY(m,    i->durability + 1);
256         }
257
258         /* Disk labels*/
259         for (i = devs; i < devs + nr_devs; i++) {
260                 struct bch_member *m;
261                 int idx;
262
263                 if (!i->label)
264                         continue;
265
266                 idx = bch2_disk_path_find_or_create(&sb, i->label);
267                 if (idx < 0)
268                         die("error creating disk path: %s", strerror(-idx));
269
270                 /*
271                  * Recompute mi and m after each sb modification: its location
272                  * in memory may have changed due to reallocation.
273                  */
274                 m = bch2_members_v2_get_mut(sb.sb, (i - devs));
275                 SET_BCH_MEMBER_GROUP(m, idx + 1);
276         }
277
278         SET_BCH_SB_FOREGROUND_TARGET(sb.sb,
279                 parse_target(&sb, devs, nr_devs, fs_opt_strs.foreground_target));
280         SET_BCH_SB_BACKGROUND_TARGET(sb.sb,
281                 parse_target(&sb, devs, nr_devs, fs_opt_strs.background_target));
282         SET_BCH_SB_PROMOTE_TARGET(sb.sb,
283                 parse_target(&sb, devs, nr_devs, fs_opt_strs.promote_target));
284         SET_BCH_SB_METADATA_TARGET(sb.sb,
285                 parse_target(&sb, devs, nr_devs, fs_opt_strs.metadata_target));
286
287         /* Crypt: */
288         if (opts.encrypted) {
289                 struct bch_sb_field_crypt *crypt =
290                         bch2_sb_field_resize(&sb, crypt, sizeof(*crypt) / sizeof(u64));
291
292                 bch_sb_crypt_init(sb.sb, crypt, opts.passphrase);
293                 SET_BCH_SB_ENCRYPTION_TYPE(sb.sb, 1);
294         }
295
296         bch2_sb_members_cpy_v2_v1(&sb);
297
298         for (i = devs; i < devs + nr_devs; i++) {
299                 u64 size_sectors = i->size >> 9;
300
301                 sb.sb->dev_idx = i - devs;
302
303                 if (!i->sb_offset) {
304                         i->sb_offset    = BCH_SB_SECTOR;
305                         i->sb_end       = size_sectors;
306                 }
307
308                 init_layout(&sb.sb->layout, fs_opts.block_size,
309                             opts.superblock_size,
310                             i->sb_offset, i->sb_end);
311
312                 /*
313                  * Also create a backup superblock at the end of the disk:
314                  *
315                  * If we're not creating a superblock at the default offset, it
316                  * means we're being run from the migrate tool and we could be
317                  * overwriting existing data if we write to the end of the disk:
318                  */
319                 if (i->sb_offset == BCH_SB_SECTOR) {
320                         struct bch_sb_layout *l = &sb.sb->layout;
321                         u64 backup_sb = size_sectors - (1 << l->sb_max_size_bits);
322
323                         backup_sb = rounddown(backup_sb, i->bucket_size >> 9);
324                         l->sb_offset[l->nr_superblocks++] = cpu_to_le64(backup_sb);
325                 }
326
327                 if (i->sb_offset == BCH_SB_SECTOR) {
328                         /* Zero start of disk */
329                         static const char zeroes[BCH_SB_SECTOR << 9];
330
331                         xpwrite(i->bdev->bd_fd, zeroes, BCH_SB_SECTOR << 9, 0,
332                                 "zeroing start of disk");
333                 }
334
335                 bch2_super_write(i->bdev->bd_fd, sb.sb);
336                 close(i->bdev->bd_fd);
337         }
338
339         return sb.sb;
340 }
341
342 void bch2_super_write(int fd, struct bch_sb *sb)
343 {
344         struct nonce nonce = { 0 };
345         unsigned bs = get_blocksize(fd);
346
347         unsigned i;
348         for (i = 0; i < sb->layout.nr_superblocks; i++) {
349                 sb->offset = sb->layout.sb_offset[i];
350
351                 if (sb->offset == BCH_SB_SECTOR) {
352                         /* Write backup layout */
353
354                         BUG_ON(bs > 4096);
355
356                         char *buf = aligned_alloc(bs, bs);
357                         xpread(fd, buf, bs, 4096 - bs);
358                         memcpy(buf + bs - sizeof(sb->layout),
359                                &sb->layout,
360                                sizeof(sb->layout));
361                         xpwrite(fd, buf, bs, 4096 - bs,
362                                 "backup layout");
363                         free(buf);
364
365                 }
366
367                 sb->csum = csum_vstruct(NULL, BCH_SB_CSUM_TYPE(sb), nonce, sb);
368                 xpwrite(fd, sb, round_up(vstruct_bytes(sb), bs),
369                         le64_to_cpu(sb->offset) << 9,
370                         "superblock");
371         }
372
373         fsync(fd);
374 }
375
376 struct bch_sb *__bch2_super_read(int fd, u64 sector)
377 {
378         struct bch_sb sb, *ret;
379
380         xpread(fd, &sb, sizeof(sb), sector << 9);
381
382         if (memcmp(&sb.magic, &BCACHE_MAGIC, sizeof(sb.magic)) &&
383             memcmp(&sb.magic, &BCHFS_MAGIC, sizeof(sb.magic)))
384                 die("not a bcachefs superblock");
385
386         size_t bytes = vstruct_bytes(&sb);
387
388         ret = malloc(bytes);
389
390         xpread(fd, ret, bytes, sector << 9);
391
392         return ret;
393 }
394
395 /* ioctl interface: */
396
397 /* Global control device: */
398 int bcachectl_open(void)
399 {
400         return xopen("/dev/bcachefs-ctl", O_RDWR);
401 }
402
403 /* Filesystem handles (ioctl, sysfs dir): */
404
405 #define SYSFS_BASE "/sys/fs/bcachefs/"
406
407 void bcache_fs_close(struct bchfs_handle fs)
408 {
409         close(fs.ioctl_fd);
410         close(fs.sysfs_fd);
411 }
412
413 struct bchfs_handle bcache_fs_open(const char *path)
414 {
415         struct bchfs_handle ret;
416
417         if (!uuid_parse(path, ret.uuid.b)) {
418                 /* It's a UUID, look it up in sysfs: */
419                 char *sysfs = mprintf(SYSFS_BASE "%s", path);
420                 ret.sysfs_fd = xopen(sysfs, O_RDONLY);
421
422                 char *minor = read_file_str(ret.sysfs_fd, "minor");
423                 char *ctl = mprintf("/dev/bcachefs%s-ctl", minor);
424                 ret.ioctl_fd = xopen(ctl, O_RDWR);
425
426                 free(sysfs);
427                 free(minor);
428                 free(ctl);
429         } else {
430                 /* It's a path: */
431                 ret.ioctl_fd = open(path, O_RDONLY);
432                 if (ret.ioctl_fd < 0)
433                         die("Error opening filesystem at %s: %m", path);
434
435                 struct bch_ioctl_query_uuid uuid;
436                 if (ioctl(ret.ioctl_fd, BCH_IOCTL_QUERY_UUID, &uuid) < 0)
437                         die("error opening %s: not a bcachefs filesystem", path);
438
439                 ret.uuid = uuid.uuid;
440
441                 char uuid_str[40];
442                 uuid_unparse(uuid.uuid.b, uuid_str);
443
444                 char *sysfs = mprintf(SYSFS_BASE "%s", uuid_str);
445                 ret.sysfs_fd = xopen(sysfs, O_RDONLY);
446                 free(sysfs);
447         }
448
449         return ret;
450 }
451
452 /*
453  * Given a path to a block device, open the filesystem it belongs to; also
454  * return the device's idx:
455  */
456 struct bchfs_handle bchu_fs_open_by_dev(const char *path, int *idx)
457 {
458         struct bch_opts opts = bch2_opts_empty();
459         char buf[1024], *uuid_str;
460
461         struct stat stat = xstat(path);
462
463         if (S_ISBLK(stat.st_mode)) {
464                 char *sysfs = mprintf("/sys/dev/block/%u:%u/bcachefs",
465                                       major(stat.st_dev),
466                                       minor(stat.st_dev));
467
468                 ssize_t len = readlink(sysfs, buf, sizeof(buf));
469                 free(sysfs);
470
471                 if (len <= 0)
472                         goto read_super;
473
474                 char *p = strrchr(buf, '/');
475                 if (!p || sscanf(p + 1, "dev-%u", idx) != 1)
476                         die("error parsing sysfs");
477
478                 *p = '\0';
479                 p = strrchr(buf, '/');
480                 uuid_str = p + 1;
481         } else {
482 read_super:
483                 opt_set(opts, noexcl,   true);
484                 opt_set(opts, nochanges, true);
485
486                 struct bch_sb_handle sb;
487                 int ret = bch2_read_super(path, &opts, &sb);
488                 if (ret)
489                         die("Error opening %s: %s", path, strerror(-ret));
490
491                 *idx = sb.sb->dev_idx;
492                 uuid_str = buf;
493                 uuid_unparse(sb.sb->user_uuid.b, uuid_str);
494
495                 bch2_free_super(&sb);
496         }
497
498         return bcache_fs_open(uuid_str);
499 }
500
501 int bchu_dev_path_to_idx(struct bchfs_handle fs, const char *dev_path)
502 {
503         int idx;
504         struct bchfs_handle fs2 = bchu_fs_open_by_dev(dev_path, &idx);
505
506         if (memcmp(&fs.uuid, &fs2.uuid, sizeof(fs.uuid)))
507                 idx = -1;
508         bcache_fs_close(fs2);
509         return idx;
510 }
511
512 int bchu_data(struct bchfs_handle fs, struct bch_ioctl_data cmd)
513 {
514         int progress_fd = xioctl(fs.ioctl_fd, BCH_IOCTL_DATA, &cmd);
515
516         while (1) {
517                 struct bch_ioctl_data_event e;
518
519                 if (read(progress_fd, &e, sizeof(e)) != sizeof(e))
520                         die("error reading from progress fd %m");
521
522                 if (e.type)
523                         continue;
524
525                 if (e.p.data_type == U8_MAX)
526                         break;
527
528                 printf("\33[2K\r");
529
530                 printf("%llu%% complete: current position %s",
531                        e.p.sectors_total
532                        ? e.p.sectors_done * 100 / e.p.sectors_total
533                        : 0,
534                        bch2_data_types[e.p.data_type]);
535
536                 switch (e.p.data_type) {
537                 case BCH_DATA_btree:
538                 case BCH_DATA_user:
539                         printf(" %s:%llu:%llu",
540                                bch2_btree_id_str(e.p.btree_id),
541                                e.p.pos.inode,
542                                e.p.pos.offset);
543                 }
544
545                 fflush(stdout);
546                 sleep(1);
547         }
548         printf("\nDone\n");
549
550         close(progress_fd);
551         return 0;
552 }
553
554 /* option parsing */
555
556 void bch2_opt_strs_free(struct bch_opt_strs *opts)
557 {
558         unsigned i;
559
560         for (i = 0; i < bch2_opts_nr; i++) {
561                 free(opts->by_id[i]);
562                 opts->by_id[i] = NULL;
563         }
564 }
565
566 struct bch_opt_strs bch2_cmdline_opts_get(int *argc, char *argv[],
567                                           unsigned opt_types)
568 {
569         struct bch_opt_strs opts;
570         unsigned i = 1;
571
572         memset(&opts, 0, sizeof(opts));
573
574         while (i < *argc) {
575                 char *optstr = strcmp_prefix(argv[i], "--");
576                 char *valstr = NULL, *p;
577                 int optid, nr_args = 1;
578
579                 if (!optstr) {
580                         i++;
581                         continue;
582                 }
583
584                 optstr = strdup(optstr);
585
586                 p = optstr;
587                 while (isalpha(*p) || *p == '_')
588                         p++;
589
590                 if (*p == '=') {
591                         *p = '\0';
592                         valstr = p + 1;
593                 }
594
595                 optid = bch2_opt_lookup(optstr);
596                 if (optid < 0 ||
597                     !(bch2_opt_table[optid].flags & opt_types)) {
598                         i++;
599                         goto next;
600                 }
601
602                 if (!valstr &&
603                     bch2_opt_table[optid].type != BCH_OPT_BOOL) {
604                         nr_args = 2;
605                         valstr = argv[i + 1];
606                 }
607
608                 if (!valstr)
609                         valstr = "1";
610
611                 opts.by_id[optid] = strdup(valstr);
612
613                 *argc -= nr_args;
614                 memmove(&argv[i],
615                         &argv[i + nr_args],
616                         sizeof(char *) * (*argc - i));
617                 argv[*argc] = NULL;
618 next:
619                 free(optstr);
620         }
621
622         return opts;
623 }
624
625 struct bch_opts bch2_parse_opts(struct bch_opt_strs strs)
626 {
627         struct bch_opts opts = bch2_opts_empty();
628         struct printbuf err = PRINTBUF;
629         unsigned i;
630         int ret;
631         u64 v;
632
633         for (i = 0; i < bch2_opts_nr; i++) {
634                 if (!strs.by_id[i])
635                         continue;
636
637                 ret = bch2_opt_parse(NULL,
638                                      &bch2_opt_table[i],
639                                      strs.by_id[i], &v, &err);
640                 if (ret < 0)
641                         die("Invalid option %s", err.buf);
642
643                 bch2_opt_set_by_id(&opts, i, v);
644         }
645
646         printbuf_exit(&err);
647         return opts;
648 }
649
650 #define newline(c)              \
651         do {                    \
652                 printf("\n");   \
653                 c = 0;          \
654         } while(0)
655 void bch2_opts_usage(unsigned opt_types)
656 {
657         const struct bch_option *opt;
658         unsigned i, c = 0, helpcol = 30;
659
660
661
662         for (opt = bch2_opt_table;
663              opt < bch2_opt_table + bch2_opts_nr;
664              opt++) {
665                 if (!(opt->flags & opt_types))
666                         continue;
667
668                 c += printf("      --%s", opt->attr.name);
669
670                 switch (opt->type) {
671                 case BCH_OPT_BOOL:
672                         break;
673                 case BCH_OPT_STR:
674                         c += printf("=(");
675                         for (i = 0; opt->choices[i]; i++) {
676                                 if (i)
677                                         c += printf("|");
678                                 c += printf("%s", opt->choices[i]);
679                         }
680                         c += printf(")");
681                         break;
682                 default:
683                         c += printf("=%s", opt->hint);
684                         break;
685                 }
686
687                 if (opt->help) {
688                         const char *l = opt->help;
689
690                         if (c >= helpcol)
691                                 newline(c);
692
693                         while (1) {
694                                 const char *n = strchrnul(l, '\n');
695
696                                 while (c < helpcol) {
697                                         putchar(' ');
698                                         c++;
699                                 }
700                                 printf("%.*s", (int) (n - l), l);
701                                 newline(c);
702
703                                 if (!*n)
704                                         break;
705                                 l = n + 1;
706                         }
707                 } else {
708                         newline(c);
709                 }
710         }
711 }
712
713 dev_names bchu_fs_get_devices(struct bchfs_handle fs)
714 {
715         DIR *dir = fdopendir(fs.sysfs_fd);
716         struct dirent *d;
717         dev_names devs;
718
719         darray_init(&devs);
720
721         while ((errno = 0), (d = readdir(dir))) {
722                 struct dev_name n = { 0, NULL, NULL };
723
724                 if (sscanf(d->d_name, "dev-%u", &n.idx) != 1)
725                         continue;
726
727                 char *block_attr = mprintf("dev-%u/block", n.idx);
728
729                 char sysfs_block_buf[4096];
730                 ssize_t r = readlinkat(fs.sysfs_fd, block_attr,
731                                        sysfs_block_buf, sizeof(sysfs_block_buf));
732                 if (r > 0) {
733                         sysfs_block_buf[r] = '\0';
734                         n.dev = strdup(basename(sysfs_block_buf));
735                 }
736
737                 free(block_attr);
738
739                 char *label_attr = mprintf("dev-%u/label", n.idx);
740                 n.label = read_file_str(fs.sysfs_fd, label_attr);
741                 free(label_attr);
742
743                 char *durability_attr = mprintf("dev-%u/durability", n.idx);
744                 n.durability = read_file_u64(fs.sysfs_fd, durability_attr);
745                 free(durability_attr);
746
747                 darray_push(&devs, n);
748         }
749
750         closedir(dir);
751
752         return devs;
753 }