summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--btrfs-corrupt-block.c109
-rw-r--r--cmds-check.c333
-rw-r--r--ctree.c135
-rw-r--r--ctree.h32
-rw-r--r--file-item.c2
5 files changed, 510 insertions, 101 deletions
diff --git a/btrfs-corrupt-block.c b/btrfs-corrupt-block.c
index f0c14a9d..10cae00a 100644
--- a/btrfs-corrupt-block.c
+++ b/btrfs-corrupt-block.c
@@ -105,6 +105,8 @@ static void print_usage(void)
"specify -i for the inode and -f for the field to corrupt)\n");
fprintf(stderr, "\t-m The metadata block to corrupt (must also "
"specify -f for the field to corrupt)\n");
+ fprintf(stderr, "\t-K The key to corrupt in the format "
+ "<num>,<num>,<num> (must also specify -f for the field)\n");
fprintf(stderr, "\t-f The field in the item to corrupt\n");
exit(1);
}
@@ -306,6 +308,13 @@ enum btrfs_metadata_block_field {
BTRFS_METADATA_BLOCK_BAD,
};
+enum btrfs_key_field {
+ BTRFS_KEY_OBJECTID,
+ BTRFS_KEY_TYPE,
+ BTRFS_KEY_OFFSET,
+ BTRFS_KEY_BAD,
+};
+
static enum btrfs_inode_field convert_inode_field(char *field)
{
if (!strncmp(field, "isize", FIELD_BUF_LEN))
@@ -328,6 +337,17 @@ convert_metadata_block_field(char *field)
return BTRFS_METADATA_BLOCK_BAD;
}
+static enum btrfs_key_field convert_key_field(char *field)
+{
+ if (!strncmp(field, "objectid", FIELD_BUF_LEN))
+ return BTRFS_KEY_OBJECTID;
+ if (!strncmp(field, "type", FIELD_BUF_LEN))
+ return BTRFS_KEY_TYPE;
+ if (!strncmp(field, "offset", FIELD_BUF_LEN))
+ return BTRFS_KEY_OFFSET;
+ return BTRFS_KEY_BAD;
+}
+
static u64 generate_u64(u64 orig)
{
u64 ret;
@@ -337,6 +357,73 @@ static u64 generate_u64(u64 orig)
return ret;
}
+static u8 generate_u8(u8 orig)
+{
+ u8 ret;
+ do {
+ ret = rand();
+ } while (ret == orig);
+ return ret;
+}
+
+static int corrupt_key(struct btrfs_root *root, struct btrfs_key *key,
+ char *field)
+{
+ enum btrfs_key_field corrupt_field = convert_key_field(field);
+ struct btrfs_path *path;
+ struct btrfs_trans_handle *trans;
+ int ret;
+
+ root = root->fs_info->fs_root;
+ if (corrupt_field == BTRFS_KEY_BAD) {
+ fprintf(stderr, "Invalid field %s\n", field);
+ return -EINVAL;
+ }
+
+ path = btrfs_alloc_path();
+ if (!path)
+ return -ENOMEM;
+
+ trans = btrfs_start_transaction(root, 1);
+ if (IS_ERR(trans)) {
+ btrfs_free_path(path);
+ return PTR_ERR(trans);
+ }
+
+ ret = btrfs_search_slot(trans, root, key, path, 0, 1);
+ if (ret < 0)
+ goto out;
+ if (ret > 0) {
+ fprintf(stderr, "Couldn't find the key to corrupt\n");
+ ret = -ENOENT;
+ goto out;
+ }
+
+ switch (corrupt_field) {
+ case BTRFS_KEY_OBJECTID:
+ key->objectid = generate_u64(key->objectid);
+ break;
+ case BTRFS_KEY_TYPE:
+ key->type = generate_u8(key->type);
+ break;
+ case BTRFS_KEY_OFFSET:
+ key->offset = generate_u64(key->objectid);
+ break;
+ default:
+ fprintf(stderr, "Invalid field %s, %d\n", field,
+ corrupt_field);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ btrfs_set_item_key_unsafe(root, path, key);
+out:
+ btrfs_free_path(path);
+ btrfs_commit_transaction(trans, root);
+ return ret;
+}
+
+
static int corrupt_inode(struct btrfs_trans_handle *trans,
struct btrfs_root *root, u64 inode, char *field)
{
@@ -548,6 +635,7 @@ static struct option long_options[] = {
{ "file-extent", 1, NULL, 'x'},
{ "metadata-block", 1, NULL, 'm'},
{ "field", 1, NULL, 'f'},
+ { "key", 1, NULL, 'K'},
{ 0, 0, 0, 0}
};
@@ -696,6 +784,7 @@ out:
int main(int ac, char **av)
{
struct cache_tree root_cache;
+ struct btrfs_key key;
struct btrfs_root *root;
struct extent_buffer *eb;
char *dev;
@@ -717,10 +806,11 @@ int main(int ac, char **av)
field[0] = '\0';
srand(128);
+ memset(&key, 0, sizeof(key));
while(1) {
int c;
- c = getopt_long(ac, av, "l:c:b:eEkuUi:f:x:m:", long_options,
+ c = getopt_long(ac, av, "l:c:b:eEkuUi:f:x:m:K:", long_options,
&option_index);
if (c < 0)
break;
@@ -787,6 +877,17 @@ int main(int ac, char **av)
print_usage();
}
break;
+ case 'K':
+ ret = sscanf(optarg, "%llu,%u,%llu",
+ &key.objectid,
+ (unsigned int *)&key.type,
+ &key.offset);
+ if (ret != 3) {
+ fprintf(stderr, "error reading key "
+ "%d\n", errno);
+ print_usage();
+ }
+ break;
default:
print_usage();
}
@@ -882,6 +983,12 @@ int main(int ac, char **av)
ret = corrupt_metadata_block(root, metadata_block, field);
goto out_close;
}
+ if (key.objectid || key.offset || key.type) {
+ if (!strlen(field))
+ print_usage();
+ ret = corrupt_key(root, &key, field);
+ goto out_close;
+ }
/*
* If we made it here and we have extent set then we didn't specify
* inode and we're screwed.
diff --git a/cmds-check.c b/cmds-check.c
index cb5ed3e8..c658879c 100644
--- a/cmds-check.c
+++ b/cmds-check.c
@@ -49,6 +49,7 @@ static u64 data_bytes_allocated = 0;
static u64 data_bytes_referenced = 0;
static int found_old_backref = 0;
static LIST_HEAD(duplicate_extents);
+static LIST_HEAD(delete_items);
static int repair = 0;
struct extent_backref {
@@ -229,6 +230,12 @@ struct walk_control {
int root_level;
};
+struct bad_item {
+ struct btrfs_key key;
+ u64 root_id;
+ struct list_head list;
+};
+
static void reset_cached_block_groups(struct btrfs_fs_info *fs_info);
static u8 imode_to_type(u32 imode)
@@ -1225,6 +1232,8 @@ static int process_one_leaf(struct btrfs_root *root, struct extent_buffer *eb,
if (key.objectid == BTRFS_FREE_SPACE_OBJECTID)
continue;
+ if (key.type == BTRFS_ORPHAN_ITEM_KEY)
+ continue;
if (active_node->current == NULL ||
active_node->current->ino < key.objectid) {
@@ -1418,28 +1427,14 @@ out:
return ret;
}
-static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec)
+static int repair_inode_isize(struct btrfs_trans_handle *trans,
+ struct btrfs_root *root, struct btrfs_path *path,
+ struct inode_record *rec)
{
- struct btrfs_trans_handle *trans;
- struct btrfs_path *path;
struct btrfs_inode_item *ei;
struct btrfs_key key;
int ret;
- /* So far we just fix dir isize wrong */
- if (!(rec->errors & I_ERR_DIR_ISIZE_WRONG))
- return 1;
-
- path = btrfs_alloc_path();
- if (!path)
- return -ENOMEM;
-
- trans = btrfs_start_transaction(root, 1);
- if (IS_ERR(trans)) {
- btrfs_free_path(path);
- return PTR_ERR(trans);
- }
-
key.objectid = rec->ino;
key.type = BTRFS_INODE_ITEM_KEY;
key.offset = (u64)-1;
@@ -1469,6 +1464,53 @@ static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec)
printf("reset isize for dir %Lu root %Lu\n", rec->ino,
root->root_key.objectid);
out:
+ btrfs_release_path(path);
+ return ret;
+}
+
+static int repair_inode_orphan_item(struct btrfs_trans_handle *trans,
+ struct btrfs_root *root,
+ struct btrfs_path *path,
+ struct inode_record *rec)
+{
+ struct btrfs_key key;
+ int ret;
+
+ key.objectid = BTRFS_ORPHAN_OBJECTID;
+ key.type = BTRFS_ORPHAN_ITEM_KEY;
+ key.offset = rec->ino;
+
+ ret = btrfs_insert_empty_item(trans, root, path, &key, 0);
+ btrfs_release_path(path);
+ if (!ret)
+ rec->errors &= ~I_ERR_NO_ORPHAN_ITEM;
+ return ret;
+}
+
+static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec)
+{
+ struct btrfs_trans_handle *trans;
+ struct btrfs_path *path;
+ int ret = 0;
+
+ /* So far we just fix dir isize wrong */
+ if (!(rec->errors & (I_ERR_DIR_ISIZE_WRONG | I_ERR_NO_ORPHAN_ITEM)))
+ return 1;
+
+ path = btrfs_alloc_path();
+ if (!path)
+ return -ENOMEM;
+
+ trans = btrfs_start_transaction(root, 1);
+ if (IS_ERR(trans)) {
+ btrfs_free_path(path);
+ return PTR_ERR(trans);
+ }
+
+ if (rec->errors & I_ERR_DIR_ISIZE_WRONG)
+ ret = repair_inode_isize(trans, root, path, rec);
+ if (!ret && rec->errors & I_ERR_NO_ORPHAN_ITEM)
+ ret = repair_inode_orphan_item(trans, root, path, rec);
btrfs_commit_transaction(trans, root);
btrfs_free_path(path);
return ret;
@@ -2005,8 +2047,8 @@ static int check_fs_roots(struct btrfs_root *root,
btrfs_item_key_to_cpu(leaf, &key, path.slots[0]);
if (key.type == BTRFS_ROOT_ITEM_KEY &&
fs_root_objectid(key.objectid)) {
- tmp_root = btrfs_read_fs_root_no_cache(root->fs_info,
- &key);
+ key.offset = (u64)-1;
+ tmp_root = btrfs_read_fs_root(root->fs_info, &key);
if (IS_ERR(tmp_root)) {
err = 1;
goto next;
@@ -2014,7 +2056,6 @@ static int check_fs_roots(struct btrfs_root *root,
ret = check_fs_root(tmp_root, root_cache, &wc);
if (ret)
err = 1;
- btrfs_free_fs_root(tmp_root);
} else if (key.type == BTRFS_ROOT_REF_KEY ||
key.type == BTRFS_ROOT_BACKREF_KEY) {
process_root_ref(leaf, path.slots[0], &key,
@@ -2289,13 +2330,148 @@ static int record_bad_block_io(struct btrfs_fs_info *info,
return btrfs_add_corrupt_extent_record(info, &key, start, len, 0);
}
-static int check_block(struct btrfs_root *root,
+static int swap_values(struct btrfs_root *root, struct btrfs_path *path,
+ struct extent_buffer *buf, int slot)
+{
+ if (btrfs_header_level(buf)) {
+ struct btrfs_key_ptr ptr1, ptr2;
+
+ read_extent_buffer(buf, &ptr1, btrfs_node_key_ptr_offset(slot),
+ sizeof(struct btrfs_key_ptr));
+ read_extent_buffer(buf, &ptr2,
+ btrfs_node_key_ptr_offset(slot + 1),
+ sizeof(struct btrfs_key_ptr));
+ write_extent_buffer(buf, &ptr1,
+ btrfs_node_key_ptr_offset(slot + 1),
+ sizeof(struct btrfs_key_ptr));
+ write_extent_buffer(buf, &ptr2,
+ btrfs_node_key_ptr_offset(slot),
+ sizeof(struct btrfs_key_ptr));
+ if (slot == 0) {
+ struct btrfs_disk_key key;
+ btrfs_node_key(buf, &key, 0);
+ btrfs_fixup_low_keys(root, path, &key,
+ btrfs_header_level(buf) + 1);
+ }
+ } else {
+ struct btrfs_item *item1, *item2;
+ struct btrfs_key k1, k2;
+ char *item1_data, *item2_data;
+ u32 item1_offset, item2_offset, item1_size, item2_size;
+
+ item1 = btrfs_item_nr(slot);
+ item2 = btrfs_item_nr(slot + 1);
+ btrfs_item_key_to_cpu(buf, &k1, slot);
+ btrfs_item_key_to_cpu(buf, &k2, slot + 1);
+ item1_offset = btrfs_item_offset(buf, item1);
+ item2_offset = btrfs_item_offset(buf, item2);
+ item1_size = btrfs_item_size(buf, item1);
+ item2_size = btrfs_item_size(buf, item2);
+
+ item1_data = malloc(item1_size);
+ if (!item1_data)
+ return -ENOMEM;
+ item2_data = malloc(item2_size);
+ if (!item2_data) {
+ free(item2_data);
+ return -ENOMEM;
+ }
+
+ read_extent_buffer(buf, item1_data, item1_offset, item1_size);
+ read_extent_buffer(buf, item2_data, item2_offset, item2_size);
+
+ write_extent_buffer(buf, item1_data, item2_offset, item2_size);
+ write_extent_buffer(buf, item2_data, item1_offset, item1_size);
+ free(item1_data);
+ free(item2_data);
+
+ btrfs_set_item_offset(buf, item1, item2_offset);
+ btrfs_set_item_offset(buf, item2, item1_offset);
+ btrfs_set_item_size(buf, item1, item2_size);
+ btrfs_set_item_size(buf, item2, item1_size);
+
+ path->slots[0] = slot;
+ btrfs_set_item_key_unsafe(root, path, &k2);
+ path->slots[0] = slot + 1;
+ btrfs_set_item_key_unsafe(root, path, &k1);
+ }
+ return 0;
+}
+
+/*
+ * Attempt to fix basic block failures. Currently we only handle bad key
+ * orders, we will cycle through the keys and swap them if necessary.
+ */
+static int try_to_fix_bad_block(struct btrfs_trans_handle *trans,
+ struct btrfs_root *root,
+ struct extent_buffer *buf,
+ struct btrfs_disk_key *parent_key,
+ enum btrfs_tree_block_status status)
+{
+ struct btrfs_path *path;
+ struct btrfs_key k1, k2;
+ int i;
+ int ret;
+
+ if (status != BTRFS_TREE_BLOCK_BAD_KEY_ORDER)
+ return -EIO;
+
+ k1.objectid = btrfs_header_owner(buf);
+ k1.type = BTRFS_ROOT_ITEM_KEY;
+ k1.offset = (u64)-1;
+
+ root = btrfs_read_fs_root(root->fs_info, &k1);
+ if (IS_ERR(root))
+ return -EIO;
+
+ path = btrfs_alloc_path();
+ if (!path)
+ return -EIO;
+
+ path->lowest_level = btrfs_header_level(buf);
+ path->skip_check_block = 1;
+ if (btrfs_header_level(buf))
+ btrfs_node_key_to_cpu(buf, &k1, 0);
+ else
+ btrfs_item_key_to_cpu(buf, &k1, 0);
+
+ ret = btrfs_search_slot(trans, root, &k1, path, 0, 1);
+ if (ret) {
+ btrfs_free_path(path);
+ return -EIO;
+ }
+
+ buf = path->nodes[0];
+ for (i = 0; i < btrfs_header_nritems(buf) - 1; i++) {
+ if (btrfs_header_level(buf)) {
+ btrfs_node_key_to_cpu(buf, &k1, i);
+ btrfs_node_key_to_cpu(buf, &k2, i + 1);
+ } else {
+ btrfs_item_key_to_cpu(buf, &k1, i);
+ btrfs_item_key_to_cpu(buf, &k2, i + 1);
+ }
+ if (btrfs_comp_cpu_keys(&k1, &k2) < 0)
+ continue;
+ ret = swap_values(root, path, buf, i);
+ if (ret)
+ break;
+ btrfs_mark_buffer_dirty(buf);
+ i = 0;
+ }
+
+ btrfs_free_path(path);
+ return ret;
+}
+
+static int check_block(struct btrfs_trans_handle *trans,
+ struct btrfs_root *root,
struct cache_tree *extent_cache,
struct extent_buffer *buf, u64 flags)
{
struct extent_record *rec;
struct cache_extent *cache;
struct btrfs_key key;
+ enum btrfs_tree_block_status status;
int ret = 1;
int level;
@@ -2318,13 +2494,26 @@ static int check_block(struct btrfs_root *root,
rec->info_level = level;
if (btrfs_is_leaf(buf))
- ret = btrfs_check_leaf(root, &rec->parent_key, buf);
+ status = btrfs_check_leaf(root, &rec->parent_key, buf);
else
- ret = btrfs_check_node(root, &rec->parent_key, buf);
-
- if (ret) {
- fprintf(stderr, "bad block %llu\n",
- (unsigned long long)buf->start);
+ status = btrfs_check_node(root, &rec->parent_key, buf);
+
+ if (status != BTRFS_TREE_BLOCK_CLEAN) {
+ if (repair)
+ status = try_to_fix_bad_block(trans, root, buf,
+ &rec->parent_key,
+ status);
+ if (status != BTRFS_TREE_BLOCK_CLEAN) {
+ ret = -EIO;
+ fprintf(stderr, "bad block %llu\n",
+ (unsigned long long)buf->start);
+ } else {
+ /*
+ * Signal to callers we need to start the scan over
+ * again since we'll have cow'ed blocks.
+ */
+ ret = -EAGAIN;
+ }
} else {
rec->content_checked = 1;
if (flags & BTRFS_BLOCK_FLAG_FULL_BACKREF)
@@ -3622,7 +3811,8 @@ static int is_dropped_key(struct btrfs_key *key,
return 0;
}
-static int run_next_block(struct btrfs_root *root,
+static int run_next_block(struct btrfs_trans_handle *trans,
+ struct btrfs_root *root,
struct block_info *bits,
int bits_nr,
u64 *last,
@@ -3644,7 +3834,7 @@ static int run_next_block(struct btrfs_root *root,
u64 owner;
u64 flags;
u64 ptr;
- int ret;
+ int ret = 0;
int i;
int nritems;
struct btrfs_key key;
@@ -3717,7 +3907,7 @@ static int run_next_block(struct btrfs_root *root,
owner = btrfs_header_owner(buf);
}
- ret = check_block(root, extent_cache, buf, flags);
+ ret = check_block(trans, root, extent_cache, buf, flags);
if (ret)
goto out;
@@ -3803,6 +3993,23 @@ static int run_next_block(struct btrfs_root *root,
0, root->sectorsize);
continue;
}
+ if (key.type == BTRFS_ORPHAN_ITEM_KEY) {
+ struct bad_item *bad;
+
+ if (key.objectid == BTRFS_ORPHAN_OBJECTID)
+ continue;
+ if (!owner)
+ continue;
+ bad = malloc(sizeof(struct bad_item));
+ if (!bad)
+ continue;
+ INIT_LIST_HEAD(&bad->list);
+ memcpy(&bad->key, &key,
+ sizeof(struct btrfs_key));
+ bad->root_id = owner;
+ list_add_tail(&bad->list, &delete_items);
+ continue;
+ }
if (key.type != BTRFS_EXTENT_DATA_KEY)
continue;
fi = btrfs_item_ptr(buf, i,
@@ -3877,7 +4084,7 @@ static int run_next_block(struct btrfs_root *root,
found_old_backref = 1;
out:
free_extent_buffer(buf);
- return 0;
+ return ret;
}
static int add_root_to_pending(struct extent_buffer *buf,
@@ -5571,9 +5778,9 @@ again:
}
btrfs_release_path(&path);
while (1) {
- ret = run_next_block(root, bits, bits_nr, &last, &pending,
- &seen, &reada, &nodes, &extent_cache,
- &chunk_cache, &dev_cache,
+ ret = run_next_block(trans, root, bits, bits_nr, &last,
+ &pending, &seen, &reada, &nodes,
+ &extent_cache, &chunk_cache, &dev_cache,
&block_group_cache, &dev_extent_cache,
NULL);
if (ret != 0)
@@ -5597,7 +5804,7 @@ again:
add_root_to_pending(buf, &extent_cache, &pending,
&seen, &nodes, &rec->found_key);
while (1) {
- ret = run_next_block(root, bits, bits_nr, &last,
+ ret = run_next_block(trans, root, bits, bits_nr, &last,
&pending, &seen, &reada,
&nodes, &extent_cache,
&chunk_cache, &dev_cache,
@@ -5612,7 +5819,8 @@ again:
free(rec);
}
- ret = check_extent_refs(trans, root, &extent_cache);
+ if (ret >= 0)
+ ret = check_extent_refs(trans, root, &extent_cache);
if (ret == -EAGAIN) {
ret = btrfs_commit_transaction(trans, root);
if (ret)
@@ -6096,6 +6304,49 @@ static int recow_extent_buffer(struct btrfs_root *root, struct extent_buffer *eb
return ret;
}
+static int delete_bad_item(struct btrfs_root *root, struct bad_item *bad)
+{
+ struct btrfs_path *path;
+ struct btrfs_trans_handle *trans;
+ struct btrfs_key key;
+ int ret;
+
+ printf("Deleting bad item [%llu,%u,%llu]\n", bad->key.objectid,
+ bad->key.type, bad->key.offset);
+ key.objectid = bad->root_id;
+ key.type = BTRFS_ROOT_ITEM_KEY;
+ key.offset = (u64)-1;
+
+ root = btrfs_read_fs_root(root->fs_info, &key);
+ if (IS_ERR(root)) {
+ fprintf(stderr, "Couldn't find owner root %llu\n",
+ key.objectid);
+ return PTR_ERR(root);
+ }
+
+ path = btrfs_alloc_path();
+ if (!path)
+ return -ENOMEM;
+
+ trans = btrfs_start_transaction(root, 1);
+ if (IS_ERR(trans)) {
+ btrfs_free_path(path);
+ return PTR_ERR(trans);
+ }
+
+ ret = btrfs_search_slot(trans, root, &bad->key, path, -1, 1);
+ if (ret) {
+ if (ret > 0)
+ ret = 0;
+ goto out;
+ }
+ ret = btrfs_del_item(trans, root, path);
+out:
+ btrfs_commit_transaction(trans, root);
+ btrfs_free_path(path);
+ return ret;
+}
+
static struct option long_options[] = {
{ "super", 1, NULL, 's' },
{ "repair", 0, NULL, 0 },
@@ -6274,6 +6525,16 @@ int cmd_check(int argc, char **argv)
break;
}
+ while (!list_empty(&delete_items)) {
+ struct bad_item *bad;
+
+ bad = list_first_entry(&delete_items, struct bad_item, list);
+ list_del_init(&bad->list);
+ if (repair)
+ ret = delete_bad_item(root, bad);
+ free(bad);
+ }
+
if (!list_empty(&root->fs_info->recow_ebs)) {
fprintf(stderr, "Transid errors in file system\n");
ret = 1;
diff --git a/ctree.c b/ctree.c
index 2d4f773b..9e5b30f3 100644
--- a/ctree.c
+++ b/ctree.c
@@ -371,31 +371,35 @@ int btrfs_cow_block(struct btrfs_trans_handle *trans,
return ret;
}
-/*
- * compare two keys in a memcmp fashion
- */
-static int btrfs_comp_keys(struct btrfs_disk_key *disk, struct btrfs_key *k2)
+int btrfs_comp_cpu_keys(struct btrfs_key *k1, struct btrfs_key *k2)
{
- struct btrfs_key k1;
-
- btrfs_disk_key_to_cpu(&k1, disk);
-
- if (k1.objectid > k2->objectid)
+ if (k1->objectid > k2->objectid)
return 1;
- if (k1.objectid < k2->objectid)
+ if (k1->objectid < k2->objectid)
return -1;
- if (k1.type > k2->type)
+ if (k1->type > k2->type)
return 1;
- if (k1.type < k2->type)
+ if (k1->type < k2->type)
return -1;
- if (k1.offset > k2->offset)
+ if (k1->offset > k2->offset)
return 1;
- if (k1.offset < k2->offset)
+ if (k1->offset < k2->offset)
return -1;
return 0;
}
/*
+ * compare two keys in a memcmp fashion
+ */
+static int btrfs_comp_keys(struct btrfs_disk_key *disk, struct btrfs_key *k2)
+{
+ struct btrfs_key k1;
+
+ btrfs_disk_key_to_cpu(&k1, disk);
+ return btrfs_comp_cpu_keys(&k1, k2);
+}
+
+/*
* The leaf data grows from end-to-front in the node.
* this returns the address of the start of the last item,
* which is the stop of the leaf data stack
@@ -409,30 +413,33 @@ static inline unsigned int leaf_data_end(struct btrfs_root *root,
return btrfs_item_offset_nr(leaf, nr - 1);
}
-int btrfs_check_node(struct btrfs_root *root,
- struct btrfs_disk_key *parent_key,
- struct extent_buffer *buf)
+enum btrfs_tree_block_status
+btrfs_check_node(struct btrfs_root *root, struct btrfs_disk_key *parent_key,
+ struct extent_buffer *buf)
{
int i;
struct btrfs_key cpukey;
struct btrfs_disk_key key;
u32 nritems = btrfs_header_nritems(buf);
+ enum btrfs_tree_block_status ret = BTRFS_TREE_BLOCK_INVALID_NRITEMS;
if (nritems == 0 || nritems > BTRFS_NODEPTRS_PER_BLOCK(root))
goto fail;
+ ret = BTRFS_TREE_BLOCK_INVALID_PARENT_KEY;
if (parent_key && parent_key->type) {
btrfs_node_key(buf, &key, 0);
if (memcmp(parent_key, &key, sizeof(key)))
goto fail;
}
+ ret = BTRFS_TREE_BLOCK_BAD_KEY_ORDER;
for (i = 0; nritems > 1 && i < nritems - 2; i++) {
btrfs_node_key(buf, &key, i);
btrfs_node_key_to_cpu(buf, &cpukey, i + 1);
if (btrfs_comp_keys(&key, &cpukey) >= 0)
goto fail;
}
- return 0;
+ return BTRFS_TREE_BLOCK_CLEAN;
fail:
if (btrfs_header_owner(buf) == BTRFS_EXTENT_TREE_OBJECTID) {
if (parent_key)
@@ -443,17 +450,18 @@ fail:
buf->start, buf->len,
btrfs_header_level(buf));
}
- return -EIO;
+ return ret;
}
-int btrfs_check_leaf(struct btrfs_root *root,
- struct btrfs_disk_key *parent_key,
- struct extent_buffer *buf)
+enum btrfs_tree_block_status
+btrfs_check_leaf(struct btrfs_root *root, struct btrfs_disk_key *parent_key,
+ struct extent_buffer *buf)
{
int i;
struct btrfs_key cpukey;
struct btrfs_disk_key key;
u32 nritems = btrfs_header_nritems(buf);
+ enum btrfs_tree_block_status ret = BTRFS_TREE_BLOCK_INVALID_NRITEMS;
if (nritems * sizeof(struct btrfs_item) > buf->len) {
fprintf(stderr, "invalid number of items %llu\n",
@@ -462,11 +470,13 @@ int btrfs_check_leaf(struct btrfs_root *root,
}
if (btrfs_header_level(buf) != 0) {
+ ret = BTRFS_TREE_BLOCK_INVALID_LEVEL;
fprintf(stderr, "leaf is not a leaf %llu\n",
(unsigned long long)btrfs_header_bytenr(buf));
goto fail;
}
if (btrfs_leaf_free_space(root, buf) < 0) {
+ ret = BTRFS_TREE_BLOCK_INVALID_FREE_SPACE;
fprintf(stderr, "leaf free space incorrect %llu %d\n",
(unsigned long long)btrfs_header_bytenr(buf),
btrfs_leaf_free_space(root, buf));
@@ -474,11 +484,12 @@ int btrfs_check_leaf(struct btrfs_root *root,
}
if (nritems == 0)
- return 0;
+ return BTRFS_TREE_BLOCK_CLEAN;
btrfs_item_key(buf, &key, 0);
if (parent_key && parent_key->type &&
memcmp(parent_key, &key, sizeof(key))) {
+ ret = BTRFS_TREE_BLOCK_INVALID_PARENT_KEY;
fprintf(stderr, "leaf parent key incorrect %llu\n",
(unsigned long long)btrfs_header_bytenr(buf));
goto fail;
@@ -487,11 +498,13 @@ int btrfs_check_leaf(struct btrfs_root *root,
btrfs_item_key(buf, &key, i);
btrfs_item_key_to_cpu(buf, &cpukey, i + 1);
if (btrfs_comp_keys(&key, &cpukey) >= 0) {
+ ret = BTRFS_TREE_BLOCK_BAD_KEY_ORDER;
fprintf(stderr, "bad key ordering %d %d\n", i, i+1);
goto fail;
}
if (btrfs_item_offset_nr(buf, i) !=
btrfs_item_end_nr(buf, i + 1)) {
+ ret = BTRFS_TREE_BLOCK_INVALID_OFFSETS;
fprintf(stderr, "incorrect offsets %u %u\n",
btrfs_item_offset_nr(buf, i),
btrfs_item_end_nr(buf, i + 1));
@@ -499,13 +512,14 @@ int btrfs_check_leaf(struct btrfs_root *root,
}
if (i == 0 && btrfs_item_end_nr(buf, i) !=
BTRFS_LEAF_DATA_SIZE(root)) {
+ ret = BTRFS_TREE_BLOCK_INVALID_OFFSETS;
fprintf(stderr, "bad item end %u wanted %u\n",
btrfs_item_end_nr(buf, i),
(unsigned)BTRFS_LEAF_DATA_SIZE(root));
goto fail;
}
}
- return 0;
+ return BTRFS_TREE_BLOCK_CLEAN;
fail:
if (btrfs_header_owner(buf) == BTRFS_EXTENT_TREE_OBJECTID) {
if (parent_key)
@@ -516,7 +530,7 @@ fail:
btrfs_add_corrupt_extent_record(root->fs_info, &cpukey,
buf->start, buf->len, 0);
}
- return -EIO;
+ return ret;
}
static int noinline check_block(struct btrfs_root *root,
@@ -525,15 +539,22 @@ static int noinline check_block(struct btrfs_root *root,
struct btrfs_disk_key key;
struct btrfs_disk_key *key_ptr = NULL;
struct extent_buffer *parent;
+ enum btrfs_tree_block_status ret;
+ if (path->skip_check_block)
+ return 0;
if (path->nodes[level + 1]) {
parent = path->nodes[level + 1];
btrfs_node_key(parent, &key, path->slots[level + 1]);
key_ptr = &key;
}
if (level == 0)
- return btrfs_check_leaf(root, key_ptr, path->nodes[0]);
- return btrfs_check_node(root, key_ptr, path->nodes[level]);
+ ret = btrfs_check_leaf(root, key_ptr, path->nodes[0]);
+ else
+ ret = btrfs_check_node(root, key_ptr, path->nodes[level]);
+ if (ret == BTRFS_TREE_BLOCK_CLEAN)
+ return 0;
+ return -EIO;
}
/*
@@ -1114,16 +1135,11 @@ again:
* This is used after shifting pointers to the left, so it stops
* fixing up pointers when a given leaf/node is not in slot 0 of the
* higher levels
- *
- * If this fails to write a tree block, it returns -1, but continues
- * fixing up the blocks in ram so the tree is consistent.
*/
-static int fixup_low_keys(struct btrfs_trans_handle *trans,
- struct btrfs_root *root, struct btrfs_path *path,
+void btrfs_fixup_low_keys(struct btrfs_root *root, struct btrfs_path *path,
struct btrfs_disk_key *key, int level)
{
int i;
- int ret = 0;
struct extent_buffer *t;
for (i = level; i < BTRFS_MAX_LEVEL; i++) {
@@ -1136,7 +1152,6 @@ static int fixup_low_keys(struct btrfs_trans_handle *trans,
if (tslot != 0)
break;
}
- return ret;
}
/*
@@ -1145,8 +1160,7 @@ static int fixup_low_keys(struct btrfs_trans_handle *trans,
* This function isn't completely safe. It's the caller's responsibility
* that the new key won't break the order
*/
-int btrfs_set_item_key_safe(struct btrfs_trans_handle *trans,
- struct btrfs_root *root, struct btrfs_path *path,
+int btrfs_set_item_key_safe(struct btrfs_root *root, struct btrfs_path *path,
struct btrfs_key *new_key)
{
struct btrfs_disk_key disk_key;
@@ -1170,11 +1184,33 @@ int btrfs_set_item_key_safe(struct btrfs_trans_handle *trans,
btrfs_set_item_key(eb, &disk_key, slot);
btrfs_mark_buffer_dirty(eb);
if (slot == 0)
- fixup_low_keys(trans, root, path, &disk_key, 1);
+ btrfs_fixup_low_keys(root, path, &disk_key, 1);
return 0;
}
/*
+ * update an item key without the safety checks. This is meant to be called by
+ * fsck only.
+ */
+void btrfs_set_item_key_unsafe(struct btrfs_root *root,
+ struct btrfs_path *path,
+ struct btrfs_key *new_key)
+{
+ struct btrfs_disk_key disk_key;
+ struct extent_buffer *eb;
+ int slot;
+
+ eb = path->nodes[0];
+ slot = path->slots[0];
+
+ btrfs_cpu_key_to_disk(&disk_key, new_key);
+ btrfs_set_item_key(eb, &disk_key, slot);
+ btrfs_mark_buffer_dirty(eb);
+ if (slot == 0)
+ btrfs_fixup_low_keys(root, path, &disk_key, 1);
+}
+
+/*
* try to push data from one node into the next node left in the
* tree.
*
@@ -1706,7 +1742,6 @@ static int push_leaf_left(struct btrfs_trans_handle *trans, struct btrfs_root
u32 right_nritems;
u32 nr;
int ret = 0;
- int wret;
u32 this_item_size;
u32 old_left_item_size;
@@ -1830,9 +1865,7 @@ static int push_leaf_left(struct btrfs_trans_handle *trans, struct btrfs_root
btrfs_mark_buffer_dirty(right);
btrfs_item_key(right, &disk_key, 0);
- wret = fixup_low_keys(trans, root, path, &disk_key, 1);
- if (wret)
- ret = wret;
+ btrfs_fixup_low_keys(root, path, &disk_key, 1);
/* then fixup the leaf pointer in the path */
if (path->slots[0] < push_items) {
@@ -2052,10 +2085,8 @@ again:
path->nodes[0] = right;
path->slots[0] = 0;
if (path->slots[1] == 0) {
- wret = fixup_low_keys(trans, root,
- path, &disk_key, 1);
- if (wret)
- ret = wret;
+ btrfs_fixup_low_keys(root, path,
+ &disk_key, 1);
}
}
btrfs_mark_buffer_dirty(right);
@@ -2270,7 +2301,7 @@ int btrfs_truncate_item(struct btrfs_trans_handle *trans,
btrfs_set_disk_key_offset(&disk_key, offset + size_diff);
btrfs_set_item_key(leaf, &disk_key, slot);
if (slot == 0)
- fixup_low_keys(trans, root, path, &disk_key, 1);
+ btrfs_fixup_low_keys(root, path, &disk_key, 1);
}
item = btrfs_item_nr(slot);
@@ -2448,7 +2479,7 @@ int btrfs_insert_empty_items(struct btrfs_trans_handle *trans,
ret = 0;
if (slot == 0) {
btrfs_cpu_key_to_disk(&disk_key, cpu_key);
- ret = fixup_low_keys(trans, root, path, &disk_key, 1);
+ btrfs_fixup_low_keys(root, path, &disk_key, 1);
}
if (btrfs_leaf_free_space(root, leaf) < 0) {
@@ -2499,7 +2530,6 @@ int btrfs_del_ptr(struct btrfs_trans_handle *trans, struct btrfs_root *root,
struct extent_buffer *parent = path->nodes[level];
u32 nritems;
int ret = 0;
- int wret;
nritems = btrfs_header_nritems(parent);
if (slot != nritems -1) {
@@ -2519,9 +2549,7 @@ int btrfs_del_ptr(struct btrfs_trans_handle *trans, struct btrfs_root *root,
struct btrfs_disk_key disk_key;
btrfs_node_key(parent, &disk_key, 0);
- wret = fixup_low_keys(trans, root, path, &disk_key, level + 1);
- if (wret)
- ret = wret;
+ btrfs_fixup_low_keys(root, path, &disk_key, level + 1);
}
btrfs_mark_buffer_dirty(parent);
return ret;
@@ -2621,10 +2649,7 @@ int btrfs_del_items(struct btrfs_trans_handle *trans, struct btrfs_root *root,
struct btrfs_disk_key disk_key;
btrfs_item_key(leaf, &disk_key, 0);
- wret = fixup_low_keys(trans, root, path,
- &disk_key, 1);
- if (wret)
- ret = wret;
+ btrfs_fixup_low_keys(root, path, &disk_key, 1);
}
/* delete the leaf if it is mostly empty */
diff --git a/ctree.h b/ctree.h
index 6d101848..22a5c6ae 100644
--- a/ctree.h
+++ b/ctree.h
@@ -547,6 +547,7 @@ struct btrfs_path {
unsigned int keep_locks:1;
unsigned int skip_locking:1;
unsigned int leave_spinning:1;
+ unsigned int skip_check_block:1;
};
/*
@@ -647,6 +648,16 @@ typedef enum {
BTRFS_ENCRYPTION_LAST = 1,
} btrfs_encryption_type;
+enum btrfs_tree_block_status {
+ BTRFS_TREE_BLOCK_CLEAN,
+ BTRFS_TREE_BLOCK_INVALID_NRITEMS,
+ BTRFS_TREE_BLOCK_INVALID_PARENT_KEY,
+ BTRFS_TREE_BLOCK_BAD_KEY_ORDER,
+ BTRFS_TREE_BLOCK_INVALID_LEVEL,
+ BTRFS_TREE_BLOCK_INVALID_FREE_SPACE,
+ BTRFS_TREE_BLOCK_INVALID_OFFSETS,
+};
+
struct btrfs_inode_item {
/* nfs style generation number */
__le64 generation;
@@ -2202,14 +2213,15 @@ int btrfs_record_file_extent(struct btrfs_trans_handle *trans,
u64 file_pos, u64 disk_bytenr,
u64 num_bytes);
/* ctree.c */
+int btrfs_comp_cpu_keys(struct btrfs_key *k1, struct btrfs_key *k2);
int btrfs_del_ptr(struct btrfs_trans_handle *trans, struct btrfs_root *root,
struct btrfs_path *path, int level, int slot);
-int btrfs_check_node(struct btrfs_root *root,
- struct btrfs_disk_key *parent_key,
- struct extent_buffer *buf);
-int btrfs_check_leaf(struct btrfs_root *root,
- struct btrfs_disk_key *parent_key,
- struct extent_buffer *buf);
+enum btrfs_tree_block_status
+btrfs_check_node(struct btrfs_root *root, struct btrfs_disk_key *parent_key,
+ struct extent_buffer *buf);
+enum btrfs_tree_block_status
+btrfs_check_leaf(struct btrfs_root *root, struct btrfs_disk_key *parent_key,
+ struct extent_buffer *buf);
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,
@@ -2279,9 +2291,13 @@ static inline int btrfs_insert_empty_item(struct btrfs_trans_handle *trans,
int btrfs_next_leaf(struct btrfs_root *root, struct btrfs_path *path);
int btrfs_prev_leaf(struct btrfs_root *root, struct btrfs_path *path);
int btrfs_leaf_free_space(struct btrfs_root *root, struct extent_buffer *leaf);
-int btrfs_set_item_key_safe(struct btrfs_trans_handle *trans,
- struct btrfs_root *root, struct btrfs_path *path,
+void btrfs_fixup_low_keys(struct btrfs_root *root, struct btrfs_path *path,
+ struct btrfs_disk_key *key, int level);
+int btrfs_set_item_key_safe(struct btrfs_root *root, struct btrfs_path *path,
struct btrfs_key *new_key);
+void btrfs_set_item_key_unsafe(struct btrfs_root *root,
+ struct btrfs_path *path,
+ struct btrfs_key *new_key);
/* root-item.c */
int btrfs_add_root_ref(struct btrfs_trans_handle *trans,
diff --git a/file-item.c b/file-item.c
index f53a5cab..6f3708b8 100644
--- a/file-item.c
+++ b/file-item.c
@@ -367,7 +367,7 @@ static noinline int truncate_one_csum(struct btrfs_trans_handle *trans,
BUG_ON(ret);
key->offset = end_byte;
- ret = btrfs_set_item_key_safe(trans, root, path, key);
+ ret = btrfs_set_item_key_safe(root, path, key);
BUG_ON(ret);
} else {
BUG();