/* * Authors: Kent Overstreet * Gabriel de Perthuis * Jacob Malevich * * GPLv2 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cmds.h" #include "libbcachefs.h" #include "libbcachefs/opts.h" #include "libbcachefs/super-io.h" #include "libbcachefs/util.h" #include "libbcachefs/darray.h" static void show_super_usage(void) { puts("bcachefs show-super \n" "Usage: bcachefs show-super [OPTION].. device\n" "\n" "Options:\n" " -f, --fields=(fields) list of sections to print\n" " --field-only=fiel) print superblock section only, no header\n" " -l, --layout print superblock layout\n" " -h, --help display this help and exit\n" "Report bugs to "); exit(EXIT_SUCCESS); } int cmd_show_super(int argc, char *argv[]) { static const struct option longopts[] = { { "fields", 1, NULL, 'f' }, { "field-only", 1, NULL, 'F' }, { "layout", 0, NULL, 'l' }, { "help", 0, NULL, 'h' }, { NULL } }; unsigned fields = 0; int field_only = -1; bool print_layout = false; bool print_default_fields = true; int opt; while ((opt = getopt_long(argc, argv, "f:lh", longopts, NULL)) != -1) switch (opt) { case 'f': fields = !strcmp(optarg, "all") ? ~0 : read_flag_list_or_die(optarg, bch2_sb_fields, "superblock field"); print_default_fields = false; break; case 'F': field_only = read_string_list_or_die(optarg, bch2_sb_fields, "superblock field"); print_default_fields = false; break; case 'l': print_layout = true; break; case 'h': show_super_usage(); break; } args_shift(optind); char *dev = arg_pop(); if (!dev) die("please supply a device"); if (argc) die("too many arguments"); 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(dev, &opts, &sb); if (ret) die("Error opening %s: %s", dev, bch2_err_str(ret)); if (print_default_fields) { fields |= bch2_sb_field_get(sb.sb, members_v2) ? 1 << BCH_SB_FIELD_members_v2 : 1 << BCH_SB_FIELD_members_v1; fields |= 1 << BCH_SB_FIELD_errors; } struct printbuf buf = PRINTBUF; buf.human_readable_units = true; if (field_only >= 0) { struct bch_sb_field *f = bch2_sb_field_get_id(sb.sb, field_only); if (f) __bch2_sb_field_to_text(&buf, sb.sb, f); } else { printbuf_tabstop_push(&buf, 44); char *model = fd_to_dev_model(sb.bdev->bd_fd); prt_str(&buf, "Device:"); prt_tab(&buf); prt_str(&buf, model); prt_newline(&buf); free(model); bch2_sb_to_text(&buf, sb.sb, print_layout, fields); } printf("%s", buf.buf); bch2_free_super(&sb); printbuf_exit(&buf); return 0; } #include "libbcachefs/super-io.h" #include "libbcachefs/sb-members.h" 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) { 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 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, BCH_MEMBER_BUCKET_SIZE(&m) << 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" "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" " -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" "Report bugs to "); exit(EXIT_SUCCESS); } int cmd_recover_super(int argc, char *argv[]) { static const struct option longopts[] = { { "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 } }; 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, &args.dev_size)) die("invalid dev_size"); break; case 'o': if (bch2_strtoull_h(optarg, &args.offset)) die("invalid offset"); 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': args.yes = true; break; case 'v': args.verbose = true; break; case 'h': recover_super_usage(); break; } 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"); if (argc) die("too many arguments"); int dev_fd = xopen(dev_path, O_RDWR); if (!args.dev_size) args.dev_size = get_size(dev_fd); struct bch_sb *sb = !args.src_device ? recover_super_from_scan(args, dev_fd) : recover_super_from_member(args); struct printbuf buf = PRINTBUF; bch2_sb_to_text(&buf, sb, true, BIT_ULL(BCH_SB_FIELD_members_v2)); printf("Found superblock:\n%s\n", buf.buf); if (args.yes) printf("Recovering\n"); else printf("Recover? "); if (args.yes || ask_yn()) bch2_super_write(dev_fd, sb); if (args.src_device) printf("Recovered device will no longer have a journal, please run fsck\n"); printbuf_exit(&buf); kvfree(sb); xclose(dev_fd); return 0; }