diff --git a/c_src/bcachefs.c b/c_src/bcachefs.c index f488436e..aed9c261 100644 --- a/c_src/bcachefs.c +++ b/c_src/bcachefs.c @@ -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" diff --git a/c_src/cmd_format.c b/c_src/cmd_format.c index 730a544e..b103d2bd 100644 --- a/c_src/cmd_format.c +++ b/c_src/cmd_format.c @@ -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; +} diff --git a/c_src/cmds.h b/c_src/cmds.h index 9de87a18..9502d539 100644 --- a/c_src/cmds.h +++ b/c_src/cmds.h @@ -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[]); diff --git a/c_src/libbcachefs.c b/c_src/libbcachefs.c index cdb5f7f6..c3af991b 100644 --- a/c_src/libbcachefs.c +++ b/c_src/libbcachefs.c @@ -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: diff --git a/c_src/libbcachefs.h b/c_src/libbcachefs.h index ff754c4f..026a3def 100644 --- a/c_src/libbcachefs.h +++ b/c_src/libbcachefs.h @@ -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 *); diff --git a/src/bcachefs.rs b/src/bcachefs.rs index 417cb01e..548add0a 100644 --- a/src/bcachefs.rs +++ b/src/bcachefs.rs @@ -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),