cmd_recover_super: 'bcachefs recover-super'

New tool for scanning for backup superblocks, to recover when the
primary superblock + sb layout have been overwritten.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2025-03-17 10:12:23 -04:00
parent c0836924b1
commit 64ce740ac6
6 changed files with 214 additions and 7 deletions

View File

@ -33,6 +33,7 @@ void bcachefs_usage(void)
"Superblock commands:\n"
" format Format a new filesystem\n"
" show-super Dump superblock information to stdout\n"
" recover-super Attempt to recover overwritten superblock from backups\n"
" set-fs-option Set a filesystem option\n"
" reset-counters Reset all counters on an unmounted device\n"
"\n"

View File

@ -433,3 +433,204 @@ int cmd_show_super(int argc, char *argv[])
printbuf_exit(&buf);
return 0;
}
#include "libbcachefs/super-io.h"
#include "libbcachefs/sb-members.h"
typedef DARRAY(struct bch_sb *) probed_sb_list;
static void probe_one_super(int dev_fd, unsigned sb_size, u64 offset,
probed_sb_list *sbs, bool verbose)
{
darray_char sb_buf = {};
darray_resize(&sb_buf, sb_size);
xpread(dev_fd, sb_buf.data, sb_buf.size, offset);
struct printbuf err = PRINTBUF;
int ret = bch2_sb_validate((void *) sb_buf.data, offset >> 9, 0, &err);
printbuf_exit(&err);
if (!ret) {
if (verbose) {
struct printbuf buf = PRINTBUF;
prt_human_readable_u64(&buf, offset);
printf("found superblock at %s\n", buf.buf);
printbuf_exit(&buf);
}
darray_push(sbs, (void *) sb_buf.data);
sb_buf.data = NULL;
}
darray_exit(&sb_buf);
}
static void probe_sb_range(int dev_fd, u64 start_offset, u64 end_offset,
probed_sb_list *sbs, bool verbose)
{
start_offset &= ~((u64) 511);
end_offset &= ~((u64) 511);
size_t buflen = end_offset - start_offset;
void *buf = malloc(buflen);
xpread(dev_fd, buf, buflen, start_offset);
for (u64 offset = 0; offset < buflen; offset += 512) {
struct bch_sb *sb = buf + offset;
if (!uuid_equal(&sb->magic, &BCACHE_MAGIC) &&
!uuid_equal(&sb->magic, &BCHFS_MAGIC))
continue;
size_t bytes = vstruct_bytes(sb);
if (offset + bytes > buflen) {
fprintf(stderr, "found sb %llu size %zu that overran buffer\n",
start_offset + offset, bytes);
continue;
}
struct printbuf err = PRINTBUF;
int ret = bch2_sb_validate(sb, (start_offset + offset) >> 9, 0, &err);
if (ret)
fprintf(stderr, "found sb %llu that failed to validate: %s\n",
start_offset + offset, err.buf);
printbuf_exit(&err);
if (ret)
continue;
if (verbose) {
struct printbuf buf = PRINTBUF;
prt_human_readable_u64(&buf, start_offset + offset);
printf("found superblock at %s\n", buf.buf);
printbuf_exit(&buf);
}
void *sb_copy = malloc(bytes);
memcpy(sb_copy, sb, bytes);
darray_push(sbs, sb_copy);
}
free(buf);
}
static u64 bch2_sb_last_mount_time(struct bch_sb *sb)
{
u64 ret = 0;
for (unsigned i = 0; i < sb->nr_devices; i++)
ret = max(ret, le64_to_cpu(bch2_sb_member_get(sb, i).last_mount));
return ret;
}
static int bch2_sb_time_cmp(struct bch_sb *l, struct bch_sb *r)
{
return cmp_int(bch2_sb_last_mount_time(l),
bch2_sb_last_mount_time(r));
}
static void recover_super_usage(void)
{
puts("bcachefs recover-super \n"
"Usage: bcachefs recover-super [OPTION].. device\n"
"\n"
"Attempt to recover a filesystem on a device that has had the main superblock\n"
"and superblock layout overwritten.\n"
"All options will be guessed if not provided\n"
"\n"
"Options:\n"
" -d, --dev_size size of filessytem on device, in bytes \n"
" -o, --offset offset to probe, in bytes\n"
" -y, --yes Recover without prompting\n"
" -v, --verbose Increase logging level\n"
" -h, --help display this help and exit\n"
"Report bugs to <linux-bcachefs@vger.kernel.org>");
exit(EXIT_SUCCESS);
}
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' },
{ NULL }
};
u64 dev_size = 0, offset = 0;
bool yes = false, verbose = false;
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");
break;
case 'o':
if (bch2_strtoull_h(optarg, &offset))
die("invalid offset");
if (offset & 511)
die("offset must be a multiple of 512");
break;
case 'y':
yes = true;
break;
case 'v':
verbose = true;
break;
case 'h':
recover_super_usage();
break;
}
args_shift(optind);
char *dev_path = arg_pop();
if (!dev_path)
die("please supply a device");
if (argc)
die("too many arguments");
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 */
probe_sb_range(dev_fd, 4096, scan_len, &sbs, verbose);
probe_sb_range(dev_fd, dev_size - scan_len, dev_size, &sbs, 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;
struct printbuf buf = PRINTBUF;
bch2_sb_to_text(&buf, best, true, BIT_ULL(BCH_SB_FIELD_members_v2));
printf("Found superblock:\n%s", buf.buf);
printf("Recover?");
if (yes || ask_yn())
bch2_super_write(dev_fd, best);
printbuf_exit(&buf);
darray_for_each(sbs, sb)
kfree(*sb);
darray_exit(&sbs);
return 0;
}

View File

@ -11,6 +11,7 @@
int cmd_format(int argc, char *argv[]);
int cmd_show_super(int argc, char *argv[]);
int cmd_recover_super(int argc, char *argv[]);
int cmd_reset_counters(int argc, char *argv[]);
int cmd_set_option(int argc, char *argv[]);

View File

@ -31,10 +31,10 @@
#define NSEC_PER_SEC 1000000000L
static void init_layout(struct bch_sb_layout *l,
unsigned block_size,
unsigned sb_size,
u64 sb_start, u64 sb_end)
void bch2_sb_layout_init(struct bch_sb_layout *l,
unsigned block_size,
unsigned sb_size,
u64 sb_start, u64 sb_end)
{
u64 sb_pos = sb_start;
unsigned i;
@ -307,9 +307,9 @@ struct bch_sb *bch2_format(struct bch_opt_strs fs_opt_strs,
i->sb_end = size_sectors;
}
init_layout(&sb.sb->layout, fs_opts.block_size,
opts.superblock_size,
i->sb_offset, i->sb_end);
bch2_sb_layout_init(&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:

View File

@ -79,6 +79,9 @@ static inline struct dev_opts dev_opts_default()
};
}
void bch2_sb_layout_init(struct bch_sb_layout *,
unsigned, unsigned, u64, u64);
u64 bch2_pick_bucket_size(struct bch_opts, struct dev_opts *);
void bch2_check_bucket_size(struct bch_opts, struct dev_opts *);

View File

@ -60,6 +60,7 @@ fn handle_c_command(mut argv: Vec<String>, symlink_cmd: Option<&str>) -> i32 {
"set-passphrase" => c::cmd_set_passphrase(argc, argv),
"set-file-option" => c::cmd_setattr(argc, argv),
"show-super" => c::cmd_show_super(argc, argv),
"recover-super" => c::cmd_recover_super(argc, argv),
"unlock" => c::cmd_unlock(argc, argv),
"version" => c::cmd_version(argc, argv),