diff options
Diffstat (limited to 'super-recover.c')
-rw-r--r-- | super-recover.c | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/super-recover.c b/super-recover.c new file mode 100644 index 00000000..e2c31294 --- /dev/null +++ b/super-recover.c @@ -0,0 +1,336 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <uuid/uuid.h> +#include <errno.h> +#include <unistd.h> +#include <ctype.h> +#include <getopt.h> + +#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; +} + |