| /* |
| * 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; |
| } |
| |