X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libbcachefs.c;h=82f131cf483b99b14129b951a99d2b78cda18299;hb=f27b135285d032d9570a1accb00412e111b38878;hp=f68a45f23d8314b71f4ad10792ed2b43167afa2a;hpb=63065c01285601afbe2457e92729efc11581e37d;p=bcachefs-tools-debian diff --git a/libbcachefs.c b/libbcachefs.c index f68a45f..82f131c 100644 --- a/libbcachefs.c +++ b/libbcachefs.c @@ -1,3 +1,5 @@ +#include +#include #include #include #include @@ -6,259 +8,355 @@ #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/journal_seq_blacklist.h" +#include "libbcachefs/opts.h" +#include "libbcachefs/replicas.h" +#include "libbcachefs/super-io.h" +#include "tools-util.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) -{ - return BCH_MIN_NR_NBUCKETS * bucket_size; -} - -static void init_layout(struct bch_sb_layout *l, unsigned block_size, - u64 start, u64 end) +static void init_layout(struct bch_sb_layout *l, + unsigned block_size, + unsigned sb_size, + u64 sb_start, u64 sb_end) { - unsigned sb_size; - u64 backup; /* offset of 2nd sb */ + u64 sb_pos = sb_start; + unsigned i; memset(l, 0, sizeof(*l)); - if (start != BCH_SB_SECTOR) - start = round_up(start, block_size); - end = round_down(end, block_size); - - if (start >= end) - die("insufficient space for superblocks"); - - /* - * Create two superblocks in the allowed range: reserve a maximum of 64k - */ - sb_size = min_t(u64, 128, end - start / 2); - - backup = start + sb_size; - backup = round_up(backup, block_size); - - backup = min(backup, end); - - sb_size = min(end - backup, backup- start); - sb_size = rounddown_pow_of_two(sb_size); - - if (sb_size < 8) - die("insufficient space for superblocks"); - - l->magic = BCACHE_MAGIC; + l->magic = BCHFS_MAGIC; l->layout_type = 0; l->nr_superblocks = 2; l->sb_max_size_bits = ilog2(sb_size); - l->sb_offset[0] = cpu_to_le64(start); - l->sb_offset[1] = cpu_to_le64(backup); + + /* Create two superblocks in the allowed range: */ + for (i = 0; i < l->nr_superblocks; i++) { + if (sb_pos != BCH_SB_SECTOR) + sb_pos = round_up(sb_pos, block_size); + + l->sb_offset[i] = cpu_to_le64(sb_pos); + sb_pos += sb_size; + } + + if (sb_pos > sb_end) + die("insufficient space for superblocks: start %llu end %llu > %llu size %u", + sb_start, sb_pos, sb_end, sb_size); } -void bch2_pick_bucket_size(struct format_opts opts, struct dev_opts *dev) +/* minimum size filesystem we can create, given a bucket size: */ +static u64 min_size(unsigned bucket_size) { - if (!dev->sb_offset) { - dev->sb_offset = BCH_SB_SECTOR; - dev->sb_end = BCH_SB_SECTOR + 256; - } + return BCH_MIN_NR_NBUCKETS * bucket_size; +} - if (!dev->size) - dev->size = get_size(dev->path, dev->fd) >> 9; +u64 bch2_pick_bucket_size(struct bch_opts opts, struct dev_opts *dev) +{ + u64 bucket_size; - if (!dev->bucket_size) { - if (dev->size < min_size(opts.block_size)) - die("cannot format %s, too small (%llu sectors, min %llu)", - dev->path, dev->size, min_size(opts.block_size)); + if (dev->size < min_size(opts.block_size)) + die("cannot format %s, too small (%llu bytes, min %llu)", + dev->path, dev->size, min_size(opts.block_size)); - /* Bucket size must be >= block size: */ - dev->bucket_size = opts.block_size; + /* Bucket size must be >= block size: */ + bucket_size = opts.block_size; - /* Bucket size must be >= btree node size: */ - dev->bucket_size = max(dev->bucket_size, opts.btree_node_size); + /* Bucket size must be >= btree node size: */ + if (opt_defined(opts, btree_node_size)) + bucket_size = max_t(unsigned, bucket_size, + opts.btree_node_size); - /* Want a bucket size of at least 128k, if possible: */ - dev->bucket_size = max(dev->bucket_size, 256U); + /* Want a bucket size of at least 128k, if possible: */ + bucket_size = max(bucket_size, 128ULL << 10); - if (dev->size >= min_size(dev->bucket_size)) { - unsigned scale = max(1, - ilog2(dev->size / min_size(dev->bucket_size)) / 4); + if (dev->size >= min_size(bucket_size)) { + unsigned scale = max(1, + ilog2(dev->size / min_size(bucket_size)) / 4); - scale = rounddown_pow_of_two(scale); + scale = rounddown_pow_of_two(scale); - /* max bucket size 1 mb */ - dev->bucket_size = min(dev->bucket_size * scale, 1U << 11); - } else { - do { - dev->bucket_size /= 2; - } while (dev->size < min_size(dev->bucket_size)); - } + /* max bucket size 1 mb */ + bucket_size = min(bucket_size * scale, 1ULL << 20); + } else { + do { + bucket_size /= 2; + } while (dev->size < min_size(bucket_size)); } - dev->nbuckets = dev->size / dev->bucket_size; + return bucket_size; +} +void bch2_check_bucket_size(struct bch_opts opts, struct dev_opts *dev) +{ if (dev->bucket_size < opts.block_size) - die("Bucket size cannot be smaller than block size"); + die("Bucket size (%llu) cannot be smaller than block size (%u)", + dev->bucket_size, opts.block_size); - if (dev->bucket_size < opts.btree_node_size) - die("Bucket size cannot be smaller than btree node size"); + if (opt_defined(opts, btree_node_size) && + dev->bucket_size < opts.btree_node_size) + die("Bucket size (%llu) cannot be smaller than btree node size (%u)", + dev->bucket_size, opts.btree_node_size); if (dev->nbuckets < BCH_MIN_NR_NBUCKETS) - die("Not enough buckets: %llu, need %u (bucket size %u)", + die("Not enough buckets: %llu, need %u (bucket size %llu)", dev->nbuckets, BCH_MIN_NR_NBUCKETS, dev->bucket_size); + if (dev->bucket_size > (u32) U16_MAX << 9) + die("Bucket size (%llu) too big (max %u)", + dev->bucket_size, (u32) U16_MAX << 9); +} + +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 *bch2_format(struct bch_opt_strs fs_opt_strs, + struct bch_opts fs_opts, + 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; + unsigned max_dev_block_size = 0; + unsigned opt_id; + u64 min_bucket_size = U64_MAX; + + for (i = devs; i < devs + nr_devs; i++) + max_dev_block_size = max(max_dev_block_size, get_blocksize(i->bdev->bd_buffered_fd)); /* calculate block size: */ - if (!opts.block_size) - for (i = devs; i < devs + nr_devs; i++) - opts.block_size = max(opts.block_size, - get_blocksize(i->path, i->fd)); + if (!opt_defined(fs_opts, block_size)) { + opt_set(fs_opts, block_size, max_dev_block_size); + } else if (fs_opts.block_size < max_dev_block_size) + die("blocksize too small: %u, must be greater than device blocksize %u", + fs_opts.block_size, max_dev_block_size); + + /* get device size, if it wasn't specified: */ + for (i = devs; i < devs + nr_devs; i++) + if (!i->size) + i->size = get_size(i->bdev->bd_buffered_fd); /* calculate bucket sizes: */ for (i = devs; i < devs + nr_devs; i++) - bch2_pick_bucket_size(opts, i); + min_bucket_size = min(min_bucket_size, + i->bucket_size ?: bch2_pick_bucket_size(fs_opts, i)); + + for (i = devs; i < devs + nr_devs; i++) + if (!i->bucket_size) + i->bucket_size = min_bucket_size; + + for (i = devs; i < devs + nr_devs; i++) { + i->nbuckets = i->size / i->bucket_size; + bch2_check_bucket_size(fs_opts, i); + } /* calculate btree node size: */ - if (!opts.btree_node_size) { + if (!opt_defined(fs_opts, btree_node_size)) { /* 256k default btree node size */ - opts.btree_node_size = 512; + opt_set(fs_opts, btree_node_size, 256 << 10); for (i = devs; i < devs + nr_devs; i++) - opts.btree_node_size = - min(opts.btree_node_size, i->bucket_size); + fs_opts.btree_node_size = + min_t(unsigned, fs_opts.btree_node_size, + i->bucket_size); } 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)); - - 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; + if (bch2_sb_realloc(&sb, 0)) + die("insufficient memory"); - uuid_generate(sb->uuid.b); + sb.sb->version = le16_to_cpu(opts.version); + sb.sb->version_min = le16_to_cpu(opts.version); + sb.sb->magic = BCHFS_MAGIC; + sb.sb->user_uuid = opts.uuid; + sb.sb->nr_devices = nr_devs; - 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_REQ(sb, opts.meta_replicas_required); - SET_BCH_SB_DATA_REPLICAS_WANT(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); - - struct timespec now; - if (clock_gettime(CLOCK_REALTIME, &now)) - die("error getting current time: %m"); + if (opts.version == bcachefs_metadata_version_current) + sb.sb->features[0] |= cpu_to_le64(BCH_SB_FEATURES_ALL); - sb->time_base_lo = cpu_to_le64(now.tv_sec * NSEC_PER_SEC + now.tv_nsec); - sb->time_precision = cpu_to_le32(1); + uuid_generate(sb.sb->uuid.b); - if (opts.encrypted) { - struct bch_sb_field_crypt *crypt = vstruct_end(sb); + if (opts.label) + memcpy(sb.sb->label, + opts.label, + min(strlen(opts.label), sizeof(sb.sb->label))); - u64s = sizeof(struct bch_sb_field_crypt) / sizeof(u64); + for (opt_id = 0; + opt_id < bch2_opts_nr; + opt_id++) { + u64 v; - le32_add_cpu(&sb->u64s, u64s); - crypt->field.u64s = cpu_to_le32(u64s); - crypt->field.type = BCH_SB_FIELD_crypt; + v = bch2_opt_defined_by_id(&fs_opts, opt_id) + ? bch2_opt_get_by_id(&fs_opts, opt_id) + : bch2_opt_get_by_id(&bch2_opts_default, opt_id); - bch_sb_crypt_init(sb, crypt, opts.passphrase); - SET_BCH_SB_ENCRYPTION_TYPE(sb, 1); + __bch2_opt_set_sb(sb.sb, &bch2_opt_table[opt_id], v); } - mi = vstruct_end(sb); - u64s = (sizeof(struct bch_sb_field_members) + - sizeof(struct bch_member) * nr_devs) / sizeof(u64); + struct timespec now; + if (clock_gettime(CLOCK_REALTIME, &now)) + die("error getting current time: %m"); - le32_add_cpu(&sb->u64s, u64s); - mi->field.u64s = cpu_to_le32(u64s); - mi->field.type = BCH_SB_FIELD_members; + 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); + /* Member info: */ + struct bch_sb_field_members_v2 *mi = + bch2_sb_field_resize(&sb, members_v2, + (sizeof(*mi) + sizeof(struct bch_member) * + nr_devs) / sizeof(u64)); + mi->member_bytes = cpu_to_le16(sizeof(struct bch_member)); for (i = devs; i < devs + nr_devs; i++) { - struct bch_member *m = mi->members + (i - devs); + struct bch_member *m = bch2_members_v2_get_mut(sb.sb, (i - devs)); uuid_generate(m->uuid.b); m->nbuckets = cpu_to_le64(i->nbuckets); m->first_bucket = 0; - m->bucket_size = cpu_to_le16(i->bucket_size); + m->bucket_size = cpu_to_le16(i->bucket_size >> 9); - 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 labels*/ + for (i = devs; i < devs + nr_devs; i++) { + struct bch_member *m; + int idx; + + if (!i->label) + continue; + + idx = bch2_disk_path_find_or_create(&sb, i->label); + if (idx < 0) + die("error creating disk path: %s", strerror(-idx)); + + /* + * Recompute mi and m after each sb modification: its location + * in memory may have changed due to reallocation. + */ + m = bch2_members_v2_get_mut(sb.sb, (i - devs)); + SET_BCH_MEMBER_GROUP(m, idx + 1); + } + + SET_BCH_SB_FOREGROUND_TARGET(sb.sb, + parse_target(&sb, devs, nr_devs, fs_opt_strs.foreground_target)); + SET_BCH_SB_BACKGROUND_TARGET(sb.sb, + parse_target(&sb, devs, nr_devs, fs_opt_strs.background_target)); + SET_BCH_SB_PROMOTE_TARGET(sb.sb, + parse_target(&sb, devs, nr_devs, fs_opt_strs.promote_target)); + SET_BCH_SB_METADATA_TARGET(sb.sb, + parse_target(&sb, devs, nr_devs, fs_opt_strs.metadata_target)); + + /* Crypt: */ + if (opts.encrypted) { + struct bch_sb_field_crypt *crypt = + bch2_sb_field_resize(&sb, crypt, sizeof(*crypt) / sizeof(u64)); + + bch_sb_crypt_init(sb.sb, crypt, opts.passphrase); + SET_BCH_SB_ENCRYPTION_TYPE(sb.sb, 1); } + bch2_sb_members_cpy_v2_v1(&sb); + for (i = devs; i < devs + nr_devs; i++) { - sb->dev_idx = i - devs; + u64 size_sectors = i->size >> 9; + + sb.sb->dev_idx = i - devs; + + if (!i->sb_offset) { + i->sb_offset = BCH_SB_SECTOR; + i->sb_end = size_sectors; + } - init_layout(&sb->layout, opts.block_size, + init_layout(&sb.sb->layout, fs_opts.block_size, + opts.superblock_size, i->sb_offset, i->sb_end); + /* + * Also create a backup superblock at the end of the disk: + * + * If we're not creating a superblock at the default offset, it + * means we're being run from the migrate tool and we could be + * overwriting existing data if we write to the end of the disk: + */ + if (i->sb_offset == BCH_SB_SECTOR) { + struct bch_sb_layout *l = &sb.sb->layout; + u64 backup_sb = size_sectors - (1 << l->sb_max_size_bits); + + backup_sb = rounddown(backup_sb, i->bucket_size >> 9); + l->sb_offset[l->nr_superblocks++] = cpu_to_le64(backup_sb); + } + if (i->sb_offset == BCH_SB_SECTOR) { /* Zero start of disk */ static const char zeroes[BCH_SB_SECTOR << 9]; - xpwrite(i->fd, zeroes, BCH_SB_SECTOR << 9, 0); + xpwrite(i->bdev->bd_buffered_fd, zeroes, BCH_SB_SECTOR << 9, 0, + "zeroing start of disk"); } - bch2_super_write(i->fd, sb); - close(i->fd); + bch2_super_write(i->bdev->bd_buffered_fd, sb.sb); + close(i->bdev->bd_buffered_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) { /* Write backup layout */ xpwrite(fd, &sb->layout, sizeof(sb->layout), - BCH_SB_LAYOUT_SECTOR << 9); + BCH_SB_LAYOUT_SECTOR << 9, + "backup layout"); } sb->csum = csum_vstruct(NULL, BCH_SB_CSUM_TYPE(sb), nonce, sb); xpwrite(fd, sb, vstruct_bytes(sb), - le64_to_cpu(sb->offset) << 9); + le64_to_cpu(sb->offset) << 9, + "superblock"); } fsync(fd); @@ -270,7 +368,8 @@ struct bch_sb *__bch2_super_read(int fd, u64 sector) xpread(fd, &sb, sizeof(sb), sector << 9); - if (memcmp(&sb.magic, &BCACHE_MAGIC, sizeof(sb.magic))) + if (memcmp(&sb.magic, &BCACHE_MAGIC, sizeof(sb.magic)) && + memcmp(&sb.magic, &BCHFS_MAGIC, sizeof(sb.magic))) die("not a bcachefs superblock"); size_t bytes = vstruct_bytes(&sb); @@ -282,134 +381,361 @@ struct bch_sb *__bch2_super_read(int fd, u64 sector) return ret; } -struct bch_sb *bch2_super_read(const char *path) +/* ioctl interface: */ + +/* Global control device: */ +int bcachectl_open(void) +{ + return xopen("/dev/bcachefs-ctl", O_RDWR); +} + +/* 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 = open(path, O_RDONLY); + if (ret.ioctl_fd < 0) + die("Error opening filesystem at %s: %m", path); + + 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, int *idx) { - int fd = xopen(path, O_RDONLY); - struct bch_sb *sb = __bch2_super_read(fd, BCH_SB_SECTOR); - close(fd); - return sb; + 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); } -void bch2_super_print(struct bch_sb *sb, int units) +int bchu_dev_path_to_idx(struct bchfs_handle fs, const char *dev_path) +{ + int idx; + struct bchfs_handle fs2 = bchu_fs_open_by_dev(dev_path, &idx); + + if (memcmp(&fs.uuid, &fs2.uuid, sizeof(fs.uuid))) + idx = -1; + bcache_fs_close(fs2); + return idx; +} + +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_total + ? e.p.sectors_done * 100 / e.p.sectors_total + : 0, + 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_id_str(e.p.btree_id), + e.p.pos.inode, + e.p.pos.offset); + } + + fflush(stdout); + sleep(1); + } + printf("\nDone\n"); + + close(progress_fd); + return 0; +} + +/* option parsing */ + +void bch2_opt_strs_free(struct bch_opt_strs *opts) { - struct bch_sb_field_members *mi; - char user_uuid_str[40], internal_uuid_str[40], member_uuid_str[40]; - char label[BCH_SB_LABEL_SIZE + 1]; unsigned i; - 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); - - printf("External UUID: %s\n" - "Internal UUID: %s\n" - "Label: %s\n" - "Version: %llu\n" - "Block_size: %s\n" - "Btree node 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 checksum type: %s\n" - "Data checksum type: %s\n" - "Compression type: %s\n" - - "String hash type: %s\n" - "32 bit inodes: %llu\n" - "GC reserve percentage: %llu%%\n" - "Root reserve percentage: %llu%%\n" - - "Devices: %u\n", - user_uuid_str, - internal_uuid_str, - label, - le64_to_cpu(sb->version), - pr_units(le16_to_cpu(sb->block_size), units), - pr_units(BCH_SB_BTREE_NODE_SIZE(sb), units), - - BCH_SB_ERROR_ACTION(sb) < BCH_NR_ERROR_ACTIONS - ? bch2_error_actions[BCH_SB_ERROR_ACTION(sb)] - : "unknown", - - BCH_SB_CLEAN(sb), - - 0LLU, //BCH_SB_META_REPLICAS_HAVE(sb), - BCH_SB_META_REPLICAS_WANT(sb), - 0LLU, //BCH_SB_DATA_REPLICAS_HAVE(sb), - BCH_SB_DATA_REPLICAS_WANT(sb), - - BCH_SB_META_CSUM_TYPE(sb) < BCH_CSUM_NR - ? bch2_csum_types[BCH_SB_META_CSUM_TYPE(sb)] - : "unknown", - - BCH_SB_DATA_CSUM_TYPE(sb) < BCH_CSUM_NR - ? bch2_csum_types[BCH_SB_DATA_CSUM_TYPE(sb)] - : "unknown", - - BCH_SB_COMPRESSION_TYPE(sb) < BCH_COMPRESSION_NR - ? bch2_compression_types[BCH_SB_COMPRESSION_TYPE(sb)] - : "unknown", - - BCH_SB_STR_HASH_TYPE(sb) < BCH_STR_HASH_NR - ? bch2_str_hash_types[BCH_SB_STR_HASH_TYPE(sb)] - : "unknown", - - BCH_SB_INODE_32BIT(sb), - BCH_SB_GC_RESERVE(sb), - BCH_SB_ROOT_RESERVE(sb), - - sb->nr_devices); - - mi = bch2_sb_get_members(sb); - if (!mi) { - printf("Member info section missing\n"); - return; + for (i = 0; i < bch2_opts_nr; i++) { + free(opts->by_id[i]); + opts->by_id[i] = NULL; } +} + +struct bch_opt_strs bch2_cmdline_opts_get(int *argc, char *argv[], + unsigned opt_types) +{ + struct bch_opt_strs opts; + unsigned i = 1; + + memset(&opts, 0, sizeof(opts)); + + while (i < *argc) { + char *optstr = strcmp_prefix(argv[i], "--"); + char *valstr = NULL, *p; + int optid, nr_args = 1; + + if (!optstr) { + i++; + continue; + } + + optstr = strdup(optstr); + + p = optstr; + while (isalpha(*p) || *p == '_') + p++; + + if (*p == '=') { + *p = '\0'; + valstr = p + 1; + } - 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); - - uuid_unparse(m->uuid.b, member_uuid_str); - - 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)", - - BCH_MEMBER_STATE(m) < BCH_MEMBER_STATE_NR - ? bch2_dev_state[BCH_MEMBER_STATE(m)] - : "unknown", - - BCH_MEMBER_TIER(m), - 0LLU, //BCH_MEMBER_HAS_METADATA(m), - 0LLU, //BCH_MEMBER_HAS_DATA(m), - - BCH_MEMBER_REPLACEMENT(m) < CACHE_REPLACEMENT_NR - ? bch2_cache_replacement_policies[BCH_MEMBER_REPLACEMENT(m)] - : "unknown", - - BCH_MEMBER_DISCARD(m)); + optid = bch2_opt_lookup(optstr); + if (optid < 0 || + !(bch2_opt_table[optid].flags & opt_types)) { + i++; + goto next; + } + + if (!valstr && + bch2_opt_table[optid].type != BCH_OPT_BOOL) { + nr_args = 2; + valstr = argv[i + 1]; + } + + if (!valstr) + valstr = "1"; + + opts.by_id[optid] = strdup(valstr); + + *argc -= nr_args; + memmove(&argv[i], + &argv[i + nr_args], + sizeof(char *) * (*argc - i)); + argv[*argc] = NULL; +next: + free(optstr); + } + + return opts; +} + +struct bch_opts bch2_parse_opts(struct bch_opt_strs strs) +{ + struct bch_opts opts = bch2_opts_empty(); + struct printbuf err = PRINTBUF; + unsigned i; + int ret; + u64 v; + + for (i = 0; i < bch2_opts_nr; i++) { + if (!strs.by_id[i]) + continue; + + ret = bch2_opt_parse(NULL, + &bch2_opt_table[i], + strs.by_id[i], &v, &err); + if (ret < 0) + die("Invalid option %s", err.buf); + + bch2_opt_set_by_id(&opts, i, v); + } + + printbuf_exit(&err); + return opts; +} + +#define newline(c) \ + do { \ + printf("\n"); \ + c = 0; \ + } while(0) +void bch2_opts_usage(unsigned opt_types) +{ + const struct bch_option *opt; + unsigned i, c = 0, helpcol = 30; + + + + for (opt = bch2_opt_table; + opt < bch2_opt_table + bch2_opts_nr; + opt++) { + if (!(opt->flags & opt_types)) + continue; + + c += printf(" --%s", opt->attr.name); + + switch (opt->type) { + case BCH_OPT_BOOL: + break; + case BCH_OPT_STR: + c += printf("=("); + for (i = 0; opt->choices[i]; i++) { + if (i) + c += printf("|"); + c += printf("%s", opt->choices[i]); + } + c += printf(")"); + break; + default: + c += printf("=%s", opt->hint); + break; + } + + if (opt->help) { + const char *l = opt->help; + + if (c >= helpcol) + newline(c); + + while (1) { + const char *n = strchrnul(l, '\n'); + + while (c < helpcol) { + putchar(' '); + c++; + } + printf("%.*s", (int) (n - l), l); + newline(c); + + if (!*n) + break; + l = n + 1; + } + } else { + newline(c); + } + } +} + +dev_names bchu_fs_get_devices(struct bchfs_handle fs) +{ + DIR *dir = fdopendir(fs.sysfs_fd); + struct dirent *d; + dev_names devs; + + darray_init(&devs); + + while ((errno = 0), (d = readdir(dir))) { + struct dev_name n = { 0, NULL, NULL }; + + if (sscanf(d->d_name, "dev-%u", &n.idx) != 1) + continue; + + char *block_attr = mprintf("dev-%u/block", n.idx); + + char sysfs_block_buf[4096]; + ssize_t r = readlinkat(fs.sysfs_fd, block_attr, + sysfs_block_buf, sizeof(sysfs_block_buf)); + if (r > 0) { + sysfs_block_buf[r] = '\0'; + n.dev = strdup(basename(sysfs_block_buf)); + } + + free(block_attr); + + char *label_attr = mprintf("dev-%u/label", n.idx); + n.label = read_file_str(fs.sysfs_fd, label_attr); + free(label_attr); + + char *durability_attr = mprintf("dev-%u/durability", n.idx); + n.durability = read_file_u64(fs.sysfs_fd, durability_attr); + free(durability_attr); + + darray_push(&devs, n); } + + closedir(dir); + + return devs; }