From be826706b5c0fcca3bdeff6934cd8d46d046ddfe Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Fri, 26 Aug 2011 09:51:36 -0400 Subject: [PATCH] btrfs-progs: add a recovery utility to pull files from damanged filesystems Signed-off-by: Josef Bacik Signed-off-by: Chris Mason --- .gitignore | 15 ++ Makefile | 16 +- calc-size.c | 268 ++++++++++++++++++ ctree.c | 10 +- ctree.h | 16 ++ disk-io.c | 222 +++++++++++---- disk-io.h | 2 + extent-tree.c | 13 + extent_io.c | 1 - find-root.c | 458 +++++++++++++++++++++++++++++++ restore.c | 854 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ volumes.c | 57 +++- volumes.h | 5 + 13 files changed, 1867 insertions(+), 70 deletions(-) create mode 100644 .gitignore create mode 100644 calc-size.c create mode 100644 find-root.c create mode 100644 restore.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e560d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.o +.*.o.d +version.h +man/*.gz +btrfs +btrfs-debug-tree +btrfs-map-logical +btrfs-show +btrfs-vol +btrfsck +btrfsctl +find-root +mkfs.btrfs +repair +restore diff --git a/Makefile b/Makefile index edee1a0..e85c726 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CC = gcc AM_CFLAGS = -Wall -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2 -CFLAGS = -g -Werror -Os +CFLAGS = -g -Os objects = ctree.o disk-io.o radix-tree.o extent-tree.o print-tree.o \ root-tree.o dir-item.o file-item.o inode-item.o \ inode-map.o crc32c.o rbtree.o extent-cache.o extent_io.o \ @@ -13,10 +13,11 @@ DEPFLAGS = -Wp,-MMD,$(@D)/.$(@F).d,-MT,$@ INSTALL = install prefix ?= /usr/local bindir = $(prefix)/bin -LIBS = -luuid +LIBS=-luuid +RESTORE_LIBS=-lz progs = btrfsctl mkfs.btrfs btrfs-debug-tree btrfs-show btrfs-vol btrfsck \ - btrfs btrfs-map-logical + btrfs btrfs-map-logical restore find-root calc-size # make C=1 to enable sparse ifdef C @@ -39,6 +40,15 @@ btrfs: $(objects) btrfs.o btrfs_cmds.o scrub.o $(CC) -lpthread $(CFLAGS) -o btrfs btrfs.o btrfs_cmds.o scrub.o \ $(objects) $(LDFLAGS) $(LIBS) +calc-size: $(objects) calc-size.o + gcc $(CFLAGS) -o calc-size calc-size.o $(objects) $(LDFLAGS) $(LIBS) + +find-root: $(objects) find-root.o + gcc $(CFLAGS) -o find-root find-root.o $(objects) $(LDFLAGS) $(LIBS) + +restore: $(objects) restore.o + gcc $(CFLAGS) -o restore restore.o $(objects) $(LDFLAGS) $(LIBS) $(RESTORE_LIBS) + btrfsctl: $(objects) btrfsctl.o $(CC) $(CFLAGS) -o btrfsctl btrfsctl.o $(objects) $(LDFLAGS) $(LIBS) diff --git a/calc-size.c b/calc-size.c new file mode 100644 index 0000000..c4adfb0 --- /dev/null +++ b/calc-size.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2011 Red Hat. 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. + */ + +#define _XOPEN_SOURCE 500 +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include "kerncompat.h" +#include "ctree.h" +#include "disk-io.h" +#include "print-tree.h" +#include "transaction.h" +#include "list.h" +#include "version.h" +#include "volumes.h" +#include "utils.h" + +static int verbose = 0; +static int no_pretty = 0; + +struct root_stats { + u64 total_nodes; + u64 total_leaves; + u64 total_bytes; + u64 total_inline; + int total_levels; +}; + +struct fs_root { + struct btrfs_key key; + struct btrfs_key *snaps; +}; + +static int walk_leaf(struct btrfs_root *root, struct btrfs_path *path, + struct root_stats *stat, int find_inline) +{ + struct extent_buffer *b = path->nodes[0]; + struct btrfs_file_extent_item *fi; + struct btrfs_key found_key; + int i; + + stat->total_bytes += root->leafsize; + stat->total_leaves++; + + if (!find_inline) + return 0; + + for (i = 0; i < btrfs_header_nritems(b); i++) { + btrfs_item_key_to_cpu(b, &found_key, i); + if (found_key.type != BTRFS_EXTENT_DATA_KEY) + continue; + + fi = btrfs_item_ptr(b, i, struct btrfs_file_extent_item); + if (btrfs_file_extent_type(b, fi) == BTRFS_FILE_EXTENT_INLINE) + stat->total_inline += + btrfs_file_extent_inline_item_len(b, + btrfs_item_nr(b, i)); + } + + return 0; +} + +static int walk_nodes(struct btrfs_root *root, struct btrfs_path *path, + struct root_stats *stat, int level, int find_inline) +{ + struct extent_buffer *b = path->nodes[level]; + int i; + int ret = 0; + + stat->total_bytes += root->nodesize; + stat->total_nodes++; + + for (i = 0; i < btrfs_header_nritems(b); i++) { + struct extent_buffer *tmp = NULL; + + path->slots[level] = i; + if ((level - 1) > 0 || find_inline) { + tmp = read_tree_block(root, btrfs_node_blockptr(b, i), + btrfs_level_size(root, level - 1), + btrfs_node_ptr_generation(b, i)); + if (!tmp) { + fprintf(stderr, "Failed to read blocknr %Lu\n", + btrfs_node_blockptr(b, i)); + continue; + } + path->nodes[level - 1] = tmp; + } + if (level - 1) + ret = walk_nodes(root, path, stat, level - 1, + find_inline); + else + ret = walk_leaf(root, path, stat, find_inline); + free_extent_buffer(tmp); + if (ret) { + fprintf(stderr, "Error walking down path\n"); + break; + } + } + + return ret; +} + +static int calc_root_size(struct btrfs_root *tree_root, struct btrfs_key *key, + int find_inline) +{ + struct btrfs_root *root; + struct btrfs_path *path; + struct root_stats stat; + int level; + int ret = 0; + int size_fail = 0; + + root = btrfs_read_fs_root(tree_root->fs_info, key); + if (!root) { + fprintf(stderr, "Failed to read root %Lu\n", key->objectid); + return 1; + } + + path = btrfs_alloc_path(); + if (!path) { + fprintf(stderr, "Could not allocate path\n"); + return 1; + } + + memset(&stat, 0, sizeof(stat)); + level = btrfs_header_level(root->node); + path->nodes[level] = root->node; + if (!level) { + ret = walk_leaf(root, path, &stat, find_inline); + if (ret) + goto out; + goto out_print; + } + + ret = walk_nodes(root, path, &stat, level, find_inline); + if (ret) + goto out; +out_print: + if (no_pretty || size_fail) { + printf("\t%Lu total bytes, %Lu inline data bytes, %Lu nodes, " + "%Lu leaves, %d levels\n", stat.total_bytes, + stat.total_inline, stat.total_nodes, stat.total_leaves, + level + 1); + } else { + char *total_size; + char *inline_size; + + total_size = pretty_sizes(stat.total_bytes); + inline_size = pretty_sizes(stat.total_inline); + + printf("\t%s total size, %s inline data, %Lu nodes, " + "%Lu leaves, %d levels\n", + total_size, inline_size, stat.total_nodes, + stat.total_leaves, level + 1); + free(total_size); + free(inline_size); + } +out: + btrfs_free_path(path); + return ret; +} + +static void usage() +{ + fprintf(stderr, "Usage: calc-size [-v] [-b] \n"); +} + +int main(int argc, char **argv) +{ + struct btrfs_key key; + struct fs_root *roots; + struct btrfs_root *root; + size_t fs_roots_size = sizeof(struct fs_root); + int opt; + int ret = 0; + + while ((opt = getopt(argc, argv, "vb")) != -1) { + switch (opt) { + case 'v': + verbose++; + break; + case 'b': + no_pretty = 1; + break; + default: + usage(); + exit(1); + } + } + + if (optind >= argc) { + usage(); + exit(1); + } + + /* + if ((ret = check_mounted(argv[optind])) < 0) { + fprintf(stderr, "Could not check mount status: %d\n", ret); + if (ret == -EACCES) + fprintf(stderr, "Maybe you need to run as root?\n"); + return ret; + } else if (ret) { + fprintf(stderr, "%s is currently mounted. Aborting.\n", + argv[optind]); + return -EBUSY; + } + */ + + root = open_ctree(argv[optind], 0, 0); + if (!root) { + fprintf(stderr, "Couldn't open ctree\n"); + exit(1); + } + + roots = malloc(fs_roots_size); + if (!roots) { + fprintf(stderr, "No memory\n"); + goto out; + } + + printf("Calculating size of root tree\n"); + key.objectid = BTRFS_ROOT_TREE_OBJECTID; + ret = calc_root_size(root, &key, 0); + if (ret) + goto out; + + printf("Calculating size of extent tree\n"); + key.objectid = BTRFS_EXTENT_TREE_OBJECTID; + ret = calc_root_size(root, &key, 0); + if (ret) + goto out; + + printf("Calculating size of csum tree\n"); + key.objectid = BTRFS_CSUM_TREE_OBJECTID; + ret = calc_root_size(root, &key, 0); + if (ret) + goto out; + + roots[0].key.objectid = BTRFS_FS_TREE_OBJECTID; + roots[0].key.offset = (u64)-1; + printf("Calculatin' size of fs tree\n"); + ret = calc_root_size(root, &roots[0].key, 1); + if (ret) + goto out; +out: + close_ctree(root); + return ret; +} diff --git a/ctree.c b/ctree.c index 12f1a55..005550f 100644 --- a/ctree.c +++ b/ctree.c @@ -765,7 +765,7 @@ static int bin_search(struct extent_buffer *eb, struct btrfs_key *key, return -1; } -static struct extent_buffer *read_node_slot(struct btrfs_root *root, +struct extent_buffer *read_node_slot(struct btrfs_root *root, struct extent_buffer *parent, int slot) { int level = btrfs_header_level(parent); @@ -1092,7 +1092,7 @@ static int noinline push_nodes_for_insert(struct btrfs_trans_handle *trans, /* * readahead one full node of leaves */ -static void reada_for_search(struct btrfs_root *root, struct btrfs_path *path, +void reada_for_search(struct btrfs_root *root, struct btrfs_path *path, int level, int slot, u64 objectid) { struct extent_buffer *node; @@ -1189,7 +1189,7 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root u8 lowest_level = 0; lowest_level = p->lowest_level; - WARN_ON(lowest_level && ins_len); + WARN_ON(lowest_level && ins_len > 0); WARN_ON(p->nodes[0] != NULL); /* WARN_ON(!mutex_is_locked(&root->fs_info->fs_mutex)); @@ -2915,6 +2915,8 @@ int btrfs_next_leaf(struct btrfs_root *root, struct btrfs_path *path) reada_for_search(root, path, level, slot, 0); next = read_node_slot(root, c, slot); + if (!next) + return -EIO; break; } path->slots[level] = slot; @@ -2929,6 +2931,8 @@ int btrfs_next_leaf(struct btrfs_root *root, struct btrfs_path *path) if (path->reada) reada_for_search(root, path, level, 0, 0); next = read_node_slot(root, next, 0); + if (!next) + return -EIO; } return 0; } diff --git a/ctree.h b/ctree.h index 6e1b80b..b1a87c8 100644 --- a/ctree.h +++ b/ctree.h @@ -79,6 +79,15 @@ struct btrfs_trans_handle; */ #define BTRFS_EXTENT_CSUM_OBJECTID -10ULL +/* For storing free space cache */ +#define BTRFS_FREE_SPACE_OBJECTID -11ULL + +/* + * The inode number assigned to the special inode for sotring + * free ino cache + */ +#define BTRFS_FREE_INO_OBJECTID -12ULL + /* dummy objectid represents multiple objectids */ #define BTRFS_MULTIPLE_OBJECTIDS -255ULL @@ -290,6 +299,9 @@ struct btrfs_header { #define BTRFS_MAX_INLINE_DATA_SIZE(r) (BTRFS_LEAF_DATA_SIZE(r) - \ sizeof(struct btrfs_item) - \ sizeof(struct btrfs_file_extent_item)) +#define BTRFS_MAX_XATTR_SIZE(r) (BTRFS_LEAF_DATA_SIZE(r) - \ + sizeof(struct btrfs_item) -\ + sizeof(struct btrfs_dir_item)) /* @@ -1735,6 +1747,10 @@ int btrfs_update_block_group(struct btrfs_trans_handle *trans, struct btrfs_root *root, u64 bytenr, u64 num, int alloc, int mark_free); /* ctree.c */ +void reada_for_search(struct btrfs_root *root, struct btrfs_path *path, + int level, int slot, u64 objectid); +struct extent_buffer *read_node_slot(struct btrfs_root *root, + struct extent_buffer *parent, int slot); int btrfs_previous_item(struct btrfs_root *root, struct btrfs_path *path, u64 min_objectid, int type); diff --git a/disk-io.c b/disk-io.c index aed624c..8429e53 100644 --- a/disk-io.c +++ b/disk-io.c @@ -35,14 +35,19 @@ #include "utils.h" #include "print-tree.h" +static int close_all_devices(struct btrfs_fs_info *fs_info); + static int check_tree_block(struct btrfs_root *root, struct extent_buffer *buf) { struct btrfs_fs_devices *fs_devices; int ret = 1; - if (buf->start != btrfs_header_bytenr(buf)) + if (buf->start != btrfs_header_bytenr(buf)) { + printk("Check tree block failed, want=%Lu, have=%Lu\n", + buf->start, btrfs_header_bytenr(buf)); return ret; + } fs_devices = root->fs_info->fs_devices; while (fs_devices) { @@ -147,7 +152,8 @@ int readahead_tree_block(struct btrfs_root *root, u64 bytenr, u32 blocksize, } static int verify_parent_transid(struct extent_io_tree *io_tree, - struct extent_buffer *eb, u64 parent_transid) + struct extent_buffer *eb, u64 parent_transid, + int ignore) { int ret; @@ -163,6 +169,11 @@ static int verify_parent_transid(struct extent_io_tree *io_tree, (unsigned long long)eb->start, (unsigned long long)parent_transid, (unsigned long long)btrfs_header_generation(eb)); + if (ignore) { + printk("Ignoring transid failure\n"); + return 0; + } + ret = 1; out: clear_extent_buffer_uptodate(io_tree, eb); @@ -177,10 +188,13 @@ struct extent_buffer *read_tree_block(struct btrfs_root *root, u64 bytenr, int ret; struct extent_buffer *eb; u64 length; + u64 best_transid = 0; struct btrfs_multi_bio *multi = NULL; struct btrfs_device *device; int mirror_num = 0; + int good_mirror = 0; int num_copies; + int ignore = 0; eb = btrfs_find_create_tree_block(root, bytenr, blocksize); if (!eb) @@ -193,7 +207,10 @@ struct extent_buffer *read_tree_block(struct btrfs_root *root, u64 bytenr, while (1) { ret = btrfs_map_block(&root->fs_info->mapping_tree, READ, eb->start, &length, &multi, mirror_num); - BUG_ON(ret); + if (ret) { + printk("Couldn't map the block %Lu\n", bytenr); + break; + } device = multi->stripes[0].dev; eb->fd = device->fd; device->total_ios++; @@ -203,18 +220,33 @@ struct extent_buffer *read_tree_block(struct btrfs_root *root, u64 bytenr, if (ret == 0 && check_tree_block(root, eb) == 0 && csum_tree_block(root, eb, 1) == 0 && - verify_parent_transid(eb->tree, eb, parent_transid) == 0) { + verify_parent_transid(eb->tree, eb, parent_transid, ignore) + == 0) { btrfs_set_buffer_uptodate(eb); return eb; } + if (ignore) { + if (check_tree_block(root, eb)) + printk("read block failed check_tree_block\n"); + else + printk("Csum didn't match\n"); + break; + } num_copies = btrfs_num_copies(&root->fs_info->mapping_tree, eb->start, eb->len); if (num_copies == 1) { - break; + ignore = 1; + continue; + } + if (btrfs_header_generation(eb) > best_transid) { + best_transid = btrfs_header_generation(eb); + good_mirror = mirror_num; } mirror_num++; if (mirror_num > num_copies) { - break; + mirror_num = good_mirror; + ignore = 1; + continue; } } free_extent_buffer(eb); @@ -234,6 +266,7 @@ int write_tree_block(struct btrfs_trans_handle *trans, struct btrfs_root *root, if (!btrfs_buffer_uptodate(eb, trans->transid)) BUG(); + printf("writing out a block\n"); btrfs_set_header_flag(eb, BTRFS_HEADER_FLAG_WRITTEN); csum_tree_block(root, eb, 0); @@ -361,6 +394,7 @@ static int __commit_transaction(struct btrfs_trans_handle *trans, int btrfs_commit_transaction(struct btrfs_trans_handle *trans, struct btrfs_root *root) { + u64 transid = trans->transid; int ret = 0; struct btrfs_fs_info *fs_info = root->fs_info; @@ -388,6 +422,7 @@ commit_tree: free_extent_buffer(root->commit_root); root->commit_root = NULL; fs_info->running_transaction = NULL; + fs_info->last_trans_committed = transid; return 0; } @@ -567,28 +602,8 @@ struct btrfs_root *btrfs_read_fs_root(struct btrfs_fs_info *fs_info, return root; } -struct btrfs_root *open_ctree(const char *filename, u64 sb_bytenr, int writes) -{ - int fp; - struct btrfs_root *root; - int flags = O_CREAT | O_RDWR; - - if (!writes) - flags = O_RDONLY; - - fp = open(filename, flags, 0600); - if (fp < 0) { - fprintf (stderr, "Could not open %s\n", filename); - return NULL; - } - root = open_ctree_fd(fp, filename, sb_bytenr, writes); - close(fp); - - return root; -} - -struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, - int writes) +struct btrfs_root *__open_ctree_fd(int fp, const char *path, u64 sb_bytenr, + u64 root_tree_bytenr, int writes) { u32 sectorsize; u32 nodesize; @@ -617,12 +632,13 @@ struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, if (ret) { fprintf(stderr, "No valid Btrfs found on %s\n", path); - return NULL; + goto out; } if (total_devs != 1) { ret = btrfs_scan_for_fsid(fs_devices, total_devs, 1); - BUG_ON(ret); + if (ret) + goto out; } memset(fs_info, 0, sizeof(*fs_info)); @@ -657,7 +673,8 @@ struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, ret = btrfs_open_devices(fs_devices, O_RDWR); else ret = btrfs_open_devices(fs_devices, O_RDONLY); - BUG_ON(ret); + if (ret) + goto out_cleanup; fs_info->super_bytenr = sb_bytenr; disk_super = &fs_info->super_copy; @@ -665,7 +682,7 @@ struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, disk_super, sb_bytenr); if (ret) { printk("No valid btrfs found\n"); - BUG_ON(1); + goto out_devices; } memcpy(fs_info->fsid, &disk_super->fsid, BTRFS_FSID_SIZE); @@ -677,7 +694,7 @@ struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, printk("couldn't open because of unsupported " "option features (%Lx).\n", (unsigned long long)features); - BUG_ON(1); + goto out_devices; } features = btrfs_super_incompat_flags(disk_super); @@ -692,7 +709,7 @@ struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, printk("couldn't open RDWR because of unsupported " "option features (%Lx).\n", (unsigned long long)features); - BUG_ON(1); + goto out_devices; } nodesize = btrfs_super_nodesize(disk_super); @@ -705,7 +722,8 @@ struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, tree_root->stripesize = stripesize; ret = btrfs_read_sys_array(tree_root); - BUG_ON(ret); + if (ret) + goto out_devices; blocksize = btrfs_level_size(tree_root, btrfs_super_chunk_root_level(disk_super)); generation = btrfs_super_chunk_root_generation(disk_super); @@ -716,8 +734,10 @@ struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, chunk_root->node = read_tree_block(chunk_root, btrfs_super_chunk_root(disk_super), blocksize, generation); - - BUG_ON(!chunk_root->node); + if (!chunk_root->node) { + printk("Couldn't read chunk root\n"); + goto out_devices; + } read_extent_buffer(chunk_root->node, fs_info->chunk_tree_uuid, (unsigned long)btrfs_header_chunk_tree_uuid(chunk_root->node), @@ -725,37 +745,51 @@ struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, if (!(btrfs_super_flags(disk_super) & BTRFS_SUPER_FLAG_METADUMP)) { ret = btrfs_read_chunk_tree(chunk_root); - BUG_ON(ret); + if (ret) + goto out_chunk; } blocksize = btrfs_level_size(tree_root, btrfs_super_root_level(disk_super)); generation = btrfs_super_generation(disk_super); + if (!root_tree_bytenr) + root_tree_bytenr = btrfs_super_root(disk_super); tree_root->node = read_tree_block(tree_root, - btrfs_super_root(disk_super), + root_tree_bytenr, blocksize, generation); - BUG_ON(!tree_root->node); + if (!tree_root->node) { + printk("Couldn't read tree root\n"); + goto out_chunk; + } ret = find_and_setup_root(tree_root, fs_info, BTRFS_EXTENT_TREE_OBJECTID, extent_root); - BUG_ON(ret); + if (ret) { + printk("Couldn't setup extent tree\n"); + goto out_tree; + } extent_root->track_dirty = 1; ret = find_and_setup_root(tree_root, fs_info, BTRFS_DEV_TREE_OBJECTID, dev_root); - BUG_ON(ret); + if (ret) { + printk("Couldn't setup device tree\n"); + goto out_extent; + } dev_root->track_dirty = 1; ret = find_and_setup_root(tree_root, fs_info, BTRFS_CSUM_TREE_OBJECTID, csum_root); - BUG_ON(ret); + if (ret) { + printk("Couldn't setup csum tree\n"); + goto out_dev; + } csum_root->track_dirty = 1; - BUG_ON(ret); - find_and_setup_log_root(tree_root, fs_info, disk_super); - fs_info->generation = generation + 1; + fs_info->generation = generation; + fs_info->last_trans_committed = generation; btrfs_read_block_groups(fs_info->tree_root); key.objectid = BTRFS_FS_TREE_OBJECTID; @@ -763,11 +797,84 @@ struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, key.offset = (u64)-1; fs_info->fs_root = btrfs_read_fs_root(fs_info, &key); + if (!fs_info->fs_root) + goto out_csum; + fs_info->data_alloc_profile = (u64)-1; fs_info->metadata_alloc_profile = (u64)-1; fs_info->system_alloc_profile = fs_info->metadata_alloc_profile; return fs_info->fs_root; +out_csum: + free_extent_buffer(fs_info->csum_root->node); +out_dev: + free_extent_buffer(fs_info->dev_root->node); +out_extent: + free_extent_buffer(fs_info->extent_root->node); +out_tree: + free_extent_buffer(fs_info->tree_root->node); +out_chunk: + free_extent_buffer(fs_info->chunk_root->node); +out_devices: + close_all_devices(fs_info); +out_cleanup: + extent_io_tree_cleanup(&fs_info->extent_cache); + extent_io_tree_cleanup(&fs_info->free_space_cache); + extent_io_tree_cleanup(&fs_info->block_group_cache); + extent_io_tree_cleanup(&fs_info->pinned_extents); + extent_io_tree_cleanup(&fs_info->pending_del); + extent_io_tree_cleanup(&fs_info->extent_ins); +out: + free(tree_root); + free(extent_root); + free(chunk_root); + free(dev_root); + free(csum_root); + free(fs_info); + return NULL; +} + +struct btrfs_root *open_ctree(const char *filename, u64 sb_bytenr, int writes) +{ + int fp; + struct btrfs_root *root; + int flags = O_CREAT | O_RDWR; + + if (!writes) + flags = O_RDONLY; + + fp = open(filename, flags, 0600); + if (fp < 0) { + fprintf (stderr, "Could not open %s\n", filename); + return NULL; + } + root = __open_ctree_fd(fp, filename, sb_bytenr, 0, writes); + close(fp); + + return root; +} + +struct btrfs_root *open_ctree_recovery(const char *filename, u64 sb_bytenr, + u64 root_tree_bytenr) +{ + int fp; + struct btrfs_root *root; + + fp = open(filename, O_RDONLY); + if (fp < 0) { + fprintf (stderr, "Could not open %s\n", filename); + return NULL; + } + root = __open_ctree_fd(fp, filename, sb_bytenr, root_tree_bytenr, 0); + close(fp); + + return root; +} + +struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, + int writes) +{ + return __open_ctree_fd(fp, path, sb_bytenr, 0, writes); } int btrfs_read_dev_super(int fd, struct btrfs_super_block *sb, u64 sb_bytenr) @@ -945,15 +1052,18 @@ int close_ctree(struct btrfs_root *root) struct btrfs_trans_handle *trans; struct btrfs_fs_info *fs_info = root->fs_info; - trans = btrfs_start_transaction(root, 1); - btrfs_commit_transaction(trans, root); - trans = btrfs_start_transaction(root, 1); - ret = commit_tree_roots(trans, fs_info); - BUG_ON(ret); - ret = __commit_transaction(trans, root); - BUG_ON(ret); - write_ctree_super(trans, root); - btrfs_free_transaction(root, trans); + if (fs_info->last_trans_committed != + fs_info->generation) { + trans = btrfs_start_transaction(root, 1); + btrfs_commit_transaction(trans, root); + trans = btrfs_start_transaction(root, 1); + ret = commit_tree_roots(trans, fs_info); + BUG_ON(ret); + ret = __commit_transaction(trans, root); + BUG_ON(ret); + write_ctree_super(trans, root); + btrfs_free_transaction(root, trans); + } btrfs_free_block_groups(fs_info); free_fs_roots(fs_info); @@ -1018,7 +1128,7 @@ int btrfs_buffer_uptodate(struct extent_buffer *buf, u64 parent_transid) if (!ret) return ret; - ret = verify_parent_transid(buf->tree, buf, parent_transid); + ret = verify_parent_transid(buf->tree, buf, parent_transid, 1); return !ret; } diff --git a/disk-io.h b/disk-io.h index 7ebec24..2048fcf 100644 --- a/disk-io.h +++ b/disk-io.h @@ -46,6 +46,8 @@ int clean_tree_block(struct btrfs_trans_handle *trans, struct btrfs_root *open_ctree(const char *filename, u64 sb_bytenr, int writes); struct btrfs_root *open_ctree_fd(int fp, const char *path, u64 sb_bytenr, int writes); +struct btrfs_root *open_ctree_recovery(const char *filename, u64 sb_bytenr, + u64 root_tree_bytenr); int close_ctree(struct btrfs_root *root); int write_all_supers(struct btrfs_root *root); int write_ctree_super(struct btrfs_trans_handle *trans, diff --git a/extent-tree.c b/extent-tree.c index b5b7aaa..5bed3c2 100644 --- a/extent-tree.c +++ b/extent-tree.c @@ -1039,6 +1039,13 @@ static int lookup_inline_extent_backref(struct btrfs_trans_handle *trans, err = ret; goto out; } + if (ret) { + printf("Failed to find [%llu, %u, %llu]\n", key.objectid, key.type, key.offset); + btrfs_print_leaf(root, path->nodes[0]); + btrfs_free_path(path); + return -ENOENT; + } + BUG_ON(ret); leaf = path->nodes[0]; @@ -1059,6 +1066,12 @@ static int lookup_inline_extent_backref(struct btrfs_trans_handle *trans, item_size = btrfs_item_size_nr(leaf, path->slots[0]); } #endif + if (item_size < sizeof(*ei)) { + printf("Size is %u, needs to be %u, slot %d\n", item_size, + sizeof(*ei), path->slots[0]); + btrfs_print_leaf(root, leaf); + return -EINVAL; + } BUG_ON(item_size < sizeof(*ei)); ei = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_extent_item); diff --git a/extent_io.c b/extent_io.c index 8f0a876..973e918 100644 --- a/extent_io.c +++ b/extent_io.c @@ -654,7 +654,6 @@ struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree, if (cache) { eb = container_of(cache, struct extent_buffer, cache_node); - BUG_ON(eb->refs != 1); free_extent_buffer(eb); } eb = __alloc_extent_buffer(tree, bytenr, blocksize); diff --git a/find-root.c b/find-root.c new file mode 100644 index 0000000..c0f38b8 --- /dev/null +++ b/find-root.c @@ -0,0 +1,458 @@ +/* + * Copyright (C) 2011 Red Hat. 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. + */ + +#define _XOPEN_SOURCE 500 +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include "kerncompat.h" +#include "ctree.h" +#include "disk-io.h" +#include "print-tree.h" +#include "transaction.h" +#include "list.h" +#include "version.h" +#include "volumes.h" +#include "utils.h" +#include "crc32c.h" + +static int verbose = 0; +static u16 csum_size = 0; +static u64 search_objectid = BTRFS_ROOT_TREE_OBJECTID; + +static void usage() +{ + fprintf(stderr, "Usage: find-roots [-v] \n"); +} + +int csum_block(void *buf, u32 len) +{ + char *result; + u32 crc = ~(u32)0; + int ret = 0; + + result = malloc(csum_size * sizeof(char)); + if (!result) { + fprintf(stderr, "No memory\n"); + return 1; + } + + len -= BTRFS_CSUM_SIZE; + crc = crc32c(crc, buf + BTRFS_CSUM_SIZE, len); + btrfs_csum_final(crc, result); + + if (memcmp(buf, result, csum_size)) + ret = 1; + free(result); + return ret; +} + +static int __setup_root(u32 nodesize, u32 leafsize, u32 sectorsize, + u32 stripesize, struct btrfs_root *root, + struct btrfs_fs_info *fs_info, u64 objectid) +{ + root->node = NULL; + root->commit_root = NULL; + root->sectorsize = sectorsize; + root->nodesize = nodesize; + root->leafsize = leafsize; + root->stripesize = stripesize; + root->ref_cows = 0; + root->track_dirty = 0; + + root->fs_info = fs_info; + root->objectid = objectid; + root->last_trans = 0; + root->highest_inode = 0; + root->last_inode_alloc = 0; + + INIT_LIST_HEAD(&root->dirty_list); + memset(&root->root_key, 0, sizeof(root->root_key)); + memset(&root->root_item, 0, sizeof(root->root_item)); + root->root_key.objectid = objectid; + return 0; +} + +static int close_all_devices(struct btrfs_fs_info *fs_info) +{ + struct list_head *list; + struct list_head *next; + struct btrfs_device *device; + + return 0; + + list = &fs_info->fs_devices->devices; + list_for_each(next, list) { + device = list_entry(next, struct btrfs_device, dev_list); + close(device->fd); + } + return 0; +} + +static struct btrfs_root *open_ctree_broken(int fd, const char *device) +{ + u32 sectorsize; + u32 nodesize; + u32 leafsize; + u32 blocksize; + u32 stripesize; + u64 generation; + struct btrfs_root *tree_root = malloc(sizeof(struct btrfs_root)); + struct btrfs_root *extent_root = malloc(sizeof(struct btrfs_root)); + struct btrfs_root *chunk_root = malloc(sizeof(struct btrfs_root)); + struct btrfs_root *dev_root = malloc(sizeof(struct btrfs_root)); + struct btrfs_root *csum_root = malloc(sizeof(struct btrfs_root)); + struct btrfs_fs_info *fs_info = malloc(sizeof(*fs_info)); + int ret; + struct btrfs_super_block *disk_super; + struct btrfs_fs_devices *fs_devices = NULL; + u64 total_devs; + u64 features; + + ret = btrfs_scan_one_device(fd, device, &fs_devices, + &total_devs, BTRFS_SUPER_INFO_OFFSET); + + if (ret) { + fprintf(stderr, "No valid Btrfs found on %s\n", device); + goto out; + } + + if (total_devs != 1) { + ret = btrfs_scan_for_fsid(fs_devices, total_devs, 1); + if (ret) + goto out; + } + + memset(fs_info, 0, sizeof(*fs_info)); + fs_info->tree_root = tree_root; + fs_info->extent_root = extent_root; + fs_info->chunk_root = chunk_root; + fs_info->dev_root = dev_root; + fs_info->csum_root = csum_root; + + fs_info->readonly = 1; + + extent_io_tree_init(&fs_info->extent_cache); + extent_io_tree_init(&fs_info->free_space_cache); + extent_io_tree_init(&fs_info->block_group_cache); + extent_io_tree_init(&fs_info->pinned_extents); + extent_io_tree_init(&fs_info->pending_del); + extent_io_tree_init(&fs_info->extent_ins); + cache_tree_init(&fs_info->fs_root_cache); + + cache_tree_init(&fs_info->mapping_tree.cache_tree); + + mutex_init(&fs_info->fs_mutex); + fs_info->fs_devices = fs_devices; + INIT_LIST_HEAD(&fs_info->dirty_cowonly_roots); + INIT_LIST_HEAD(&fs_info->space_info); + + __setup_root(4096, 4096, 4096, 4096, tree_root, + fs_info, BTRFS_ROOT_TREE_OBJECTID); + + ret = btrfs_open_devices(fs_devices, O_RDONLY); + if (ret) + goto out_cleanup; + + fs_info->super_bytenr = BTRFS_SUPER_INFO_OFFSET; + disk_super = &fs_info->super_copy; + ret = btrfs_read_dev_super(fs_devices->latest_bdev, + disk_super, BTRFS_SUPER_INFO_OFFSET); + if (ret) { + printk("No valid btrfs found\n"); + goto out_devices; + } + + memcpy(fs_info->fsid, &disk_super->fsid, BTRFS_FSID_SIZE); + + + features = btrfs_super_incompat_flags(disk_super) & + ~BTRFS_FEATURE_INCOMPAT_SUPP; + if (features) { + printk("couldn't open because of unsupported " + "option features (%Lx).\n", features); + goto out_devices; + } + + features = btrfs_super_incompat_flags(disk_super); + if (!(features & BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF)) { + features |= BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF; + btrfs_set_super_incompat_flags(disk_super, features); + } + + nodesize = btrfs_super_nodesize(disk_super); + leafsize = btrfs_super_leafsize(disk_super); + sectorsize = btrfs_super_sectorsize(disk_super); + stripesize = btrfs_super_stripesize(disk_super); + tree_root->nodesize = nodesize; + tree_root->leafsize = leafsize; + tree_root->sectorsize = sectorsize; + tree_root->stripesize = stripesize; + + ret = btrfs_read_sys_array(tree_root); + if (ret) + goto out_devices; + blocksize = btrfs_level_size(tree_root, + btrfs_super_chunk_root_level(disk_super)); + generation = btrfs_super_chunk_root_generation(disk_super); + + __setup_root(nodesize, leafsize, sectorsize, stripesize, + chunk_root, fs_info, BTRFS_CHUNK_TREE_OBJECTID); + + chunk_root->node = read_tree_block(chunk_root, + btrfs_super_chunk_root(disk_super), + blocksize, generation); + if (!chunk_root->node) { + printk("Couldn't read chunk root\n"); + goto out_devices; + } + + read_extent_buffer(chunk_root->node, fs_info->chunk_tree_uuid, + (unsigned long)btrfs_header_chunk_tree_uuid(chunk_root->node), + BTRFS_UUID_SIZE); + + if (!(btrfs_super_flags(disk_super) & BTRFS_SUPER_FLAG_METADUMP)) { + ret = btrfs_read_chunk_tree(chunk_root); + if (ret) + goto out_chunk; + } + + return fs_info->chunk_root; +out_chunk: + free_extent_buffer(fs_info->chunk_root->node); +out_devices: + close_all_devices(fs_info); +out_cleanup: + extent_io_tree_cleanup(&fs_info->extent_cache); + extent_io_tree_cleanup(&fs_info->free_space_cache); + extent_io_tree_cleanup(&fs_info->block_group_cache); + extent_io_tree_cleanup(&fs_info->pinned_extents); + extent_io_tree_cleanup(&fs_info->pending_del); + extent_io_tree_cleanup(&fs_info->extent_ins); +out: + free(tree_root); + free(extent_root); + free(chunk_root); + free(dev_root); + free(csum_root); + free(fs_info); + return NULL; +} + +static int search_iobuf(struct btrfs_root *root, void *iobuf, + size_t iobuf_size, off_t offset) +{ + u64 gen = btrfs_super_generation(&root->fs_info->super_copy); + u64 objectid = search_objectid; + u32 size = btrfs_super_nodesize(&root->fs_info->super_copy); + u8 level = root->fs_info->super_copy.root_level; + size_t block_off = 0; + + while (block_off < iobuf_size) { + void *block = iobuf + block_off; + struct btrfs_header *header = block; + u64 h_byte, h_level, h_gen, h_owner; + +// printf("searching %Lu\n", offset + block_off); + h_byte = le64_to_cpu(header->bytenr); + h_owner = le64_to_cpu(header->owner); + h_level = header->level; + h_gen = le64_to_cpu(header->generation); + + if (h_owner != objectid) + goto next; + if (h_byte != (offset + block_off)) + goto next; + if (h_level != level) + goto next; + if (csum_block(block, size)) { + fprintf(stderr, "Well block %Lu seems good, " + "but the csum doesn't match\n", + h_byte); + goto next; + } + if (h_gen != gen) { + fprintf(stderr, "Well block %Lu seems great, " + "but generation doesn't match, " + "have=%Lu, want=%Lu\n", h_byte, h_gen, + gen); + goto next; + } + printf("Found tree root at %Lu\n", h_byte); + return 0; +next: + block_off += size; + } + + return 1; +} + +static int read_physical(struct btrfs_root *root, int fd, u64 offset, + u64 bytenr, u64 len) +{ + char *iobuf = malloc(len); + ssize_t done; + size_t total_read = 0; + int ret = 1; + + if (!iobuf) { + fprintf(stderr, "No memory\n"); + return -1; + } + + while (total_read < len) { + done = pread64(fd, iobuf + total_read, len - total_read, + bytenr + total_read); + if (done < 0) { + fprintf(stderr, "Failed to read: %s\n", + strerror(errno)); + ret = -1; + goto out; + } + total_read += done; + } + + ret = search_iobuf(root, iobuf, total_read, offset); +out: + free(iobuf); + return ret; +} + +static int find_root(struct btrfs_root *root) +{ + struct btrfs_multi_bio *multi = NULL; + struct btrfs_device *device; + u64 metadata_offset = 0, metadata_size = 0; + off_t offset = 0; + off_t bytenr; + int fd; + int err; + int ret = 1; + + printf("Super think's the tree root is at %Lu, chunk root %Lu\n", + btrfs_super_root(&root->fs_info->super_copy), + btrfs_super_chunk_root(&root->fs_info->super_copy)); + + err = btrfs_next_metadata(&root->fs_info->mapping_tree, + &metadata_offset, &metadata_size); + if (err) + return ret; + + offset = metadata_offset; + while (1) { + u64 map_length = 4096; + u64 type; + + if (offset > + btrfs_super_total_bytes(&root->fs_info->super_copy)) { + printf("Went past the fs size, exiting"); + break; + } + if (offset >= (metadata_offset + metadata_size)) { + err = btrfs_next_metadata(&root->fs_info->mapping_tree, + &metadata_offset, + &metadata_size); + if (err) { + printf("No more metdata to scan, exiting\n"); + break; + } + offset = metadata_offset; + } + err = __btrfs_map_block(&root->fs_info->mapping_tree, READ, + offset, &map_length, &type, &multi, 0); + if (err) { + offset += map_length; + continue; + } + + if (!(type & BTRFS_BLOCK_GROUP_METADATA)) { + offset += map_length; + continue; + } + + device = multi->stripes[0].dev; + fd = device->fd; + bytenr = multi->stripes[0].physical; + kfree(multi); + + err = read_physical(root, fd, offset, bytenr, map_length); + if (!err) { + ret = 0; + break; + } else if (err < 0) { + ret = err; + break; + } + offset += map_length; + } + return ret; +} + +int main(int argc, char **argv) +{ + struct btrfs_root *root; + int dev_fd; + int opt; + int ret; + + while ((opt = getopt(argc, argv, "vo:")) != -1) { + switch(opt) { + case 'v': + verbose++; + break; + case 'o': + errno = 0; + search_objectid = (u64)strtoll(optarg, NULL, + 10); + if (errno) { + fprintf(stderr, "Error parsing " + "objectid\n"); + exit(1); + } + break; + default: + usage(); + exit(1); + } + } + + if (optind >= argc) { + usage(); + exit(1); + } + + dev_fd = open(argv[optind], O_RDONLY); + if (dev_fd < 0) { + fprintf(stderr, "Failed to open device %s\n", argv[optind]); + exit(1); + } + + root = open_ctree_broken(dev_fd, argv[optind]); + close(dev_fd); + if (!root) + exit(1); + + csum_size = btrfs_super_csum_size(&root->fs_info->super_copy); + ret = find_root(root); + close_ctree(root); + return ret; +} diff --git a/restore.c b/restore.c new file mode 100644 index 0000000..c20f4e1 --- /dev/null +++ b/restore.c @@ -0,0 +1,854 @@ +/* + * Copyright (C) 2011 Red Hat. 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. + */ + +#define _XOPEN_SOURCE 500 +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include "kerncompat.h" +#include "ctree.h" +#include "disk-io.h" +#include "print-tree.h" +#include "transaction.h" +#include "list.h" +#include "version.h" +#include "volumes.h" +#include "utils.h" + +static char path_name[4096]; +static int get_snaps = 0; +static int verbose = 0; +static int ignore_errors = 0; +static int overwrite = 0; + +static int decompress(char *inbuf, char *outbuf, u64 compress_len, + u64 decompress_len) +{ + z_stream strm; + int ret; + + memset(&strm, 0, sizeof(strm)); + ret = inflateInit(&strm); + if (ret != Z_OK) { + fprintf(stderr, "inflate init returnd %d\n", ret); + return -1; + } + + strm.avail_in = compress_len; + strm.next_in = (unsigned char *)inbuf; + strm.avail_out = decompress_len; + strm.next_out = (unsigned char *)outbuf; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret != Z_STREAM_END) { + (void)inflateEnd(&strm); + fprintf(stderr, "ret is %d\n", ret); + return -1; + } + + (void)inflateEnd(&strm); + return 0; +} + +int next_leaf(struct btrfs_root *root, struct btrfs_path *path) +{ + int slot; + int level = 1; + struct extent_buffer *c; + struct extent_buffer *next = NULL; + + for (; level < BTRFS_MAX_LEVEL; level++) { + if (path->nodes[level]) + break; + } + + if (level == BTRFS_MAX_LEVEL) + return 1; + + slot = path->slots[level] + 1; + + while(level < BTRFS_MAX_LEVEL) { + if (!path->nodes[level]) + return 1; + + slot = path->slots[level] + 1; + c = path->nodes[level]; + if (slot >= btrfs_header_nritems(c)) { + level++; + if (level == BTRFS_MAX_LEVEL) + return 1; + continue; + } + + if (next) + free_extent_buffer(next); + + if (path->reada) + reada_for_search(root, path, level, slot, 0); + + next = read_node_slot(root, c, slot); + break; + } + path->slots[level] = slot; + while(1) { + level--; + c = path->nodes[level]; + free_extent_buffer(c); + path->nodes[level] = next; + path->slots[level] = 0; + if (!level) + break; + if (path->reada) + reada_for_search(root, path, level, 0, 0); + next = read_node_slot(root, next, 0); + } + return 0; +} + +static int copy_one_inline(int fd, struct btrfs_path *path, u64 pos) +{ + struct extent_buffer *leaf = path->nodes[0]; + struct btrfs_file_extent_item *fi; + char buf[4096]; + char *outbuf; + ssize_t done; + unsigned long ptr; + int ret; + int len; + int ram_size; + int compress; + + fi = btrfs_item_ptr(leaf, path->slots[0], + struct btrfs_file_extent_item); + ptr = btrfs_file_extent_inline_start(fi); + len = btrfs_file_extent_inline_item_len(leaf, + btrfs_item_nr(leaf, path->slots[0])); + read_extent_buffer(leaf, buf, ptr, len); + + compress = btrfs_file_extent_compression(leaf, fi); + if (compress == BTRFS_COMPRESS_NONE) { + done = pwrite(fd, buf, len, pos); + if (done < len) { + fprintf(stderr, "Short inline write, wanted %d, did " + "%zd: %d\n", len, done, errno); + return -1; + } + return 0; + } + + ram_size = btrfs_file_extent_ram_bytes(leaf, fi); + outbuf = malloc(ram_size); + if (!outbuf) { + fprintf(stderr, "No memory\n"); + return -1; + } + + ret = decompress(buf, outbuf, len, ram_size); + if (ret) { + free(outbuf); + return ret; + } + + done = pwrite(fd, outbuf, ram_size, pos); + free(outbuf); + if (done < len) { + fprintf(stderr, "Short compressed inline write, wanted %d, " + "did %zd: %d\n", ram_size, done, errno); + return -1; + } + + return 0; +} + +static int copy_one_extent(struct btrfs_root *root, int fd, + struct extent_buffer *leaf, + struct btrfs_file_extent_item *fi, u64 pos) +{ + struct btrfs_multi_bio *multi = NULL; + struct btrfs_device *device; + char *inbuf, *outbuf = NULL; + ssize_t done, total = 0; + u64 bytenr; + u64 ram_size; + u64 disk_size; + u64 length; + u64 size_left; + u64 dev_bytenr; + u64 count = 0; + int compress; + int ret; + int dev_fd; + + compress = btrfs_file_extent_compression(leaf, fi); + bytenr = btrfs_file_extent_disk_bytenr(leaf, fi); + disk_size = btrfs_file_extent_disk_num_bytes(leaf, fi); + ram_size = btrfs_file_extent_ram_bytes(leaf, fi); + size_left = disk_size; + + inbuf = malloc(disk_size); + if (!inbuf) { + fprintf(stderr, "No memory\n"); + return -1; + } + + if (compress != BTRFS_COMPRESS_NONE) { + outbuf = malloc(ram_size); + if (!outbuf) { + fprintf(stderr, "No memory\n"); + free(inbuf); + return -1; + } + } +again: + length = size_left; + ret = btrfs_map_block(&root->fs_info->mapping_tree, READ, + bytenr, &length, &multi, 0); + if (ret) { + free(inbuf); + free(outbuf); + fprintf(stderr, "Error mapping block %d\n", ret); + return ret; + } + device = multi->stripes[0].dev; + dev_fd = device->fd; + device->total_ios++; + dev_bytenr = multi->stripes[0].physical; + kfree(multi); + + if (size_left < length) + length = size_left; + size_left -= length; + + done = pread(dev_fd, inbuf+count, length, dev_bytenr); + if (done < length) { + free(inbuf); + free(outbuf); + fprintf(stderr, "Short read %d\n", errno); + return -1; + } + + count += length; + bytenr += length; + if (size_left) + goto again; + + + if (compress == BTRFS_COMPRESS_NONE) { + while (total < ram_size) { + done = pwrite(fd, inbuf+total, ram_size-total, + pos+total); + if (done < 0) { + free(inbuf); + fprintf(stderr, "Error writing: %d\n", errno); + return -1; + } + total += done; + } + free(inbuf); + return 0; + } + + ret = decompress(inbuf, outbuf, disk_size, ram_size); + free(inbuf); + if (ret) { + free(outbuf); + return ret; + } + + while (total < ram_size) { + done = pwrite(fd, outbuf+total, ram_size-total, pos+total); + if (done < 0) { + free(outbuf); + fprintf(stderr, "Error writing: %d\n", errno); + return -1; + } + total += done; + } + free(outbuf); + + return 0; +} + +static int ask_to_continue(const char *file) +{ + char buf[2]; + char *ret; + + printf("We seem to be looping a lot on %s, do you want to keep going " + "on ? (y/N): ", file); +again: + ret = fgets(buf, 2, stdin); + if (*ret == '\n' || tolower(*ret) == 'n') + return 1; + if (tolower(*ret) != 'y') { + printf("Please enter either 'y' or 'n': "); + goto again; + } + + return 0; +} + + +static int copy_file(struct btrfs_root *root, int fd, struct btrfs_key *key, + const char *file) +{ + struct extent_buffer *leaf; + struct btrfs_path *path; + struct btrfs_file_extent_item *fi; + struct btrfs_key found_key; + int ret; + int extent_type; + int compression; + int loops = 0; + + path = btrfs_alloc_path(); + if (!path) { + fprintf(stderr, "Ran out of memory\n"); + return -1; + } + path->skip_locking = 1; + + key->offset = 0; + key->type = BTRFS_EXTENT_DATA_KEY; + + ret = btrfs_search_slot(NULL, root, key, path, 0, 0); + if (ret < 0) { + fprintf(stderr, "Error searching %d\n", ret); + btrfs_free_path(path); + return ret; + } + + leaf = path->nodes[0]; + while (!leaf) { + ret = next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, "Error getting next leaf %d\n", + ret); + btrfs_free_path(path); + return ret; + } else if (ret > 0) { + /* No more leaves to search */ + btrfs_free_path(path); + return 0; + } + leaf = path->nodes[0]; + } + + while (1) { + if (loops++ >= 1024) { + ret = ask_to_continue(file); + if (ret) + break; + loops = 0; + } + if (path->slots[0] >= btrfs_header_nritems(leaf)) { + do { + ret = next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, "Error searching %d\n", ret); + btrfs_free_path(path); + return ret; + } else if (ret) { + /* No more leaves to search */ + btrfs_free_path(path); + return 0; + } + leaf = path->nodes[0]; + } while (!leaf); + continue; + } + btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]); + if (found_key.objectid != key->objectid) + break; + if (found_key.type != key->type) + break; + fi = btrfs_item_ptr(leaf, path->slots[0], + struct btrfs_file_extent_item); + extent_type = btrfs_file_extent_type(leaf, fi); + compression = btrfs_file_extent_compression(leaf, fi); + if (compression >= BTRFS_COMPRESS_LAST) { + fprintf(stderr, "Don't support compression yet %d\n", + compression); + btrfs_free_path(path); + return -1; + } + + if (extent_type == BTRFS_FILE_EXTENT_PREALLOC) + goto next; + if (extent_type == BTRFS_FILE_EXTENT_INLINE) { + ret = copy_one_inline(fd, path, found_key.offset); + if (ret) { + btrfs_free_path(path); + return -1; + } + } else if (extent_type == BTRFS_FILE_EXTENT_REG) { + ret = copy_one_extent(root, fd, leaf, fi, + found_key.offset); + if (ret) { + btrfs_free_path(path); + return ret; + } + } else { + printf("Weird extent type %d\n", extent_type); + } +next: + path->slots[0]++; + } + + btrfs_free_path(path); + return 0; +} + +static int search_dir(struct btrfs_root *root, struct btrfs_key *key, + const char *dir) +{ + struct btrfs_path *path; + struct extent_buffer *leaf; + struct btrfs_dir_item *dir_item; + struct btrfs_key found_key, location; + char filename[BTRFS_NAME_LEN + 1]; + unsigned long name_ptr; + int name_len; + int ret; + int fd; + int loops = 0; + u8 type; + + path = btrfs_alloc_path(); + if (!path) { + fprintf(stderr, "Ran out of memory\n"); + return -1; + } + path->skip_locking = 1; + + key->offset = 0; + key->type = BTRFS_DIR_INDEX_KEY; + + ret = btrfs_search_slot(NULL, root, key, path, 0, 0); + if (ret < 0) { + fprintf(stderr, "Error searching %d\n", ret); + btrfs_free_path(path); + return ret; + } + + leaf = path->nodes[0]; + while (!leaf) { + if (verbose > 1) + printf("No leaf after search, looking for the next " + "leaf\n"); + ret = next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, "Error getting next leaf %d\n", + ret); + btrfs_free_path(path); + return ret; + } else if (ret > 0) { + /* No more leaves to search */ + if (verbose) + printf("Reached the end of the tree looking " + "for the directory\n"); + btrfs_free_path(path); + return 0; + } + leaf = path->nodes[0]; + } + + while (leaf) { + if (loops++ >= 1024) { + printf("We have looped trying to restore files in %s " + "too many times to be making progress, " + "stopping\n", dir); + break; + } + + if (path->slots[0] >= btrfs_header_nritems(leaf)) { + do { + ret = next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, "Error searching %d\n", + ret); + btrfs_free_path(path); + return ret; + } else if (ret > 0) { + /* No more leaves to search */ + if (verbose) + printf("Reached the end of " + "the tree searching the" + " directory\n"); + btrfs_free_path(path); + return 0; + } + leaf = path->nodes[0]; + } while (!leaf); + continue; + } + btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]); + if (found_key.objectid != key->objectid) { + if (verbose > 1) + printf("Found objectid=%Lu, key=%Lu\n", + found_key.objectid, key->objectid); + break; + } + if (found_key.type != key->type) { + if (verbose > 1) + printf("Found type=%u, want=%u\n", + found_key.type, key->type); + break; + } + dir_item = btrfs_item_ptr(leaf, path->slots[0], + struct btrfs_dir_item); + name_ptr = (unsigned long)(dir_item + 1); + name_len = btrfs_dir_name_len(leaf, dir_item); + read_extent_buffer(leaf, filename, name_ptr, name_len); + filename[name_len] = '\0'; + type = btrfs_dir_type(leaf, dir_item); + btrfs_dir_item_key_to_cpu(leaf, dir_item, &location); + + snprintf(path_name, 4096, "%s/%s", dir, filename); + + + /* + * At this point we're only going to restore directories and + * files, no symlinks or anything else. + */ + if (type == BTRFS_FT_REG_FILE) { + if (!overwrite) { + static int warn = 0; + struct stat st; + + ret = stat(path_name, &st); + if (!ret) { + loops = 0; + if (verbose || !warn) + printf("Skipping existing file" + " %s\n", path_name); + if (warn) + goto next; + printf("If you wish to overwrite use " + "the -o option to overwrite\n"); + warn = 1; + goto next; + } + ret = 0; + } + if (verbose) + printf("Restoring %s\n", path_name); + fd = open(path_name, O_CREAT|O_WRONLY, 0644); + if (fd < 0) { + fprintf(stderr, "Error creating %s: %d\n", + path_name, errno); + if (ignore_errors) + goto next; + btrfs_free_path(path); + return -1; + } + loops = 0; + ret = copy_file(root, fd, &location, path_name); + close(fd); + if (ret) { + if (ignore_errors) + goto next; + btrfs_free_path(path); + return ret; + } + } else if (type == BTRFS_FT_DIR) { + struct btrfs_root *search_root = root; + char *dir = strdup(path_name); + + if (!dir) { + fprintf(stderr, "Ran out of memory\n"); + btrfs_free_path(path); + return -1; + } + + if (location.type == BTRFS_ROOT_ITEM_KEY) { + /* + * If we are a snapshot and this is the index + * object to ourselves just skip it. + */ + if (location.objectid == + root->root_key.objectid) { + free(dir); + goto next; + } + + search_root = btrfs_read_fs_root(root->fs_info, + &location); + if (IS_ERR(search_root)) { + free(dir); + fprintf(stderr, "Error reading " + "subvolume %s: %lu\n", + path_name, + PTR_ERR(search_root)); + if (ignore_errors) + goto next; + return PTR_ERR(search_root); + } + + /* + * A subvolume will have a key.offset of 0, a + * snapshot will have key.offset of a transid. + */ + if (search_root->root_key.offset != 0 && + get_snaps == 0) { + free(dir); + printf("Skipping snapshot %s\n", + filename); + goto next; + } + location.objectid = BTRFS_FIRST_FREE_OBJECTID; + } + + if (verbose) + printf("Restoring %s\n", path_name); + + errno = 0; + ret = mkdir(path_name, 0755); + if (ret && errno != EEXIST) { + free(dir); + fprintf(stderr, "Error mkdiring %s: %d\n", + path_name, errno); + if (ignore_errors) + goto next; + btrfs_free_path(path); + return -1; + } + loops = 0; + ret = search_dir(search_root, &location, dir); + free(dir); + if (ret) { + if (ignore_errors) + goto next; + btrfs_free_path(path); + return ret; + } + } +next: + path->slots[0]++; + } + + if (verbose) + printf("Done searching %s\n", dir); + btrfs_free_path(path); + return 0; +} + +static void usage() +{ + fprintf(stderr, "Usage: restore [-svio] [-t disk offset] " + "\n"); +} + +static struct btrfs_root *open_fs(const char *dev, u64 root_location, int super_mirror) +{ + struct btrfs_root *root; + u64 bytenr; + int i; + + for (i = super_mirror; i < BTRFS_SUPER_MIRROR_MAX; i++) { + bytenr = btrfs_sb_offset(i); + root = open_ctree_recovery(dev, bytenr, root_location); + if (root) + return root; + fprintf(stderr, "Could not open root, trying backup super\n"); + } + + return NULL; +} + +static int find_first_dir(struct btrfs_root *root, u64 *objectid) +{ + struct btrfs_path *path; + struct btrfs_key found_key; + struct btrfs_key key; + int ret = -1; + int i; + + key.objectid = 0; + key.type = BTRFS_DIR_INDEX_KEY; + key.offset = 0; + + path = btrfs_alloc_path(); + if (!path) { + fprintf(stderr, "Ran out of memory\n"); + goto out; + } + + ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); + if (ret < 0) { + fprintf(stderr, "Error searching %d\n", ret); + goto out; + } + + if (!path->nodes[0]) { + fprintf(stderr, "No leaf!\n"); + goto out; + } +again: + for (i = path->slots[0]; + i < btrfs_header_nritems(path->nodes[0]); i++) { + btrfs_item_key_to_cpu(path->nodes[0], &found_key, i); + if (found_key.type != key.type) + continue; + + printf("Using objectid %Lu for first dir\n", + found_key.objectid); + *objectid = found_key.objectid; + ret = 0; + goto out; + } + do { + ret = next_leaf(root, path); + if (ret < 0) { + fprintf(stderr, "Error getting next leaf %d\n", + ret); + goto out; + } else if (ret > 0) { + fprintf(stderr, "No more leaves\n"); + goto out; + } + } while (!path->nodes[0]); + if (path->nodes[0]) + goto again; + printf("Couldn't find a dir index item\n"); +out: + btrfs_free_path(path); + return ret; +} + +int main(int argc, char **argv) +{ + struct btrfs_root *root; + struct btrfs_key key; + char dir_name[128]; + u64 tree_location = 0; + u64 fs_location = 0; + int len; + int ret; + int opt; + int super_mirror = 0; + int find_dir = 0; + + while ((opt = getopt(argc, argv, "sviot:u:df:")) != -1) { + switch (opt) { + case 's': + get_snaps = 1; + break; + case 'v': + verbose++; + break; + case 'i': + ignore_errors = 1; + break; + case 'o': + overwrite = 1; + break; + case 't': + errno = 0; + tree_location = (u64)strtoll(optarg, NULL, 10); + if (errno != 0) { + fprintf(stderr, "Tree location not valid\n"); + exit(1); + } + break; + case 'f': + errno = 0; + fs_location = (u64)strtoll(optarg, NULL, 10); + if (errno != 0) { + fprintf(stderr, "Fs location not valid\n"); + exit(1); + } + break; + case 'u': + errno = 0; + super_mirror = (int)strtol(optarg, NULL, 10); + if (errno != 0 || + super_mirror >= BTRFS_SUPER_MIRROR_MAX) { + fprintf(stderr, "Super mirror not " + "valid\n"); + exit(1); + } + break; + case 'd': + find_dir = 1; + break; + default: + usage(); + exit(1); + } + } + + if (optind + 1 >= argc) { + usage(); + exit(1); + } + + if ((ret = check_mounted(argv[optind])) < 0) { + fprintf(stderr, "Could not check mount status: %s\n", + strerror(ret)); + return ret; + } else if (ret) { + fprintf(stderr, "%s is currently mounted. Aborting.\n", argv[optind + 1]); + return -EBUSY; + } + + root = open_fs(argv[optind], tree_location, super_mirror); + if (root == NULL) + return 1; + + if (fs_location != 0) { + free_extent_buffer(root->node); + root->node = read_tree_block(root, fs_location, 4096, 0); + if (!root->node) { + fprintf(stderr, "Failed to read fs location\n"); + goto out; + } + } + + printf("Root objectid is %Lu\n", root->objectid); + + memset(path_name, 0, 4096); + + strncpy(dir_name, argv[optind + 1], 128); + + /* Strip the trailing / on the dir name */ + while (1) { + len = strlen(dir_name); + if (dir_name[len - 1] != '/') + break; + dir_name[len - 1] = '\0'; + } + + if (find_dir) { + ret = find_first_dir(root, &key.objectid); + if (ret) + goto out; + } else { + key.objectid = BTRFS_FIRST_FREE_OBJECTID; + } + + ret = search_dir(root->fs_info->fs_root, &key, dir_name); + +out: + close_ctree(root); + return ret; +} diff --git a/volumes.c b/volumes.c index 37f68df..03bfb8c 100644 --- a/volumes.c +++ b/volumes.c @@ -982,6 +982,30 @@ int btrfs_num_copies(struct btrfs_mapping_tree *map_tree, u64 logical, u64 len) return ret; } +int btrfs_next_metadata(struct btrfs_mapping_tree *map_tree, u64 *logical, + u64 *size) +{ + struct cache_extent *ce; + struct map_lookup *map; + + ce = find_first_cache_extent(&map_tree->cache_tree, *logical); + + while (ce) { + ce = next_cache_extent(ce); + if (!ce) + return -ENOENT; + + map = container_of(ce, struct map_lookup, ce); + if (map->type & BTRFS_BLOCK_GROUP_METADATA) { + *logical = ce->start; + *size = ce->size; + return 0; + } + } + + return -ENOENT; +} + int btrfs_rmap_block(struct btrfs_mapping_tree *map_tree, u64 chunk_start, u64 physical, u64 devid, u64 **logical, int *naddrs, int *stripe_len) @@ -1042,6 +1066,14 @@ int btrfs_map_block(struct btrfs_mapping_tree *map_tree, int rw, u64 logical, u64 *length, struct btrfs_multi_bio **multi_ret, int mirror_num) { + return __btrfs_map_block(map_tree, rw, logical, length, NULL, + multi_ret, mirror_num); +} + +int __btrfs_map_block(struct btrfs_mapping_tree *map_tree, int rw, + u64 logical, u64 *length, u64 *type, + struct btrfs_multi_bio **multi_ret, int mirror_num) +{ struct cache_extent *ce; struct map_lookup *map; u64 offset; @@ -1057,16 +1089,24 @@ int btrfs_map_block(struct btrfs_mapping_tree *map_tree, int rw, stripes_allocated = 1; } again: + ce = find_first_cache_extent(&map_tree->cache_tree, logical); + if (!ce) { + if (multi) + kfree(multi); + return -ENOENT; + } + if (ce->start > logical || ce->start + ce->size < logical) { + if (multi) + kfree(multi); + return -ENOENT; + } + if (multi_ret) { multi = kzalloc(btrfs_multi_bio_size(stripes_allocated), GFP_NOFS); if (!multi) return -ENOMEM; } - - ce = find_first_cache_extent(&map_tree->cache_tree, logical); - BUG_ON(!ce); - BUG_ON(ce->start > logical || ce->start + ce->size < logical); map = container_of(ce, struct map_lookup, ce); offset = logical - ce->start; @@ -1158,6 +1198,8 @@ again: stripe_index++; } *multi_ret = multi; + if (type) + *type = map->type; out: return 0; } @@ -1442,7 +1484,7 @@ int btrfs_read_sys_array(struct btrfs_root *root) u8 *ptr; unsigned long sb_ptr; u32 cur; - int ret; + int ret = 0; sb = btrfs_find_create_tree_block(root, BTRFS_SUPER_INFO_OFFSET, BTRFS_SUPER_INFO_SIZE); @@ -1473,7 +1515,8 @@ int btrfs_read_sys_array(struct btrfs_root *root) if (key.type == BTRFS_CHUNK_ITEM_KEY) { chunk = (struct btrfs_chunk *)sb_ptr; ret = read_one_chunk(root, &key, sb, chunk); - BUG_ON(ret); + if (ret) + break; num_stripes = btrfs_chunk_num_stripes(sb, chunk); len = btrfs_chunk_item_size(num_stripes); } else { @@ -1484,7 +1527,7 @@ int btrfs_read_sys_array(struct btrfs_root *root) cur += len; } free_extent_buffer(sb); - return 0; + return ret; } int btrfs_read_chunk_tree(struct btrfs_root *root) diff --git a/volumes.h b/volumes.h index 93b0e48..7104d36 100644 --- a/volumes.h +++ b/volumes.h @@ -96,9 +96,14 @@ int btrfs_alloc_dev_extent(struct btrfs_trans_handle *trans, u64 chunk_tree, u64 chunk_objectid, u64 chunk_offset, u64 num_bytes, u64 *start); +int __btrfs_map_block(struct btrfs_mapping_tree *map_tree, int rw, + u64 logical, u64 *length, u64 *type, + struct btrfs_multi_bio **multi_ret, int mirror_num); int btrfs_map_block(struct btrfs_mapping_tree *map_tree, int rw, u64 logical, u64 *length, struct btrfs_multi_bio **multi_ret, int mirror_num); +int btrfs_next_metadata(struct btrfs_mapping_tree *map_tree, u64 *logical, + u64 *size); int btrfs_rmap_block(struct btrfs_mapping_tree *map_tree, u64 chunk_start, u64 physical, u64 devid, u64 **logical, int *naddrs, int *stripe_len); -- 2.7.4