X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libbcachefs.c;h=98f058d757da7383b1a285d0aa972488ea17b327;hb=cc6479303f0672d5e07899009c64eb32821e170b;hp=0fdf5da4d6f9c460d99288e8b29a6c4be5a5408c;hpb=2831b89a7c8ba9262a00ec6d70d1355c02f84a67;p=bcachefs-tools-debian diff --git a/libbcachefs.c b/libbcachefs.c index 0fdf5da..98f058d 100644 --- a/libbcachefs.c +++ b/libbcachefs.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -6,23 +7,25 @@ #include #include #include +#include #include #include #include #include -#include "bcachefs_format.h" -#include "checksum.h" -#include "crypto.h" #include "libbcachefs.h" -#include "opts.h" -#include "super-io.h" +#include "crypto.h" +#include "libbcachefs/bcachefs_format.h" +#include "libbcachefs/btree_cache.h" +#include "libbcachefs/checksum.h" +#include "libbcachefs/disk_groups.h" +#include "libbcachefs/opts.h" +#include "libbcachefs/replicas.h" +#include "libbcachefs/super-io.h" #define NSEC_PER_SEC 1000000000L -#define BCH_MIN_NR_NBUCKETS (1 << 10) - /* minimum size filesystem we can create, given a bucket size: */ static u64 min_size(unsigned bucket_size) { @@ -121,13 +124,34 @@ void bch2_pick_bucket_size(struct format_opts opts, struct dev_opts *dev) } +static unsigned parse_target(struct bch_sb_handle *sb, + struct dev_opts *devs, size_t nr_devs, + const char *s) +{ + struct dev_opts *i; + int idx; + + if (!s) + return 0; + + for (i = devs; i < devs + nr_devs; i++) + if (!strcmp(s, i->path)) + return dev_to_target(i - devs); + + idx = bch2_disk_path_find(sb, s); + if (idx >= 0) + return group_to_target(idx); + + die("Invalid target %s", s); + return 0; +} + struct bch_sb *bch2_format(struct format_opts opts, struct dev_opts *devs, size_t nr_devs) { - struct bch_sb *sb; + struct bch_sb_handle sb = { NULL }; struct dev_opts *i; struct bch_sb_field_members *mi; - unsigned u64s; /* calculate block size: */ if (!opts.block_size) @@ -149,77 +173,61 @@ struct bch_sb *bch2_format(struct format_opts opts, min(opts.btree_node_size, i->bucket_size); } - if (!opts.max_journal_entry_size) { - /* 2 MB default: */ - opts.max_journal_entry_size = 4096; - } + if (!is_power_of_2(opts.block_size)) + die("block size must be power of 2"); - opts.max_journal_entry_size = - roundup_pow_of_two(opts.max_journal_entry_size); + if (!is_power_of_2(opts.btree_node_size)) + die("btree node size must be power of 2"); if (uuid_is_null(opts.uuid.b)) uuid_generate(opts.uuid.b); - sb = calloc(1, sizeof(*sb) + - sizeof(struct bch_sb_field_members) + - sizeof(struct bch_member) * nr_devs + - sizeof(struct bch_sb_field_crypt)); + if (bch2_sb_realloc(&sb, 0)) + die("insufficient memory"); - sb->version = cpu_to_le64(BCACHE_SB_VERSION_CDEV_V4); - sb->magic = BCACHE_MAGIC; - sb->block_size = cpu_to_le16(opts.block_size); - sb->user_uuid = opts.uuid; - sb->nr_devices = nr_devs; + sb.sb->version = cpu_to_le64(BCH_SB_VERSION_MAX); + sb.sb->magic = BCACHE_MAGIC; + sb.sb->block_size = cpu_to_le16(opts.block_size); + sb.sb->user_uuid = opts.uuid; + sb.sb->nr_devices = nr_devs; - uuid_generate(sb->uuid.b); + uuid_generate(sb.sb->uuid.b); if (opts.label) - strncpy((char *) sb->label, opts.label, sizeof(sb->label)); - - SET_BCH_SB_CSUM_TYPE(sb, opts.meta_csum_type); - SET_BCH_SB_META_CSUM_TYPE(sb, opts.meta_csum_type); - SET_BCH_SB_DATA_CSUM_TYPE(sb, opts.data_csum_type); - SET_BCH_SB_COMPRESSION_TYPE(sb, opts.compression_type); - - SET_BCH_SB_BTREE_NODE_SIZE(sb, opts.btree_node_size); - SET_BCH_SB_GC_RESERVE(sb, 8); - SET_BCH_SB_META_REPLICAS_WANT(sb, opts.meta_replicas); - SET_BCH_SB_META_REPLICAS_HAVE(sb, opts.meta_replicas); - SET_BCH_SB_META_REPLICAS_REQ(sb, opts.meta_replicas_required); - SET_BCH_SB_DATA_REPLICAS_WANT(sb, opts.data_replicas); - SET_BCH_SB_DATA_REPLICAS_HAVE(sb, opts.data_replicas); - SET_BCH_SB_DATA_REPLICAS_REQ(sb, opts.data_replicas_required); - SET_BCH_SB_ERROR_ACTION(sb, opts.on_error_action); - SET_BCH_SB_STR_HASH_TYPE(sb, BCH_STR_HASH_SIPHASH); - SET_BCH_SB_JOURNAL_ENTRY_SIZE(sb, ilog2(opts.max_journal_entry_size)); + memcpy(sb.sb->label, + opts.label, + min(strlen(opts.label), sizeof(sb.sb->label))); + + SET_BCH_SB_CSUM_TYPE(sb.sb, opts.meta_csum_type); + SET_BCH_SB_META_CSUM_TYPE(sb.sb, opts.meta_csum_type); + SET_BCH_SB_DATA_CSUM_TYPE(sb.sb, opts.data_csum_type); + SET_BCH_SB_COMPRESSION_TYPE(sb.sb, opts.compression_type); + SET_BCH_SB_BACKGROUND_COMPRESSION_TYPE(sb.sb, + opts.background_compression_type); + + SET_BCH_SB_BTREE_NODE_SIZE(sb.sb, opts.btree_node_size); + SET_BCH_SB_GC_RESERVE(sb.sb, 8); + SET_BCH_SB_META_REPLICAS_WANT(sb.sb, opts.meta_replicas); + SET_BCH_SB_META_REPLICAS_REQ(sb.sb, opts.meta_replicas_required); + SET_BCH_SB_DATA_REPLICAS_WANT(sb.sb, opts.data_replicas); + SET_BCH_SB_DATA_REPLICAS_REQ(sb.sb, opts.data_replicas_required); + SET_BCH_SB_ERROR_ACTION(sb.sb, opts.on_error_action); + SET_BCH_SB_STR_HASH_TYPE(sb.sb, BCH_STR_HASH_SIPHASH); + SET_BCH_SB_ENCODED_EXTENT_MAX_BITS(sb.sb,ilog2(opts.encoded_extent_max)); + + SET_BCH_SB_POSIX_ACL(sb.sb, 1); struct timespec now; if (clock_gettime(CLOCK_REALTIME, &now)) - die("error getting current time: %s", strerror(errno)); - - sb->time_base_lo = cpu_to_le64(now.tv_sec * NSEC_PER_SEC + now.tv_nsec); - sb->time_precision = cpu_to_le32(1); + die("error getting current time: %m"); - if (opts.encrypted) { - struct bch_sb_field_crypt *crypt = vstruct_end(sb); - - u64s = sizeof(struct bch_sb_field_crypt) / sizeof(u64); + sb.sb->time_base_lo = cpu_to_le64(now.tv_sec * NSEC_PER_SEC + now.tv_nsec); + sb.sb->time_precision = cpu_to_le32(1); - le32_add_cpu(&sb->u64s, u64s); - crypt->field.u64s = cpu_to_le32(u64s); - crypt->field.type = BCH_SB_FIELD_crypt; - - bch_sb_crypt_init(sb, crypt, opts.passphrase); - SET_BCH_SB_ENCRYPTION_TYPE(sb, 1); - } - - mi = vstruct_end(sb); - u64s = (sizeof(struct bch_sb_field_members) + - sizeof(struct bch_member) * nr_devs) / sizeof(u64); - - le32_add_cpu(&sb->u64s, u64s); - mi->field.u64s = cpu_to_le32(u64s); - mi->field.type = BCH_SB_FIELD_members; + /* Member info: */ + mi = bch2_sb_resize_members(&sb, + (sizeof(*mi) + sizeof(struct bch_member) * + nr_devs) / sizeof(u64)); for (i = devs; i < devs + nr_devs; i++) { struct bch_member *m = mi->members + (i - devs); @@ -229,15 +237,47 @@ struct bch_sb *bch2_format(struct format_opts opts, m->first_bucket = 0; m->bucket_size = cpu_to_le16(i->bucket_size); - SET_BCH_MEMBER_TIER(m, i->tier); SET_BCH_MEMBER_REPLACEMENT(m, CACHE_REPLACEMENT_LRU); SET_BCH_MEMBER_DISCARD(m, i->discard); + SET_BCH_MEMBER_DATA_ALLOWED(m, i->data_allowed); + SET_BCH_MEMBER_DURABILITY(m, i->durability + 1); } + /* Disk groups */ for (i = devs; i < devs + nr_devs; i++) { - sb->dev_idx = i - devs; + struct bch_member *m = mi->members + (i - devs); + int idx; + + if (!i->group) + continue; + + idx = bch2_disk_path_find_or_create(&sb, i->group); + if (idx < 0) + die("error creating disk path: %s", idx); + + SET_BCH_MEMBER_GROUP(m, idx + 1); + } + + SET_BCH_SB_FOREGROUND_TARGET(sb.sb, + parse_target(&sb, devs, nr_devs, opts.foreground_target)); + SET_BCH_SB_BACKGROUND_TARGET(sb.sb, + parse_target(&sb, devs, nr_devs, opts.background_target)); + SET_BCH_SB_PROMOTE_TARGET(sb.sb, + parse_target(&sb, devs, nr_devs, opts.promote_target)); + + /* Crypt: */ + if (opts.encrypted) { + struct bch_sb_field_crypt *crypt = + bch2_sb_resize_crypt(&sb, sizeof(*crypt) / sizeof(u64)); - init_layout(&sb->layout, opts.block_size, + bch_sb_crypt_init(sb.sb, crypt, opts.passphrase); + SET_BCH_SB_ENCRYPTION_TYPE(sb.sb, 1); + } + + for (i = devs; i < devs + nr_devs; i++) { + sb.sb->dev_idx = i - devs; + + init_layout(&sb.sb->layout, opts.block_size, i->sb_offset, i->sb_end); if (i->sb_offset == BCH_SB_SECTOR) { @@ -247,18 +287,19 @@ struct bch_sb *bch2_format(struct format_opts opts, xpwrite(i->fd, zeroes, BCH_SB_SECTOR << 9, 0); } - bch2_super_write(i->fd, sb); + bch2_super_write(i->fd, sb.sb); close(i->fd); } - return sb; + return sb.sb; } void bch2_super_write(int fd, struct bch_sb *sb) { struct nonce nonce = { 0 }; - for (unsigned i = 0; i < sb->layout.nr_superblocks; i++) { + unsigned i; + for (i = 0; i < sb->layout.nr_superblocks; i++) { sb->offset = sb->layout.sb_offset[i]; if (sb->offset == BCH_SB_SECTOR) { @@ -293,56 +334,393 @@ struct bch_sb *__bch2_super_read(int fd, u64 sector) return ret; } -struct bch_sb *bch2_super_read(const char *path) +static unsigned get_dev_has_data(struct bch_sb *sb, unsigned dev) +{ + struct bch_sb_field_replicas *replicas; + struct bch_replicas_entry *r; + unsigned i, data_has = 0; + + replicas = bch2_sb_get_replicas(sb); + + if (replicas) + for_each_replicas_entry(replicas, r) + for (i = 0; i < r->nr_devs; i++) + if (r->devs[i] == dev) + data_has |= 1 << r->data_type; + + return data_has; +} + +static int bch2_sb_get_target(struct bch_sb *sb, char *buf, size_t len, u64 v) +{ + struct target t = target_decode(v); + int ret; + + switch (t.type) { + case TARGET_NULL: + return scnprintf(buf, len, "none"); + case TARGET_DEV: { + struct bch_sb_field_members *mi = bch2_sb_get_members(sb); + struct bch_member *m = mi->members + t.dev; + + if (bch2_dev_exists(sb, mi, t.dev)) { + char uuid_str[40]; + + uuid_unparse(m->uuid.b, uuid_str); + + ret = scnprintf(buf, len, "Device %u (%s)", t.dev, + uuid_str); + } else { + ret = scnprintf(buf, len, "Bad device %u", t.dev); + } + + break; + } + case TARGET_GROUP: { + struct bch_sb_field_disk_groups *gi; + gi = bch2_sb_get_disk_groups(sb); + + struct bch_disk_group *g = gi->entries + t.group; + + if (t.group < disk_groups_nr(gi) && !BCH_GROUP_DELETED(g)) { + ret = scnprintf(buf, len, "Group %u (%.*s)", t.group, + BCH_SB_LABEL_SIZE, g->label); + } else { + ret = scnprintf(buf, len, "Bad group %u", t.group); + } + break; + } + default: + BUG(); + } + + return ret; +} + +/* superblock printing: */ + +static void bch2_sb_print_layout(struct bch_sb *sb, enum units units) +{ + struct bch_sb_layout *l = &sb->layout; + unsigned i; + + printf(" type: %u\n" + " superblock max size: %s\n" + " nr superblocks: %u\n" + " Offsets: ", + l->layout_type, + pr_units(1 << l->sb_max_size_bits, units), + l->nr_superblocks); + + for (i = 0; i < l->nr_superblocks; i++) { + if (i) + printf(", "); + printf("%llu", le64_to_cpu(l->sb_offset[i])); + } + putchar('\n'); +} + +static void bch2_sb_print_journal(struct bch_sb *sb, struct bch_sb_field *f, + enum units units) { - int fd = xopen(path, O_RDONLY); - struct bch_sb *sb = __bch2_super_read(fd, BCH_SB_SECTOR); - close(fd); - return sb; + struct bch_sb_field_journal *journal = field_to_type(f, journal); + unsigned i, nr = bch2_nr_journal_buckets(journal); + + printf(" Buckets: "); + for (i = 0; i < nr; i++) { + if (i) + putchar(' '); + printf("%llu", le64_to_cpu(journal->buckets[i])); + } + putchar('\n'); } -void bch2_super_print(struct bch_sb *sb, int units) +static void bch2_sb_print_members(struct bch_sb *sb, struct bch_sb_field *f, + enum units units) +{ + struct bch_sb_field_members *mi = field_to_type(f, members); + struct bch_sb_field_disk_groups *gi = bch2_sb_get_disk_groups(sb); + unsigned i; + + for (i = 0; i < sb->nr_devices; i++) { + struct bch_member *m = mi->members + i; + time_t last_mount = le64_to_cpu(m->last_mount); + char member_uuid_str[40]; + char data_allowed_str[100]; + char data_has_str[100]; + char group[BCH_SB_LABEL_SIZE+10]; + char time_str[64]; + + if (!bch2_member_exists(m)) + continue; + + uuid_unparse(m->uuid.b, member_uuid_str); + + if (BCH_MEMBER_GROUP(m)) { + unsigned idx = BCH_MEMBER_GROUP(m) - 1; + + if (idx < disk_groups_nr(gi)) { + snprintf(group, sizeof(group), "%.*s (%u)", + BCH_SB_LABEL_SIZE, + gi->entries[idx].label, idx); + } else { + strcpy(group, "(bad disk groups section)"); + } + } else { + strcpy(group, "(none)"); + } + + bch2_flags_to_text(&PBUF(data_allowed_str), + bch2_data_types, + BCH_MEMBER_DATA_ALLOWED(m)); + if (!data_allowed_str[0]) + strcpy(data_allowed_str, "(none)"); + + bch2_flags_to_text(&PBUF(data_has_str), + bch2_data_types, + get_dev_has_data(sb, i)); + if (!data_has_str[0]) + strcpy(data_has_str, "(none)"); + + if (last_mount) { + struct tm *tm = localtime(&last_mount); + size_t err = strftime(time_str, sizeof(time_str), "%c", tm); + if (!err) + strcpy(time_str, "(formatting error)"); + } else { + strcpy(time_str, "(never)"); + } + + printf(" Device %u:\n" + " UUID: %s\n" + " Size: %s\n" + " Bucket size: %s\n" + " First bucket: %u\n" + " Buckets: %llu\n" + " Last mount: %s\n" + " State: %s\n" + " Group: %s\n" + " Data allowed: %s\n" + + " Has data: %s\n" + + " Replacement policy: %s\n" + " Discard: %llu\n", + i, member_uuid_str, + pr_units(le16_to_cpu(m->bucket_size) * + le64_to_cpu(m->nbuckets), units), + pr_units(le16_to_cpu(m->bucket_size), units), + le16_to_cpu(m->first_bucket), + le64_to_cpu(m->nbuckets), + time_str, + + BCH_MEMBER_STATE(m) < BCH_MEMBER_STATE_NR + ? bch2_dev_state[BCH_MEMBER_STATE(m)] + : "unknown", + + group, + data_allowed_str, + data_has_str, + + BCH_MEMBER_REPLACEMENT(m) < CACHE_REPLACEMENT_NR + ? bch2_cache_replacement_policies[BCH_MEMBER_REPLACEMENT(m)] + : "unknown", + + BCH_MEMBER_DISCARD(m)); + } +} + +static void bch2_sb_print_crypt(struct bch_sb *sb, struct bch_sb_field *f, + enum units units) +{ + struct bch_sb_field_crypt *crypt = field_to_type(f, crypt); + + printf(" KFD: %llu\n" + " scrypt n: %llu\n" + " scrypt r: %llu\n" + " scrypt p: %llu\n", + BCH_CRYPT_KDF_TYPE(crypt), + BCH_KDF_SCRYPT_N(crypt), + BCH_KDF_SCRYPT_R(crypt), + BCH_KDF_SCRYPT_P(crypt)); +} + +static void bch2_sb_print_replicas_v0(struct bch_sb *sb, struct bch_sb_field *f, + enum units units) +{ + struct bch_sb_field_replicas_v0 *replicas = field_to_type(f, replicas_v0); + struct bch_replicas_entry_v0 *e; + unsigned i; + + for_each_replicas_entry(replicas, e) { + printf_pad(32, " %s:", bch2_data_types[e->data_type]); + + putchar('['); + for (i = 0; i < e->nr_devs; i++) { + if (i) + putchar(' '); + printf("%u", e->devs[i]); + } + printf("]\n"); + } +} + +static void bch2_sb_print_replicas(struct bch_sb *sb, struct bch_sb_field *f, + enum units units) +{ + struct bch_sb_field_replicas *replicas = field_to_type(f, replicas); + struct bch_replicas_entry *e; + unsigned i; + + for_each_replicas_entry(replicas, e) { + printf_pad(32, " %s: %u/%u", + bch2_data_types[e->data_type], + e->nr_required, + e->nr_devs); + + putchar('['); + for (i = 0; i < e->nr_devs; i++) { + if (i) + putchar(' '); + printf("%u", e->devs[i]); + } + printf("]\n"); + } +} + +static void bch2_sb_print_quota(struct bch_sb *sb, struct bch_sb_field *f, + enum units units) +{ +} + +static void bch2_sb_print_disk_groups(struct bch_sb *sb, struct bch_sb_field *f, + enum units units) +{ +} + +static void bch2_sb_print_clean(struct bch_sb *sb, struct bch_sb_field *f, + enum units units) +{ +} + +typedef void (*sb_field_print_fn)(struct bch_sb *, struct bch_sb_field *, enum units); + +struct bch_sb_field_toolops { + sb_field_print_fn print; +}; + +static const struct bch_sb_field_toolops bch2_sb_field_ops[] = { +#define x(f, nr) \ + [BCH_SB_FIELD_##f] = { \ + .print = bch2_sb_print_##f, \ + }, + BCH_SB_FIELDS() +#undef x +}; + +static inline void bch2_sb_field_print(struct bch_sb *sb, + struct bch_sb_field *f, + enum units units) +{ + unsigned type = le32_to_cpu(f->type); + + if (type < BCH_SB_FIELD_NR) + bch2_sb_field_ops[type].print(sb, f, units); + else + printf("(unknown field %u)\n", type); +} + +void bch2_sb_print(struct bch_sb *sb, bool print_layout, + unsigned fields, enum units units) { struct bch_sb_field_members *mi; - char user_uuid_str[40], internal_uuid_str[40], member_uuid_str[40]; + char user_uuid_str[40], internal_uuid_str[40]; + char fields_have_str[200]; char label[BCH_SB_LABEL_SIZE + 1]; - unsigned i; + char time_str[64]; + char foreground_str[64]; + char background_str[64]; + char promote_str[64]; + struct bch_sb_field *f; + u64 fields_have = 0; + unsigned nr_devices = 0; + time_t time_base = le64_to_cpu(sb->time_base_lo) / NSEC_PER_SEC; + + memcpy(label, sb->label, BCH_SB_LABEL_SIZE); + label[BCH_SB_LABEL_SIZE] = '\0'; - memset(label, 0, sizeof(label)); - memcpy(label, sb->label, sizeof(sb->label)); uuid_unparse(sb->user_uuid.b, user_uuid_str); uuid_unparse(sb->uuid.b, internal_uuid_str); + if (time_base) { + struct tm *tm = localtime(&time_base); + size_t err = strftime(time_str, sizeof(time_str), "%c", tm); + if (!err) + strcpy(time_str, "(formatting error)"); + } else { + strcpy(time_str, "(not set)"); + } + + mi = bch2_sb_get_members(sb); + if (mi) { + struct bch_member *m; + + for (m = mi->members; + m < mi->members + sb->nr_devices; + m++) + nr_devices += bch2_member_exists(m); + } + + bch2_sb_get_target(sb, foreground_str, sizeof(foreground_str), + BCH_SB_FOREGROUND_TARGET(sb)); + + bch2_sb_get_target(sb, background_str, sizeof(background_str), + BCH_SB_BACKGROUND_TARGET(sb)); + + bch2_sb_get_target(sb, promote_str, sizeof(promote_str), + BCH_SB_PROMOTE_TARGET(sb)); + + vstruct_for_each(sb, f) + fields_have |= 1 << le32_to_cpu(f->type); + bch2_flags_to_text(&PBUF(fields_have_str), + bch2_sb_fields, fields_have); + printf("External UUID: %s\n" "Internal UUID: %s\n" "Label: %s\n" "Version: %llu\n" + "Created: %s\n" "Block_size: %s\n" "Btree node size: %s\n" - "Max journal entry size: %s\n" "Error action: %s\n" "Clean: %llu\n" - "Metadata replicas: have %llu, want %llu\n" - "Data replicas: have %llu, want %llu\n" + "Metadata replicas: %llu\n" + "Data replicas: %llu\n" + + "Metadata checksum type: %s (%llu)\n" + "Data checksum type: %s (%llu)\n" + "Compression type: %s (%llu)\n" - "Metadata checksum type: %s\n" - "Data checksum type: %s\n" - "Compression type: %s\n" + "Foreground write target: %s\n" + "Background write target: %s\n" + "Promote target: %s\n" - "String hash type: %s\n" + "String hash type: %s (%llu)\n" "32 bit inodes: %llu\n" "GC reserve percentage: %llu%%\n" "Root reserve percentage: %llu%%\n" - "Devices: %u\n", + "Devices: %u live, %u total\n" + "Sections: %s\n" + "Superblock size: %llu\n", user_uuid_str, internal_uuid_str, label, le64_to_cpu(sb->version), + time_str, pr_units(le16_to_cpu(sb->block_size), units), pr_units(BCH_SB_BTREE_NODE_SIZE(sb), units), - pr_units(1U << BCH_SB_JOURNAL_ENTRY_SIZE(sb), units), BCH_SB_ERROR_ACTION(sb) < BCH_NR_ERROR_ACTIONS ? bch2_error_actions[BCH_SB_ERROR_ACTION(sb)] @@ -350,79 +728,205 @@ void bch2_super_print(struct bch_sb *sb, int units) BCH_SB_CLEAN(sb), - BCH_SB_META_REPLICAS_HAVE(sb), BCH_SB_META_REPLICAS_WANT(sb), - BCH_SB_DATA_REPLICAS_HAVE(sb), BCH_SB_DATA_REPLICAS_WANT(sb), - BCH_SB_META_CSUM_TYPE(sb) < BCH_CSUM_NR + BCH_SB_META_CSUM_TYPE(sb) < BCH_CSUM_OPT_NR ? bch2_csum_types[BCH_SB_META_CSUM_TYPE(sb)] : "unknown", + BCH_SB_META_CSUM_TYPE(sb), - BCH_SB_DATA_CSUM_TYPE(sb) < BCH_CSUM_NR + BCH_SB_DATA_CSUM_TYPE(sb) < BCH_CSUM_OPT_NR ? bch2_csum_types[BCH_SB_DATA_CSUM_TYPE(sb)] : "unknown", + BCH_SB_DATA_CSUM_TYPE(sb), - BCH_SB_COMPRESSION_TYPE(sb) < BCH_COMPRESSION_NR + BCH_SB_COMPRESSION_TYPE(sb) < BCH_COMPRESSION_OPT_NR ? bch2_compression_types[BCH_SB_COMPRESSION_TYPE(sb)] : "unknown", + BCH_SB_COMPRESSION_TYPE(sb), + + foreground_str, + background_str, + promote_str, BCH_SB_STR_HASH_TYPE(sb) < BCH_STR_HASH_NR ? bch2_str_hash_types[BCH_SB_STR_HASH_TYPE(sb)] : "unknown", + BCH_SB_STR_HASH_TYPE(sb), BCH_SB_INODE_32BIT(sb), BCH_SB_GC_RESERVE(sb), BCH_SB_ROOT_RESERVE(sb), - sb->nr_devices); + nr_devices, sb->nr_devices, + fields_have_str, + vstruct_bytes(sb)); - mi = bch2_sb_get_members(sb); - if (!mi) { - printf("Member info section missing\n"); - return; + if (print_layout) { + printf("\n" + "Layout:\n"); + bch2_sb_print_layout(sb, units); } - for (i = 0; i < sb->nr_devices; i++) { - struct bch_member *m = mi->members + i; - time_t last_mount = le64_to_cpu(m->last_mount); + vstruct_for_each(sb, f) { + unsigned type = le32_to_cpu(f->type); + char name[60]; - uuid_unparse(m->uuid.b, member_uuid_str); + if (!(fields & (1 << type))) + continue; - printf("\n" - "Device %u:\n" - " UUID: %s\n" - " Size: %s\n" - " Bucket size: %s\n" - " First bucket: %u\n" - " Buckets: %llu\n" - " Last mount: %s\n" - " State: %s\n" - " Tier: %llu\n" - " Has metadata: %llu\n" - " Has data: %llu\n" - " Replacement policy: %s\n" - " Discard: %llu\n", - i, member_uuid_str, - pr_units(le16_to_cpu(m->bucket_size) * - le64_to_cpu(m->nbuckets), units), - pr_units(le16_to_cpu(m->bucket_size), units), - le16_to_cpu(m->first_bucket), - le64_to_cpu(m->nbuckets), - last_mount ? ctime(&last_mount) : "(never)", + if (type < BCH_SB_FIELD_NR) { + scnprintf(name, sizeof(name), "%s", bch2_sb_fields[type]); + name[0] = toupper(name[0]); + } else { + scnprintf(name, sizeof(name), "(unknown field %u)", type); + } - BCH_MEMBER_STATE(m) < BCH_MEMBER_STATE_NR - ? bch2_dev_state[BCH_MEMBER_STATE(m)] - : "unknown", + printf("\n%s (size %llu):\n", name, vstruct_bytes(f)); + if (type < BCH_SB_FIELD_NR) + bch2_sb_field_print(sb, f, units); + } +} - BCH_MEMBER_TIER(m), - BCH_MEMBER_HAS_METADATA(m), - BCH_MEMBER_HAS_DATA(m), +/* ioctl interface: */ - BCH_MEMBER_REPLACEMENT(m) < CACHE_REPLACEMENT_NR - ? bch2_cache_replacement_policies[BCH_MEMBER_REPLACEMENT(m)] - : "unknown", +/* Global control device: */ +int bcachectl_open(void) +{ + return xopen("/dev/bcachefs-ctl", O_RDWR); +} - BCH_MEMBER_DISCARD(m)); +/* Filesystem handles (ioctl, sysfs dir): */ + +#define SYSFS_BASE "/sys/fs/bcachefs/" + +void bcache_fs_close(struct bchfs_handle fs) +{ + close(fs.ioctl_fd); + close(fs.sysfs_fd); +} + +struct bchfs_handle bcache_fs_open(const char *path) +{ + struct bchfs_handle ret; + + if (!uuid_parse(path, ret.uuid.b)) { + /* It's a UUID, look it up in sysfs: */ + char *sysfs = mprintf(SYSFS_BASE "%s", path); + ret.sysfs_fd = xopen(sysfs, O_RDONLY); + + char *minor = read_file_str(ret.sysfs_fd, "minor"); + char *ctl = mprintf("/dev/bcachefs%s-ctl", minor); + ret.ioctl_fd = xopen(ctl, O_RDWR); + + free(sysfs); + free(minor); + free(ctl); + } else { + /* It's a path: */ + ret.ioctl_fd = xopen(path, O_RDONLY); + + struct bch_ioctl_query_uuid uuid; + if (ioctl(ret.ioctl_fd, BCH_IOCTL_QUERY_UUID, &uuid) < 0) + die("error opening %s: not a bcachefs filesystem", path); + + ret.uuid = uuid.uuid; + + char uuid_str[40]; + uuid_unparse(uuid.uuid.b, uuid_str); + + char *sysfs = mprintf(SYSFS_BASE "%s", uuid_str); + ret.sysfs_fd = xopen(sysfs, O_RDONLY); + free(sysfs); + } + + return ret; +} + +/* + * Given a path to a block device, open the filesystem it belongs to; also + * return the device's idx: + */ +struct bchfs_handle bchu_fs_open_by_dev(const char *path, unsigned *idx) +{ + char buf[1024], *uuid_str; + + struct stat stat = xstat(path); + + if (!S_ISBLK(stat.st_mode)) + die("%s is not a block device", path); + + char *sysfs = mprintf("/sys/dev/block/%u:%u/bcachefs", + major(stat.st_dev), + minor(stat.st_dev)); + ssize_t len = readlink(sysfs, buf, sizeof(buf)); + free(sysfs); + + if (len > 0) { + char *p = strrchr(buf, '/'); + if (!p || sscanf(p + 1, "dev-%u", idx) != 1) + die("error parsing sysfs"); + + *p = '\0'; + p = strrchr(buf, '/'); + uuid_str = p + 1; + } else { + struct bch_opts opts = bch2_opts_empty(); + + opt_set(opts, noexcl, true); + opt_set(opts, nochanges, true); + + struct bch_sb_handle sb; + int ret = bch2_read_super(path, &opts, &sb); + if (ret) + die("Error opening %s: %s", path, strerror(-ret)); + + *idx = sb.sb->dev_idx; + uuid_str = buf; + uuid_unparse(sb.sb->user_uuid.b, uuid_str); + + bch2_free_super(&sb); } + + return bcache_fs_open(uuid_str); +} + +int bchu_data(struct bchfs_handle fs, struct bch_ioctl_data cmd) +{ + int progress_fd = xioctl(fs.ioctl_fd, BCH_IOCTL_DATA, &cmd); + + while (1) { + struct bch_ioctl_data_event e; + + if (read(progress_fd, &e, sizeof(e)) != sizeof(e)) + die("error reading from progress fd %m"); + + if (e.type) + continue; + + if (e.p.data_type == U8_MAX) + break; + + printf("\33[2K\r"); + + printf("%llu%% complete: current position %s", + e.p.sectors_done * 100 / e.p.sectors_total, + bch2_data_types[e.p.data_type]); + + switch (e.p.data_type) { + case BCH_DATA_BTREE: + case BCH_DATA_USER: + printf(" %s:%llu:%llu", + bch2_btree_ids[e.p.btree_id], + e.p.pos.inode, + e.p.pos.offset); + } + + sleep(1); + } + printf("\nDone\n"); + + close(progress_fd); + return 0; }