btrfsck: add code to rebuild extent records
authorChris Mason <chris.mason@oracle.com>
Tue, 7 Feb 2012 10:13:24 +0000 (05:13 -0500)
committerChris Mason <chris.mason@oracle.com>
Tue, 7 Feb 2012 15:58:32 +0000 (10:58 -0500)
This also includes a new --repair btrfsck option.  For now it can
only fix errors in the extent allocation tree.

Signed-off-by: Chris Mason <chris.mason@oracle.com>
btrfsck.c
ctree.h
extent-tree.c
print-tree.c

index f09945f..ab66b12 100644 (file)
--- a/btrfsck.c
+++ b/btrfsck.c
@@ -23,6 +23,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/stat.h>
+#include <getopt.h>
 #include "kerncompat.h"
 #include "ctree.h"
 #include "disk-io.h"
@@ -74,9 +75,13 @@ struct extent_record {
        struct cache_extent cache;
        struct btrfs_disk_key parent_key;
        u64 start;
+       u64 max_size;
        u64 nr;
        u64 refs;
        u64 extent_item_refs;
+       u64 generation;
+       u64 info_objectid;
+       u8 info_level;
        unsigned int content_checked:1;
        unsigned int owner_ref_checked:1;
        unsigned int is_root:1;
@@ -1082,7 +1087,9 @@ static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path,
        ret = btrfs_lookup_extent_info(NULL, root,
                                       path->nodes[*level]->start,
                                       path->nodes[*level]->len, &refs, NULL);
-       BUG_ON(ret);
+       if (ret < 0)
+               goto out;
+
        if (refs > 1) {
                ret = enter_shared_node(root, path->nodes[*level]->start,
                                        refs, wc, *level);
@@ -1109,7 +1116,8 @@ static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path,
                blocksize = btrfs_level_size(root, *level - 1);
                ret = btrfs_lookup_extent_info(NULL, root, bytenr, blocksize,
                                               &refs, NULL);
-               BUG_ON(ret);
+               if (ret < 0)
+                       refs = 0;
 
                if (refs > 1) {
                        ret = enter_shared_node(root, bytenr, refs,
@@ -1834,12 +1842,12 @@ static int all_backpointers_checked(struct extent_record *rec, int print_errs)
                        if (!print_errs)
                                goto out;
                        tback = (struct tree_backref *)back;
-                       fprintf(stderr, "Backref %llu %s %llu not referenced\n",
+                       fprintf(stderr, "Backref %llu %s %llu not referenced back %p\n",
                                (unsigned long long)rec->start,
                                back->full_backref ? "parent" : "root",
                                back->full_backref ?
                                (unsigned long long)tback->parent :
-                               (unsigned long long)tback->root);
+                               (unsigned long long)tback->root, back);
                }
                if (back->is_data) {
                        dback = (struct data_backref *)back;
@@ -1849,7 +1857,7 @@ static int all_backpointers_checked(struct extent_record *rec, int print_errs)
                                        goto out;
                                fprintf(stderr, "Incorrect local backref count"
                                        " on %llu %s %llu owner %llu"
-                                       " offset %llu found %u wanted %u\n",
+                                       " offset %llu found %u wanted %u back %p\n",
                                        (unsigned long long)rec->start,
                                        back->full_backref ?
                                        "parent" : "root",
@@ -1858,7 +1866,7 @@ static int all_backpointers_checked(struct extent_record *rec, int print_errs)
                                        (unsigned long long)dback->root,
                                        (unsigned long long)dback->owner,
                                        (unsigned long long)dback->offset,
-                                       dback->found_ref, dback->num_refs);
+                                       dback->found_ref, dback->num_refs, back);
                        }
                }
                if (!back->is_data) {
@@ -1965,12 +1973,28 @@ static int check_block(struct btrfs_root *root,
 {
        struct extent_record *rec;
        struct cache_extent *cache;
+       struct btrfs_key key;
        int ret = 1;
+       int level;
 
        cache = find_cache_extent(extent_cache, buf->start, buf->len);
        if (!cache)
                return 1;
        rec = container_of(cache, struct extent_record, cache);
+       rec->generation = btrfs_header_generation(buf);
+
+       level = btrfs_header_level(buf);
+       if (btrfs_header_nritems(buf) > 0) {
+
+               if (level == 0)
+                       btrfs_item_key_to_cpu(buf, &key, 0);
+               else
+                       btrfs_node_key_to_cpu(buf, &key, 0);
+
+               rec->info_objectid = key.objectid;
+       }
+       rec->info_level = level;
+
        if (btrfs_is_leaf(buf)) {
                ret = check_leaf(root, &rec->parent_key, buf);
        } else {
@@ -2035,6 +2059,7 @@ static struct tree_backref *alloc_tree_backref(struct extent_record *rec,
                ref->node.full_backref = 0;
        }
        list_add_tail(&ref->node.list, &rec->backrefs);
+
        return ref;
 }
 
@@ -2052,7 +2077,7 @@ static struct data_backref *find_data_backref(struct extent_record *rec,
                if (!node->is_data)
                        continue;
                back = (struct data_backref *)node;
-               if (parent > 0) { 
+               if (parent > 0) {
                        if (!node->full_backref)
                                continue;
                        if (parent == back->parent)
@@ -2070,11 +2095,13 @@ static struct data_backref *find_data_backref(struct extent_record *rec,
 
 static struct data_backref *alloc_data_backref(struct extent_record *rec,
                                                u64 parent, u64 root,
-                                               u64 owner, u64 offset)
+                                               u64 owner, u64 offset,
+                                               u64 max_size)
 {
        struct data_backref *ref = malloc(sizeof(*ref));
        memset(&ref->node, 0, sizeof(ref->node));
        ref->node.is_data = 1;
+
        if (parent > 0) {
                ref->parent = parent;
                ref->owner = 0;
@@ -2089,13 +2116,16 @@ static struct data_backref *alloc_data_backref(struct extent_record *rec,
        ref->found_ref = 0;
        ref->num_refs = 0;
        list_add_tail(&ref->node.list, &rec->backrefs);
+       if (max_size > rec->max_size)
+               rec->max_size = max_size;
        return ref;
 }
 
 static int add_extent_rec(struct cache_tree *extent_cache,
                          struct btrfs_key *parent_key,
                          u64 start, u64 nr, u64 extent_item_refs,
-                         int is_root, int inc_ref, int set_checked)
+                         int is_root, int inc_ref, int set_checked,
+                         u64 max_size)
 {
        struct extent_record *rec;
        struct cache_extent *cache;
@@ -2136,11 +2166,15 @@ static int add_extent_rec(struct cache_tree *extent_cache,
                if (parent_key)
                        btrfs_cpu_key_to_disk(&rec->parent_key, parent_key);
 
+               if (rec->max_size < max_size)
+                       rec->max_size = max_size;
+
                maybe_free_extent_rec(extent_cache, rec);
                return ret;
        }
        rec = malloc(sizeof(*rec));
        rec->start = start;
+       rec->max_size = max_size;
        rec->nr = nr;
        rec->content_checked = 0;
        rec->owner_ref_checked = 0;
@@ -2187,7 +2221,7 @@ static int add_tree_backref(struct cache_tree *extent_cache, u64 bytenr,
 
        cache = find_cache_extent(extent_cache, bytenr, 1);
        if (!cache) {
-               add_extent_rec(extent_cache, NULL, bytenr, 1, 0, 0, 0, 0);
+               add_extent_rec(extent_cache, NULL, bytenr, 1, 0, 0, 0, 0, 0);
                cache = find_cache_extent(extent_cache, bytenr, 1);
                if (!cache)
                        abort();
@@ -2226,7 +2260,7 @@ static int add_tree_backref(struct cache_tree *extent_cache, u64 bytenr,
 
 static int add_data_backref(struct cache_tree *extent_cache, u64 bytenr,
                            u64 parent, u64 root, u64 owner, u64 offset,
-                           u32 num_refs, int found_ref)
+                           u32 num_refs, int found_ref, u64 max_size)
 {
        struct extent_record *rec;
        struct data_backref *back;
@@ -2234,7 +2268,8 @@ static int add_data_backref(struct cache_tree *extent_cache, u64 bytenr,
 
        cache = find_cache_extent(extent_cache, bytenr, 1);
        if (!cache) {
-               add_extent_rec(extent_cache, NULL, bytenr, 1, 0, 0, 0, 0);
+               add_extent_rec(extent_cache, NULL, bytenr, 1, 0, 0, 0, 0,
+                              max_size);
                cache = find_cache_extent(extent_cache, bytenr, 1);
                if (!cache)
                        abort();
@@ -2244,9 +2279,13 @@ static int add_data_backref(struct cache_tree *extent_cache, u64 bytenr,
        if (rec->start != bytenr) {
                abort();
        }
+       if (rec->max_size < max_size)
+               rec->max_size = max_size;
+
        back = find_data_backref(rec, parent, root, owner, offset);
        if (!back)
-               back = alloc_data_backref(rec, parent, root, owner, offset);
+               back = alloc_data_backref(rec, parent, root, owner, offset,
+                                         max_size);
 
        if (found_ref) {
                BUG_ON(num_refs != 1);
@@ -2359,11 +2398,10 @@ static int process_extent_ref_v0(struct cache_tree *extent_cache,
        btrfs_item_key_to_cpu(leaf, &key, slot);
        ref0 = btrfs_item_ptr(leaf, slot, struct btrfs_extent_ref_v0);
        if (btrfs_ref_objectid_v0(leaf, ref0) < BTRFS_FIRST_FREE_OBJECTID) {
-               add_tree_backref(extent_cache, key.objectid, key.offset,
-                                0, 0);
+               add_tree_backref(extent_cache, key.objectid, key.offset, 0, 0);
        } else {
                add_data_backref(extent_cache, key.objectid, key.offset, 0,
-                                0, 0, btrfs_ref_count_v0(leaf, ref0), 0);
+                                0, 0, btrfs_ref_count_v0(leaf, ref0), 0, 0);
        }
        return 0;
 }
@@ -2396,14 +2434,14 @@ static int process_extent_item(struct cache_tree *extent_cache,
                BUG();
 #endif
                return add_extent_rec(extent_cache, NULL, key.objectid,
-                                     key.offset, refs, 0, 0, 0);
+                                     key.offset, refs, 0, 0, 0, key.offset);
        }
 
        ei = btrfs_item_ptr(eb, slot, struct btrfs_extent_item);
        refs = btrfs_extent_refs(eb, ei);
 
        add_extent_rec(extent_cache, NULL, key.objectid, key.offset,
-                      refs, 0, 0, 0);
+                      refs, 0, 0, 0, key.offset);
 
        ptr = (unsigned long)(ei + 1);
        if (btrfs_extent_flags(eb, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK)
@@ -2431,14 +2469,14 @@ static int process_extent_item(struct cache_tree *extent_cache,
                                                                       dref),
                                        btrfs_extent_data_ref_offset(eb, dref),
                                        btrfs_extent_data_ref_count(eb, dref),
-                                       0);
+                                       0, key.offset);
                        break;
                case BTRFS_SHARED_DATA_REF_KEY:
                        sref = (struct btrfs_shared_data_ref *)(iref + 1);
                        add_data_backref(extent_cache, key.objectid, offset,
                                        0, 0, 0,
                                        btrfs_shared_data_ref_count(eb, sref),
-                                       0);
+                                       0, key.offset);
                        break;
                default:
                        fprintf(stderr, "corrupt extent record: key %Lu %u %Lu\n",
@@ -2515,6 +2553,8 @@ static int run_next_block(struct btrfs_root *root,
        nritems = btrfs_header_nritems(buf);
 
        ret = btrfs_lookup_extent_info(NULL, root, bytenr, size, NULL, &flags);
+       if (ret < 0)
+               flags = BTRFS_BLOCK_FLAG_FULL_BACKREF;
 
        if (flags & BTRFS_BLOCK_FLAG_FULL_BACKREF) {
                parent = bytenr;
@@ -2573,7 +2613,7 @@ static int run_next_block(struct btrfs_root *root,
                                                                       ref),
                                        btrfs_extent_data_ref_offset(buf, ref),
                                        btrfs_extent_data_ref_count(buf, ref),
-                                       0);
+                                       0, root->sectorsize);
                                continue;
                        }
                        if (key.type == BTRFS_SHARED_DATA_REF_KEY) {
@@ -2583,7 +2623,7 @@ static int run_next_block(struct btrfs_root *root,
                                add_data_backref(extent_cache,
                                        key.objectid, key.offset, 0, 0, 0, 
                                        btrfs_shared_data_ref_count(buf, ref),
-                                       0);
+                                       0, root->sectorsize);
                                continue;
                        }
                        if (key.type != BTRFS_EXTENT_DATA_KEY)
@@ -2606,26 +2646,33 @@ static int run_next_block(struct btrfs_root *root,
                        ret = add_extent_rec(extent_cache, NULL,
                                   btrfs_file_extent_disk_bytenr(buf, fi),
                                   btrfs_file_extent_disk_num_bytes(buf, fi),
-                                  0, 0, 1, 1);
+                                  0, 0, 1, 1,
+                                  btrfs_file_extent_disk_num_bytes(buf, fi));
                        add_data_backref(extent_cache,
                                btrfs_file_extent_disk_bytenr(buf, fi),
                                parent, owner, key.objectid, key.offset -
-                               btrfs_file_extent_offset(buf, fi), 1, 1);
+                               btrfs_file_extent_offset(buf, fi), 1, 1,
+                               btrfs_file_extent_disk_num_bytes(buf, fi));
                        BUG_ON(ret);
                }
        } else {
                int level;
+               struct btrfs_key first_key;
+
+               first_key.objectid = 0;
+
+               if (nritems > 0)
+                       btrfs_item_key_to_cpu(buf, &first_key, 0);
                level = btrfs_header_level(buf);
                for (i = 0; i < nritems; i++) {
                        u64 ptr = btrfs_node_blockptr(buf, i);
                        u32 size = btrfs_level_size(root, level - 1);
                        btrfs_node_key_to_cpu(buf, &key, i);
                        ret = add_extent_rec(extent_cache, &key,
-                                            ptr, size, 0, 0, 1, 0);
+                                            ptr, size, 0, 0, 1, 0, size);
                        BUG_ON(ret);
 
-                       add_tree_backref(extent_cache, ptr, parent, 
-                                        owner, 1);
+                       add_tree_backref(extent_cache, ptr, parent, owner, 1);
 
                        if (level > 1) {
                                add_pending(nodes, seen, ptr, size);
@@ -2663,25 +2710,313 @@ static int add_root_to_pending(struct extent_buffer *buf,
        else
                add_pending(pending, seen, buf->start, buf->len);
        add_extent_rec(extent_cache, NULL, buf->start, buf->len,
-                      0, 1, 1, 0);
+                      0, 1, 1, 0, buf->len);
 
        if (root_key->objectid == BTRFS_TREE_RELOC_OBJECTID ||
            btrfs_header_backref_rev(buf) < BTRFS_MIXED_BACKREF_REV)
-               add_tree_backref(extent_cache, buf->start, buf->start, 0, 1);
+               add_tree_backref(extent_cache, buf->start, buf->start,
+                                0, 1);
        else
                add_tree_backref(extent_cache, buf->start, 0,
                                 root_key->objectid, 1);
        return 0;
 }
 
+static int delete_extent_records(struct btrfs_trans_handle *trans,
+                                struct btrfs_root *root,
+                                struct btrfs_path *path,
+                                u64 bytenr)
+{
+       struct btrfs_key key;
+       struct btrfs_key found_key;
+       struct extent_buffer *leaf;
+       int ret;
+       int slot;
+
+
+       key.objectid = bytenr;
+       key.type = (u8)-1;
+       key.offset = (u64)-1;
+
+       while(1) {
+               ret = btrfs_search_slot(trans, root->fs_info->extent_root,
+                                       &key, path, 0, 1);
+               if (ret < 0)
+                       break;
+
+               if (ret > 0) {
+                       ret = 0;
+                       if (path->slots[0] == 0)
+                               break;
+                       path->slots[0]--;
+               }
+               ret = 0;
+
+               leaf = path->nodes[0];
+               slot = path->slots[0];
+
+               btrfs_item_key_to_cpu(leaf, &found_key, slot);
+               if (found_key.objectid != bytenr)
+                       break;
+
+               if (found_key.type != BTRFS_EXTENT_ITEM_KEY &&
+                   found_key.type != BTRFS_TREE_BLOCK_REF_KEY &&
+                   found_key.type != BTRFS_EXTENT_DATA_REF_KEY &&
+                   found_key.type != BTRFS_EXTENT_REF_V0_KEY &&
+                   found_key.type != BTRFS_SHARED_BLOCK_REF_KEY &&
+                   found_key.type != BTRFS_SHARED_DATA_REF_KEY) {
+                       btrfs_release_path(NULL, path);
+                       if (found_key.type == 0) {
+                               if (found_key.offset == 0)
+                                       break;
+                               key.offset = found_key.offset - 1;
+                               key.type = found_key.type;
+                       }
+                       key.type = found_key.type - 1;
+                       key.offset = (u64)-1;
+                       continue;
+               }
+
+               fprintf(stderr, "repair deleting extent record: key %Lu %u %Lu\n",
+                       found_key.objectid, found_key.type, found_key.offset);
+
+               ret = btrfs_del_item(trans, root->fs_info->extent_root, path);
+               if (ret)
+                       break;
+               btrfs_release_path(NULL, path);
+
+               if (found_key.type == BTRFS_EXTENT_ITEM_KEY) {
+                       ret = btrfs_update_block_group(trans, root, bytenr,
+                                                      found_key.offset, 0, 1);
+                       if (ret)
+                               break;
+               }
+       }
+
+       btrfs_release_path(NULL, path);
+       return ret;
+}
+
+/*
+ * for a single backref, this will allocate a new extent
+ * and add the backref to it.
+ */
+static int record_extent(struct btrfs_trans_handle *trans,
+                        struct btrfs_fs_info *info,
+                        struct btrfs_path *path,
+                        struct extent_record *rec,
+                        struct extent_backref *back,
+                        int allocated, u64 flags)
+{
+       int ret;
+       struct btrfs_root *extent_root = info->extent_root;
+       struct extent_buffer *leaf;
+       struct btrfs_key ins_key;
+       struct btrfs_extent_item *ei;
+       struct tree_backref *tback;
+       struct data_backref *dback;
+       struct btrfs_tree_block_info *bi;
+
+       if (!back->is_data)
+               rec->max_size = max_t(u64, rec->max_size,
+                                   info->extent_root->leafsize);
+
+       if (!allocated) {
+               u32 item_size = sizeof(*ei);
+
+               if (!back->is_data)
+                       item_size += sizeof(*bi);
+
+               ins_key.objectid = rec->start;
+               ins_key.offset = rec->max_size;
+               ins_key.type = BTRFS_EXTENT_ITEM_KEY;
+
+               ret = btrfs_insert_empty_item(trans, extent_root, path,
+                                       &ins_key, item_size);
+               if (ret)
+                       goto fail;
+
+               leaf = path->nodes[0];
+               ei = btrfs_item_ptr(leaf, path->slots[0],
+                                   struct btrfs_extent_item);
+
+               btrfs_set_extent_refs(leaf, ei, 0);
+               btrfs_set_extent_generation(leaf, ei, rec->generation);
+
+               if (back->is_data) {
+                       btrfs_set_extent_flags(leaf, ei,
+                                              BTRFS_EXTENT_FLAG_DATA);
+               } else {
+                       struct btrfs_disk_key copy_key;;
+
+                       tback = (struct tree_backref *)back;
+                       bi = (struct btrfs_tree_block_info *)(ei + 1);
+                       memset_extent_buffer(leaf, 0, (unsigned long)bi,
+                                            sizeof(*bi));
+                       memset(&copy_key, 0, sizeof(copy_key));
+
+                       copy_key.objectid = le64_to_cpu(rec->info_objectid);
+                       btrfs_set_tree_block_level(leaf, bi, rec->info_level);
+                       btrfs_set_tree_block_key(leaf, bi, &copy_key);
+
+                       btrfs_set_extent_flags(leaf, ei,
+                                              BTRFS_EXTENT_FLAG_TREE_BLOCK | flags);
+               }
+
+               btrfs_mark_buffer_dirty(leaf);
+               ret = btrfs_update_block_group(trans, extent_root, rec->start,
+                                              rec->max_size, 1, 0);
+               if (ret)
+                       goto fail;
+               btrfs_release_path(NULL, path);
+       }
+
+       if (back->is_data) {
+               u64 parent;
+               int i;
+
+               dback = (struct data_backref *)back;
+               if (back->full_backref)
+                       parent = dback->parent;
+               else
+                       parent = 0;
+
+               for (i = 0; i < dback->found_ref; i++) {
+                       /* if parent != 0, we're doing a full backref
+                        * passing BTRFS_FIRST_FREE_OBJECTID as the owner
+                        * just makes the backref allocator create a data
+                        * backref
+                        */
+                       ret = btrfs_inc_extent_ref(trans, info->extent_root,
+                                                  rec->start, rec->max_size,
+                                                  parent,
+                                                  dback->root,
+                                                  parent ?
+                                                  BTRFS_FIRST_FREE_OBJECTID :
+                                                  dback->owner,
+                                                  dback->offset);
+                       if (ret)
+                               break;
+               }
+               fprintf(stderr, "adding new data backref"
+                               " on %llu %s %llu owner %llu"
+                               " offset %llu found %d\n",
+                               (unsigned long long)rec->start,
+                               back->full_backref ?
+                               "parent" : "root",
+                               back->full_backref ?
+                               (unsigned long long)parent :
+                               (unsigned long long)dback->root,
+                               (unsigned long long)dback->owner,
+                               (unsigned long long)dback->offset,
+                               dback->found_ref);
+       } else {
+               u64 parent;
+
+               tback = (struct tree_backref *)back;
+               if (back->full_backref)
+                       parent = tback->parent;
+               else
+                       parent = 0;
+
+               ret = btrfs_inc_extent_ref(trans, info->extent_root,
+                                          rec->start, rec->max_size,
+                                          parent, tback->root, 0, 0);
+               fprintf(stderr, "adding new tree backref on "
+                       "start %llu len %llu parent %llu root %llu\n",
+                       rec->start, rec->max_size, tback->parent, tback->root);
+       }
+       if (ret)
+               goto fail;
+fail:
+       btrfs_release_path(NULL, path);
+       return ret;
+}
+
+/*
+ * when an incorrect extent item is found, this will delete
+ * all of the existing entries for it and recreate them
+ * based on what the tree scan found.
+ */
+static int fixup_extent_refs(struct btrfs_trans_handle *trans,
+                            struct btrfs_fs_info *info,
+                            struct extent_record *rec)
+{
+       int ret;
+       struct btrfs_path *path;
+       struct list_head *cur = rec->backrefs.next;
+       struct extent_backref *back;
+       int allocated = 0;
+       u64 flags = 0;
+
+       /* remember our flags for recreating the extent */
+       ret = btrfs_lookup_extent_info(NULL, info->extent_root, rec->start,
+                                      rec->max_size, NULL, &flags);
+       if (ret < 0)
+               flags = BTRFS_BLOCK_FLAG_FULL_BACKREF;
+
+       path = btrfs_alloc_path();
+
+       /* step one, delete all the existing records */
+       ret = delete_extent_records(trans, info->extent_root, path,
+                                   rec->start);
+
+       if (ret < 0)
+               goto out;
+
+       /* step two, recreate all the refs we did find */
+       while(cur != &rec->backrefs) {
+               back = list_entry(cur, struct extent_backref, list);
+               cur = cur->next;
+
+               /*
+                * if we didn't find any references, don't create a
+                * new extent record
+                */
+               if (!back->found_ref)
+                       continue;
+
+               ret = record_extent(trans, info, path, rec, back, allocated, flags);
+               allocated = 1;
+
+               if (ret)
+                       goto out;
+       }
+out:
+       btrfs_free_path(path);
+       return ret;
+}
+
 static int check_extent_refs(struct btrfs_root *root,
-                     struct cache_tree *extent_cache)
+                     struct cache_tree *extent_cache, int repair)
 {
        struct extent_record *rec;
        struct cache_extent *cache;
+       struct btrfs_trans_handle *trans = NULL;
        int err = 0;
+       int ret = 0;
+       int fixed = 0;
 
+       if (repair)
+               trans = btrfs_start_transaction(root, 1);
+
+       if (repair) {
+               /*
+                * if we're doing a repair, we have to make sure
+                * we don't allocate from the problem extents.
+                * In the worst case, this will be all the
+                * extents in the FS
+                */
+               cache = find_first_cache_extent(extent_cache, 0);
+               while(cache) {
+                       rec = container_of(cache, struct extent_record, cache);
+                       btrfs_pin_extent(root->fs_info,
+                                        rec->start, rec->nr);
+                       cache = next_cache_extent(cache);
+               }
+       }
        while(1) {
+               fixed = 0;
                cache = find_first_cache_extent(extent_cache, 0);
                if (!cache)
                        break;
@@ -2693,19 +3028,39 @@ static int check_extent_refs(struct btrfs_root *root,
                        fprintf(stderr, "extent item %llu, found %llu\n",
                                (unsigned long long)rec->extent_item_refs,
                                (unsigned long long)rec->refs);
+                       if (!fixed && repair) {
+                               ret = fixup_extent_refs(trans, root->fs_info, rec);
+                               if (ret)
+                                       goto repair_abort;
+                               fixed = 1;
+                       }
                        err = 1;
+
                }
                if (all_backpointers_checked(rec, 1)) {
                        fprintf(stderr, "backpointer mismatch on [%llu %llu]\n",
                                (unsigned long long)rec->start,
                                (unsigned long long)rec->nr);
 
+                       if (!fixed && repair) {
+                               ret = fixup_extent_refs(trans, root->fs_info, rec);
+                               if (ret)
+                                       goto repair_abort;
+                               fixed = 1;
+                       }
+
                        err = 1;
                }
                if (!rec->owner_ref_checked) {
                        fprintf(stderr, "owner ref check failed [%llu %llu]\n",
                                (unsigned long long)rec->start,
                                (unsigned long long)rec->nr);
+                       if (!fixed && repair) {
+                               ret = fixup_extent_refs(trans, root->fs_info, rec);
+                               if (ret)
+                                       goto repair_abort;
+                               fixed = 1;
+                       }
                        err = 1;
                }
 
@@ -2713,10 +3068,18 @@ static int check_extent_refs(struct btrfs_root *root,
                free_all_extent_backrefs(rec);
                free(rec);
        }
+repair_abort:
+       if (repair) {
+               if (ret) {
+                       fprintf(stderr, "failed to repair damaged filesystem, aborting\n");
+                       exit(1);
+               }
+               btrfs_commit_transaction(trans, root);
+       }
        return err;
 }
 
-static int check_extents(struct btrfs_root *root)
+static int check_extents(struct btrfs_root *root, int repair)
 {
        struct cache_tree extent_cache;
        struct cache_tree seen;
@@ -2797,7 +3160,7 @@ static int check_extents(struct btrfs_root *root)
                if (ret != 0)
                        break;
        }
-       ret = check_extent_refs(root, &extent_cache);
+       ret = check_extent_refs(root, &extent_cache, repair);
        return ret;
 }
 
@@ -2808,6 +3171,12 @@ static void print_usage(void)
        exit(1);
 }
 
+static struct option long_options[] = {
+       { "super", 1, NULL, 's' },
+       { "repair", 0, NULL, 0 },
+       { 0, 0, 0, 0}
+};
+
 int main(int ac, char **av)
 {
        struct cache_tree root_cache;
@@ -2816,10 +3185,13 @@ int main(int ac, char **av)
        u64 bytenr = 0;
        int ret;
        int num;
+       int repair = 0;
+       int option_index = 0;
 
        while(1) {
                int c;
-               c = getopt(ac, av, "s:");
+               c = getopt_long(ac, av, "", long_options,
+                               &option_index);
                if (c < 0)
                        break;
                switch(c) {
@@ -2829,9 +3201,14 @@ int main(int ac, char **av)
                                printf("using SB copy %d, bytenr %llu\n", num,
                                       (unsigned long long)bytenr);
                                break;
-                       default:
+                       case '?':
                                print_usage();
                }
+               if (option_index == 1) {
+                       printf("enabling repair mode\n");
+                       repair = 1;
+               }
+
        }
        ac = ac - optind;
 
@@ -2849,15 +3226,23 @@ int main(int ac, char **av)
                return -EBUSY;
        }
 
-       info = open_ctree_fs_info(av[optind], bytenr, 0, 0);
+       info = open_ctree_fs_info(av[optind], bytenr, repair, 1);
 
        if (info == NULL)
                return 1;
 
+       if (!extent_buffer_uptodate(info->tree_root->node) ||
+           !extent_buffer_uptodate(info->dev_root->node) ||
+           !extent_buffer_uptodate(info->extent_root->node) ||
+           !extent_buffer_uptodate(info->chunk_root->node)) {
+               fprintf(stderr, "Critical roots corrupted, unable to fsck the FS\n");
+               return -EIO;
+       }
+
        root = info->fs_root;
 
        fprintf(stderr, "checking extents\n");
-       ret = check_extents(root);
+       ret = check_extents(root, repair);
        if (ret)
                goto out;
        fprintf(stderr, "checking fs roots\n");
diff --git a/ctree.h b/ctree.h
index 1c3740c..5e770d2 100644 (file)
--- a/ctree.h
+++ b/ctree.h
@@ -1785,6 +1785,7 @@ static inline u32 btrfs_level_size(struct btrfs_root *root, int level) {
        btrfs_item_offset_nr(leaf, slot)))
 
 /* extent-tree.c */
+void btrfs_pin_extent(struct btrfs_fs_info *fs_info, u64 bytenr, u64 num_bytes);
 int btrfs_extent_post_op(struct btrfs_trans_handle *trans,
                         struct btrfs_root *root);
 int btrfs_copy_pinned(struct btrfs_root *root, struct extent_io_tree *copy);
index 5bed3c2..1f13992 100644 (file)
@@ -1041,8 +1041,6 @@ static int lookup_inline_extent_backref(struct btrfs_trans_handle *trans,
        }
        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;
        }
 
@@ -1067,8 +1065,9 @@ static int lookup_inline_extent_backref(struct btrfs_trans_handle *trans,
        }
 #endif
        if (item_size < sizeof(*ei)) {
-               printf("Size is %u, needs to be %u, slot %d\n", item_size,
-                      sizeof(*ei), path->slots[0]);
+               printf("Size is %u, needs to be %u, slot %d\n",
+                      (unsigned)item_size,
+                      (unsigned)sizeof(*ei), path->slots[0]);
                btrfs_print_leaf(root, leaf);
                return -EINVAL;
        }
@@ -1460,10 +1459,8 @@ int btrfs_lookup_extent_info(struct btrfs_trans_handle *trans,
        if (ret < 0)
                goto out;
        if (ret != 0) {
-               btrfs_print_leaf(root, path->nodes[0]);
-               printk("failed to find block number %Lu\n",
-                      (unsigned long long)bytenr);
-               BUG();
+               ret = -EIO;
+               goto out;
        }
 
        l = path->nodes[0];
@@ -1484,9 +1481,8 @@ int btrfs_lookup_extent_info(struct btrfs_trans_handle *trans,
                        extent_flags = BTRFS_BLOCK_FLAG_FULL_BACKREF;
 #else
                        BUG();
-#endif         
-               }
-               BUG_ON(num_refs == 0);
+#endif
+       }
        item = btrfs_item_ptr(l, path->slots[0], struct btrfs_extent_item);
        if (refs)
                *refs = num_refs;
@@ -2033,6 +2029,12 @@ pinit:
        return 0;
 }
 
+void btrfs_pin_extent(struct btrfs_fs_info *fs_info,
+                      u64 bytenr, u64 num_bytes)
+{
+       update_pinned_extents(fs_info->extent_root, bytenr, num_bytes, 1);
+}
+
 /*
  * remove an extent from the root, returns 0 on success
  */
index 494ba8b..fc134c0 100644 (file)
@@ -239,7 +239,7 @@ static void print_extent_item(struct extent_buffer *eb, int slot)
                               btrfs_shared_data_ref_count(eb, sref));
                        break;
                default:
-                       BUG();
+                       return;
                }
                ptr += btrfs_extent_inline_ref_size(type);
        }