summaryrefslogtreecommitdiff
path: root/c_src
diff options
context:
space:
mode:
authorKent Overstreet <kent.overstreet@linux.dev>2025-05-08 11:56:17 -0400
committerKent Overstreet <kent.overstreet@linux.dev>2025-05-08 12:26:19 -0400
commit723b9092ace2a9bb6d9751955cb7656386aed07e (patch)
treedc9abf3b2d34cc100600ad6c3b5a9155486712a5 /c_src
parent79ec3dd3e3cae13f67f2362644e55c2da676d72b (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.c183
-rw-r--r--c_src/libbcachefs.c40
-rw-r--r--c_src/libbcachefs.h2
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 *);