diff options
author | Kent Overstreet <kent.overstreet@linux.dev> | 2025-05-08 11:56:17 -0400 |
---|---|---|
committer | Kent Overstreet <kent.overstreet@linux.dev> | 2025-05-08 12:26:19 -0400 |
commit | 723b9092ace2a9bb6d9751955cb7656386aed07e (patch) | |
tree | dc9abf3b2d34cc100600ad6c3b5a9155486712a5 /c_src | |
parent | 79ec3dd3e3cae13f67f2362644e55c2da676d72b (diff) |
cmd_recover_super: Add a mode for recovering from another member device
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
Diffstat (limited to 'c_src')
-rw-r--r-- | c_src/cmd_super.c | 183 | ||||
-rw-r--r-- | c_src/libbcachefs.c | 40 | ||||
-rw-r--r-- | c_src/libbcachefs.h | 2 |
3 files changed, 163 insertions, 62 deletions
diff --git a/c_src/cmd_super.c b/c_src/cmd_super.c index 7c0cdba0..869d87cf 100644 --- a/c_src/cmd_super.c +++ b/c_src/cmd_super.c @@ -136,6 +136,20 @@ int cmd_show_super(int argc, char *argv[]) typedef DARRAY(struct bch_sb *) probed_sb_list; +struct recover_super_args { + u64 dev_size; + u64 offset; + u64 scan_len; + + const char *src_device; + int dev_idx; + + bool yes; + bool verbose; + + const char *dev_path; +}; + static void probe_one_super(int dev_fd, unsigned sb_size, u64 offset, probed_sb_list *sbs, bool verbose) { @@ -225,6 +239,73 @@ static int bch2_sb_time_cmp(struct bch_sb *l, struct bch_sb *r) bch2_sb_last_mount_time(r)); } +static struct bch_sb *recover_super_from_scan(struct recover_super_args args, int dev_fd) +{ + probed_sb_list sbs = {}; + + if (args.offset) { + probe_one_super(dev_fd, SUPERBLOCK_SIZE_DEFAULT, args.offset, &sbs, args.verbose); + } else { + probe_sb_range(dev_fd, 4096, args.scan_len, &sbs, args.verbose); + probe_sb_range(dev_fd, args.dev_size - args.scan_len, args.dev_size, &sbs, args.verbose); + } + + if (!sbs.nr) { + printf("Found no bcachefs superblocks\n"); + exit(EXIT_FAILURE); + } + + struct bch_sb *best = NULL; + darray_for_each(sbs, sb) + if (!best || bch2_sb_time_cmp(best, *sb) < 0) + best = *sb; + + darray_for_each(sbs, sb) + if (*sb == best) + *sb = NULL; + + darray_for_each(sbs, sb) + kfree(*sb); + darray_exit(&sbs); + return best; +} + +static struct bch_sb *recover_super_from_member(struct recover_super_args args) +{ + struct bch_opts opts = bch2_opts_empty(); + opt_set(opts, noexcl, true); + opt_set(opts, nochanges, true); + + struct bch_sb_handle src_sb; + int ret = bch2_read_super(args.src_device, &opts, &src_sb); + if (ret) + die("Error opening %s: %s", args.src_device, bch2_err_str(ret)); + + if (!bch2_member_exists(src_sb.sb, args.dev_idx)) + die("Member %u does not exist in source superblock", args.dev_idx); + + bch2_sb_field_delete(&src_sb, BCH_SB_FIELD_journal); + bch2_sb_field_delete(&src_sb, BCH_SB_FIELD_journal_v2); + src_sb.sb->dev_idx = args.dev_idx; + + struct bch_sb *sb = src_sb.sb; + src_sb.sb = NULL; + + bch2_free_super(&src_sb); + + struct bch_member m = bch2_sb_member_get(sb, args.dev_idx); + + bch2_sb_layout_init(&sb->layout, + le16_to_cpu(sb->block_size) << 9, + le16_to_cpu(m.bucket_size) << 9, + 1U << sb->layout.sb_max_size_bits, + BCH_SB_SECTOR, + args.dev_size >> 9, + false); + + return sb; +} + static void recover_super_usage(void) { puts("bcachefs recover-super \n" @@ -237,6 +318,11 @@ static void recover_super_usage(void) "Options:\n" " -d, --dev_size size of filessytem on device, in bytes \n" " -o, --offset offset to probe, in bytes\n" + " -l, --scan_len Length in bytes to scan from start and end of device\n" + " Should be >= bucket size to find sb at end of device\n" + " Default 16M\n" + " -s, --src_device member device to recover from, in a multi device fs\n" + " -i, --dev_idx index of this device, if recovering from another device\n" " -y, --yes Recover without prompting\n" " -v, --verbose Increase logging level\n" " -h, --help display this help and exit\n" @@ -247,35 +333,52 @@ static void recover_super_usage(void) int cmd_recover_super(int argc, char *argv[]) { static const struct option longopts[] = { - { "dev_size", 1, NULL, 'd' }, - { "offset", 1, NULL, 'o' }, - { "yes", 0, NULL, 'y' }, - { "verbose", 0, NULL, 'v' }, - { "help", 0, NULL, 'h' }, + { "dev_size", required_argument, NULL, 'd' }, + { "offset", required_argument, NULL, 'o' }, + { "scan_len", required_argument, NULL, 'l' }, + { "src_device", required_argument, NULL, 's' }, + { "dev_idx", required_argument, NULL, 'i' }, + { "yes", no_argument, NULL, 'y' }, + { "verbose", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, { NULL } }; - u64 dev_size = 0, offset = 0; - bool yes = false, verbose = false; + struct recover_super_args args = { + .scan_len = 16 << 20, + .dev_idx = -1, + }; int opt; while ((opt = getopt_long(argc, argv, "d:o:yvh", longopts, NULL)) != -1) switch (opt) { case 'd': - if (bch2_strtoull_h(optarg, &dev_size)) - die("invalid offset"); + if (bch2_strtoull_h(optarg, &args.dev_size)) + die("invalid dev_size"); break; case 'o': - if (bch2_strtoull_h(optarg, &offset)) + if (bch2_strtoull_h(optarg, &args.offset)) die("invalid offset"); - if (offset & 511) + if (args.offset & 511) die("offset must be a multiple of 512"); break; + case 'l': + if (bch2_strtoull_h(optarg, &args.scan_len)) + die("invalid scan_len"); + break; + case 's': + args.src_device = strdup(optarg); + break; + case 'i': + if (kstrtoint(optarg, 10, &args.dev_idx) || + args.dev_idx < 0) + die("invalid dev_idx"); + break; case 'y': - yes = true; + args.yes = true; break; case 'v': - verbose = true; + args.verbose = true; break; case 'h': recover_super_usage(); @@ -283,6 +386,12 @@ int cmd_recover_super(int argc, char *argv[]) } args_shift(optind); + if (args.src_device && args.dev_idx == -1) + die("--src_device requires --dev_idx"); + + if (args.dev_idx >= 0 && !args.src_device) + die("--dev_idx requires --src_device"); + char *dev_path = arg_pop(); if (!dev_path) die("please supply a device"); @@ -291,43 +400,31 @@ int cmd_recover_super(int argc, char *argv[]) int dev_fd = xopen(dev_path, O_RDWR); - if (!dev_size) - dev_size = get_size(dev_fd); - - probed_sb_list sbs = {}; - - if (offset) { - probe_one_super(dev_fd, SUPERBLOCK_SIZE_DEFAULT, offset, &sbs, verbose); - } else { - unsigned scan_len = 16 << 20; /* 16MB, start and end of device */ + if (!args.dev_size) + args.dev_size = get_size(dev_fd); - probe_sb_range(dev_fd, 4096, scan_len, &sbs, verbose); - probe_sb_range(dev_fd, dev_size - scan_len, dev_size, &sbs, verbose); - } + struct bch_sb *sb = !args.src_device + ? recover_super_from_scan(args, dev_fd) + : recover_super_from_member(args); - if (!sbs.nr) { - printf("Found no bcachefs superblocks\n"); - exit(EXIT_FAILURE); - } + struct printbuf buf = PRINTBUF; + bch2_sb_to_text(&buf, sb, true, BIT_ULL(BCH_SB_FIELD_members_v2)); - struct bch_sb *best = NULL; - darray_for_each(sbs, sb) - if (!best || bch2_sb_time_cmp(best, *sb) < 0) - best = *sb; + printf("Found superblock:\n%s\n", buf.buf); - struct printbuf buf = PRINTBUF; - bch2_sb_to_text(&buf, best, true, BIT_ULL(BCH_SB_FIELD_members_v2)); + if (args.yes) + printf("Recovering\n"); + else + printf("Recover? "); - printf("Found superblock:\n%s", buf.buf); - printf("Recover?"); + if (args.yes || ask_yn()) + bch2_super_write(dev_fd, sb); - if (yes || ask_yn()) - bch2_super_write(dev_fd, best); + if (args.src_device) + printf("Recovered device will no longer have a journal, please run fsck\n"); printbuf_exit(&buf); - darray_for_each(sbs, sb) - kfree(*sb); - darray_exit(&sbs); - + kvfree(sb); + xclose(dev_fd); return 0; } diff --git a/c_src/libbcachefs.c b/c_src/libbcachefs.c index 6f68f4c5..5df37d1f 100644 --- a/c_src/libbcachefs.c +++ b/c_src/libbcachefs.c @@ -36,8 +36,10 @@ void bch2_sb_layout_init(struct bch_sb_layout *l, unsigned block_size, + unsigned bucket_size, unsigned sb_size, - u64 sb_start, u64 sb_end) + u64 sb_start, u64 sb_end, + bool no_sb_at_end) { u64 sb_pos = sb_start; unsigned i; @@ -61,6 +63,20 @@ void bch2_sb_layout_init(struct bch_sb_layout *l, 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); + + /* + * 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 (sb_start == BCH_SB_SECTOR && !no_sb_at_end) { + u64 backup_sb = sb_end - (1 << l->sb_max_size_bits); + + backup_sb = rounddown(backup_sb, bucket_size >> 9); + l->sb_offset[l->nr_superblocks++] = cpu_to_le64(backup_sb); + } } static u64 dev_max_bucket_size(u64 dev_size) @@ -304,24 +320,12 @@ struct bch_sb *bch2_format(struct bch_opt_strs fs_opt_strs, i->sb_end = size_sectors; } - bch2_sb_layout_init(&sb.sb->layout, fs_opts.block_size, + bch2_sb_layout_init(&sb.sb->layout, + fs_opts.block_size, + i->opts.bucket_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 && !opts.no_sb_at_end) { - 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->opts.bucket_size >> 9); - l->sb_offset[l->nr_superblocks++] = cpu_to_le64(backup_sb); - } + i->sb_offset, i->sb_end, + opts.no_sb_at_end); if (i->sb_offset == BCH_SB_SECTOR) { /* Zero start of disk */ diff --git a/c_src/libbcachefs.h b/c_src/libbcachefs.h index 6e9de138..38572bfd 100644 --- a/c_src/libbcachefs.h +++ b/c_src/libbcachefs.h @@ -92,7 +92,7 @@ static inline struct dev_opts dev_opts_default() } void bch2_sb_layout_init(struct bch_sb_layout *, - unsigned, unsigned, u64, u64); + unsigned, unsigned, unsigned, u64, u64, bool); u64 bch2_pick_bucket_size(struct bch_opts, dev_opts_list); void bch2_check_bucket_size(struct bch_opts, struct dev_opts *); |