/* * Copyright (C) 2013 FUJITSU LIMITED. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License v2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 021110-1307, USA. */ #include #include #include #include #include #include #include #include #include #include "kerncompat.h" #include "ctree.h" #include "disk-io.h" #include "list.h" #include "utils.h" #include "crc32c.h" #include "volumes.h" #include "commands.h" struct btrfs_recover_superblock { struct btrfs_fs_devices *fs_devices; struct list_head good_supers; struct list_head bad_supers; u64 max_generation; }; struct super_block_record { struct list_head list; char *device_name; struct btrfs_super_block sb; u64 bytenr; }; static void init_recover_superblock(struct btrfs_recover_superblock *recover) { INIT_LIST_HEAD(&recover->good_supers); INIT_LIST_HEAD(&recover->bad_supers); recover->fs_devices = NULL; recover->max_generation = 0; } static void free_recover_superblock(struct btrfs_recover_superblock *recover) { struct super_block_record *record; if (!recover->fs_devices) return; while (!list_empty(&recover->good_supers)) { record = list_entry(recover->good_supers.next, struct super_block_record, list); list_del_init(&record->list); free(record->device_name); free(record); } while (!list_empty(&recover->bad_supers)) { record = list_entry(recover->bad_supers.next, struct super_block_record, list); list_del_init(&record->list); free(record->device_name); free(record); } } static int check_super(u64 bytenr, struct btrfs_super_block *sb) { int csum_size = btrfs_super_csum_size(sb); char result[csum_size]; u32 crc = ~(u32)0; if (btrfs_super_bytenr(sb) != bytenr) return 0; if (sb->magic != cpu_to_le64(BTRFS_MAGIC)) return 0; crc = btrfs_csum_data(NULL, (char *)sb + BTRFS_CSUM_SIZE, crc, BTRFS_SUPER_INFO_SIZE - BTRFS_CSUM_SIZE); btrfs_csum_final(crc, result); return !memcmp(sb, &result, csum_size); } static int add_superblock_record(struct btrfs_super_block *sb, char *fname, u64 bytenr, struct list_head *head) { struct super_block_record *record; record = malloc(sizeof(struct super_block_record)); if (!record) return -ENOMEM; record->device_name = strdup(fname); if (!record->device_name) { free(record); return -ENOMEM; } memcpy(&record->sb, sb, sizeof(*sb)); record->bytenr = bytenr; list_add_tail(&record->list, head); return 0; } static int read_dev_supers(char *filename, struct btrfs_recover_superblock *recover) { int i, ret, fd; u8 buf[BTRFS_SUPER_INFO_SIZE]; u64 max_gen, bytenr; /* just ignore errno that were set in btrfs_scan_fs_devices() */ errno = 0; struct btrfs_super_block *sb = (struct btrfs_super_block *)buf; fd = open(filename, O_RDONLY, 0666); if (fd < 0) return -errno; for (i = 0; i < BTRFS_SUPER_MIRROR_MAX; i++) { bytenr = btrfs_sb_offset(i); ret = pread64(fd, buf, sizeof(buf), bytenr); if (ret < sizeof(buf)) { ret = -errno; goto out; } ret = check_super(bytenr, sb); if (ret) { ret = add_superblock_record(sb, filename, bytenr, &recover->good_supers); if (ret) goto out; max_gen = btrfs_super_generation(sb); if (max_gen > recover->max_generation) recover->max_generation = max_gen; } else { ret = add_superblock_record(sb, filename, bytenr, &recover->bad_supers); if (ret) goto out; } } out: close(fd); return ret; } static int read_fs_supers(struct btrfs_recover_superblock *recover) { struct super_block_record *record; struct super_block_record *next_record; struct btrfs_device *dev; int ret; u64 gen; list_for_each_entry(dev, &recover->fs_devices->devices, dev_list) { ret = read_dev_supers(dev->name, recover); if (ret) return ret; } list_for_each_entry_safe(record, next_record, &recover->good_supers, list) { gen = btrfs_super_generation(&record->sb); if (gen < recover->max_generation) list_move_tail(&record->list, &recover->bad_supers); } return 0; } static struct super_block_record *recover_get_good_super( struct btrfs_recover_superblock *recover) { struct super_block_record *record; record = list_entry(recover->good_supers.next, struct super_block_record, list); return record; } static void print_all_devices(struct list_head *devices) { struct btrfs_device *dev; printf("All Devices:\n"); list_for_each_entry(dev, devices, dev_list) { printf("\t"); printf("Device: id = %llu, name = %s\n", dev->devid, dev->name); } printf("\n"); } static void print_super_info(struct super_block_record *record) { printf("\t\tdevice name = %s\n", record->device_name); printf("\t\tsuperblock bytenr = %llu\n", record->bytenr); } static void print_all_supers(struct btrfs_recover_superblock *recover) { struct super_block_record *record; printf("\t[All good supers]:\n"); list_for_each_entry(record, &recover->good_supers, list) { print_super_info(record); printf("\n"); } printf("\t[All bad supers]:\n"); list_for_each_entry(record, &recover->bad_supers, list) { print_super_info(record); printf("\n"); } printf("\n"); } static void recover_err_str(int ret) { switch (ret) { case 0: printf("All supers are valid, no need to recover\n"); break; case 1: printf("Usage or syntax errors\n"); break; case 2: printf("Recovered bad superblocks successful\n"); break; case 3: printf("Failed to recover bad superblocks\n"); break; case 4: printf("Aborted to recover bad superblocks\n"); break; default: printf("Unknown recover result\n"); break; } } int btrfs_recover_superblocks(const char *dname, int verbose, int yes) { int fd, ret; struct btrfs_recover_superblock recover; struct super_block_record *record; struct btrfs_root *root = NULL; fd = open(dname, O_RDONLY); if (fd < 0) { fprintf(stderr, "open %s error\n", dname); return 1; } init_recover_superblock(&recover); ret = btrfs_scan_fs_devices(fd, dname, &recover.fs_devices, 0, 1, 0); close(fd); if (ret) { ret = 1; goto no_recover; } if (verbose) print_all_devices(&recover.fs_devices->devices); ret = read_fs_supers(&recover); if (ret) { ret = 1; goto no_recover; } if (verbose) { printf("Before Recovering:\n"); print_all_supers(&recover); } if (list_empty(&recover.bad_supers)) goto no_recover; if (!yes) { ret = ask_user("Make sure this is a btrfs disk otherwise the tool will destroy other fs, Are you sure?"); if (!ret) { ret = 4; goto no_recover; } } record = recover_get_good_super(&recover); root = open_ctree(record->device_name, record->bytenr, OPEN_CTREE_RECOVER_SUPER | OPEN_CTREE_WRITES); if (!root) { ret = 3; goto no_recover; } /* reset super_bytenr in order that we will rewite all supers */ root->fs_info->super_bytenr = BTRFS_SUPER_INFO_OFFSET; ret = write_all_supers(root); if (!ret) ret = 2; else ret = 3; close_ctree(root); no_recover: recover_err_str(ret); free_recover_superblock(&recover); /* check if we have freed fs_deivces in close_ctree() */ if (!root) btrfs_close_devices(recover.fs_devices); return ret; }