summaryrefslogtreecommitdiff
path: root/check
diff options
context:
space:
mode:
authorDimitri John Ledkov <xnox@ubuntu.com>2018-07-23 14:32:01 +0100
committerDimitri John Ledkov <xnox@ubuntu.com>2018-07-23 14:32:01 +0100
commit3b9cf4c8cda0818e4d3f9892ece9f7d99de13b03 (patch)
tree59446f505b5bb3b31b1b3bb81af997dda68407c2 /check
parentf22f0302575d3a167ee550470c922de82e34342b (diff)
Diffstat (limited to 'check')
-rw-r--r--check/main.c268
-rw-r--r--check/mode-common.c147
-rw-r--r--check/mode-common.h3
-rw-r--r--check/mode-lowmem.c650
-rw-r--r--check/mode-lowmem.h2
-rw-r--r--check/mode-original.h1
6 files changed, 780 insertions, 291 deletions
diff --git a/check/main.c b/check/main.c
index c4a1801f..3190b5d4 100644
--- a/check/main.c
+++ b/check/main.c
@@ -576,6 +576,8 @@ static void print_inode_error(struct btrfs_root *root, struct inode_record *rec)
fprintf(stderr, ", link count wrong");
if (errors & I_ERR_FILE_EXTENT_ORPHAN)
fprintf(stderr, ", orphan file extent");
+ if (errors & I_ERR_ODD_INODE_FLAGS)
+ fprintf(stderr, ", odd inode flags");
fprintf(stderr, "\n");
/* Print the orphan extents if needed */
if (errors & I_ERR_FILE_EXTENT_ORPHAN)
@@ -805,6 +807,7 @@ static int process_inode_item(struct extent_buffer *eb,
{
struct inode_record *rec;
struct btrfs_inode_item *item;
+ u64 flags;
rec = active_node->current;
BUG_ON(rec->ino != key->objectid || rec->refs > 1);
@@ -822,6 +825,10 @@ static int process_inode_item(struct extent_buffer *eb,
rec->found_inode_item = 1;
if (rec->nlink == 0)
rec->errors |= I_ERR_NO_ORPHAN_ITEM;
+ flags = btrfs_inode_flags(eb, item);
+ if (S_ISLNK(rec->imode) &&
+ flags & (BTRFS_INODE_IMMUTABLE | BTRFS_INODE_APPEND))
+ rec->errors |= I_ERR_ODD_INODE_FLAGS;
maybe_free_inode_rec(&active_node->inode_cache, rec);
return 0;
}
@@ -1622,9 +1629,9 @@ static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path,
refs = nrefs->refs[*level];
ret = 0;
} else {
- ret = btrfs_lookup_extent_info(NULL, root,
- path->nodes[*level]->start,
- *level, 1, &refs, NULL);
+ ret = btrfs_lookup_extent_info(NULL, fs_info,
+ path->nodes[*level]->start,
+ *level, 1, &refs, NULL);
if (ret < 0) {
err = ret;
goto out;
@@ -1664,7 +1671,7 @@ static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path,
if (bytenr == nrefs->bytenr[*level - 1]) {
refs = nrefs->refs[*level - 1];
} else {
- ret = btrfs_lookup_extent_info(NULL, root, bytenr,
+ ret = btrfs_lookup_extent_info(NULL, fs_info, bytenr,
*level - 1, 1, &refs, NULL);
if (ret < 0) {
refs = 0;
@@ -5147,7 +5154,7 @@ static int check_cache_range(struct btrfs_root *root,
for (i = 0; i < BTRFS_SUPER_MIRROR_MAX; i++) {
bytenr = btrfs_sb_offset(i);
ret = btrfs_rmap_block(root->fs_info,
- cache->key.objectid, bytenr, 0,
+ cache->key.objectid, bytenr,
&logical, &nr, &stripe_len);
if (ret)
return ret;
@@ -5381,6 +5388,13 @@ static int check_space_cache(struct btrfs_root *root)
return error ? -EINVAL : 0;
}
+/*
+ * Check data checksum for [@bytenr, @bytenr + @num_bytes).
+ *
+ * Return <0 for fatal error (fails to read checksum/data or allocate memory).
+ * Return >0 for csum mismatch for any copy.
+ * Return 0 if everything is OK.
+ */
static int check_extent_csums(struct btrfs_root *root, u64 bytenr,
u64 num_bytes, unsigned long leaf_offset,
struct extent_buffer *eb)
@@ -5398,6 +5412,7 @@ static int check_extent_csums(struct btrfs_root *root, u64 bytenr,
int ret = 0;
int mirror;
int num_copies;
+ bool csum_mismatch = false;
if (num_bytes % fs_info->sectorsize)
return -EINVAL;
@@ -5406,47 +5421,51 @@ static int check_extent_csums(struct btrfs_root *root, u64 bytenr,
if (!data)
return -ENOMEM;
+ num_copies = btrfs_num_copies(root->fs_info, bytenr, num_bytes);
while (offset < num_bytes) {
- mirror = 0;
-again:
- read_len = num_bytes - offset;
- /* read as much space once a time */
- ret = read_extent_data(fs_info, data + offset,
- bytenr + offset, &read_len, mirror);
- if (ret)
- goto out;
- data_checked = 0;
- /* verify every 4k data's checksum */
- while (data_checked < read_len) {
- csum = ~(u32)0;
- tmp = offset + data_checked;
-
- csum = btrfs_csum_data((char *)data + tmp,
- csum, fs_info->sectorsize);
- btrfs_csum_final(csum, (u8 *)&csum);
-
- csum_offset = leaf_offset +
- tmp / fs_info->sectorsize * csum_size;
- read_extent_buffer(eb, (char *)&csum_expected,
- csum_offset, csum_size);
- /* try another mirror */
- if (csum != csum_expected) {
- fprintf(stderr, "mirror %d bytenr %llu csum %u expected csum %u\n",
+ /*
+ * Mirror 0 means 'read from any valid copy', so it's skipped.
+ * The indexes 1-N represent the n-th copy for levels with
+ * redundancy.
+ */
+ for (mirror = 1; mirror <= num_copies; mirror++) {
+ read_len = num_bytes - offset;
+ /* read as much space once a time */
+ ret = read_extent_data(fs_info, data + offset,
+ bytenr + offset, &read_len, mirror);
+ if (ret)
+ goto out;
+
+ data_checked = 0;
+ /* verify every 4k data's checksum */
+ while (data_checked < read_len) {
+ csum = ~(u32)0;
+ tmp = offset + data_checked;
+
+ csum = btrfs_csum_data((char *)data + tmp,
+ csum, fs_info->sectorsize);
+ btrfs_csum_final(csum, (u8 *)&csum);
+
+ csum_offset = leaf_offset +
+ tmp / fs_info->sectorsize * csum_size;
+ read_extent_buffer(eb, (char *)&csum_expected,
+ csum_offset, csum_size);
+ if (csum != csum_expected) {
+ csum_mismatch = true;
+ fprintf(stderr,
+ "mirror %d bytenr %llu csum %u expected csum %u\n",
mirror, bytenr + tmp,
csum, csum_expected);
- num_copies = btrfs_num_copies(root->fs_info,
- bytenr, num_bytes);
- if (mirror < num_copies - 1) {
- mirror += 1;
- goto again;
}
+ data_checked += fs_info->sectorsize;
}
- data_checked += fs_info->sectorsize;
}
offset += read_len;
}
out:
free(data);
+ if (!ret && csum_mismatch)
+ ret = 1;
return ret;
}
@@ -5660,8 +5679,14 @@ static int check_csums(struct btrfs_root *root)
leaf_offset = btrfs_item_ptr_offset(leaf, path.slots[0]);
ret = check_extent_csums(root, key.offset, data_len,
leaf_offset, leaf);
- if (ret)
+ /*
+ * Only break for fatal errors, if mismatch is found, continue
+ * checking until all extents are checked.
+ */
+ if (ret < 0)
break;
+ if (ret > 0)
+ errors++;
skip_csum_check:
if (!num_bytes) {
offset = key.offset;
@@ -5910,7 +5935,7 @@ static int run_next_block(struct btrfs_root *root,
flags = 0;
if (!init_extent_tree) {
- ret = btrfs_lookup_extent_info(NULL, root, bytenr,
+ ret = btrfs_lookup_extent_info(NULL, fs_info, bytenr,
btrfs_header_level(buf), 1, NULL,
&flags);
if (ret < 0) {
@@ -5970,7 +5995,7 @@ static int run_next_block(struct btrfs_root *root,
goto out;
if (btrfs_is_leaf(buf)) {
- btree_space_waste += btrfs_leaf_free_space(fs_info, buf);
+ btree_space_waste += btrfs_leaf_free_space(buf);
for (i = 0; i < nritems; i++) {
struct btrfs_file_extent_item *fi;
@@ -6277,10 +6302,10 @@ out:
}
static int delete_extent_records(struct btrfs_trans_handle *trans,
- struct btrfs_root *root,
struct btrfs_path *path,
u64 bytenr)
{
+ struct btrfs_fs_info *fs_info = trans->fs_info;
struct btrfs_key key;
struct btrfs_key found_key;
struct extent_buffer *leaf;
@@ -6293,8 +6318,8 @@ static int delete_extent_records(struct btrfs_trans_handle *trans,
key.offset = (u64)-1;
while (1) {
- ret = btrfs_search_slot(trans, root->fs_info->extent_root,
- &key, path, 0, 1);
+ ret = btrfs_search_slot(trans, fs_info->extent_root, &key,
+ path, 0, 1);
if (ret < 0)
break;
@@ -6336,7 +6361,7 @@ static int delete_extent_records(struct btrfs_trans_handle *trans,
"repair deleting extent record: key [%llu,%u,%llu]\n",
found_key.objectid, found_key.type, found_key.offset);
- ret = btrfs_del_item(trans, root->fs_info->extent_root, path);
+ ret = btrfs_del_item(trans, fs_info->extent_root, path);
if (ret)
break;
btrfs_release_path(path);
@@ -6344,10 +6369,10 @@ static int delete_extent_records(struct btrfs_trans_handle *trans,
if (found_key.type == BTRFS_EXTENT_ITEM_KEY ||
found_key.type == BTRFS_METADATA_ITEM_KEY) {
u64 bytes = (found_key.type == BTRFS_EXTENT_ITEM_KEY) ?
- found_key.offset : root->fs_info->nodesize;
+ found_key.offset : fs_info->nodesize;
- ret = btrfs_update_block_group(root, bytenr,
- bytes, 0, 0);
+ ret = btrfs_update_block_group(fs_info->extent_root,
+ bytenr, bytes, 0, 0);
if (ret)
break;
}
@@ -7275,8 +7300,7 @@ static int fixup_extent_refs(struct btrfs_fs_info *info,
}
/* step two, delete all the existing records */
- ret = delete_extent_records(trans, info->extent_root, &path,
- rec->start);
+ ret = delete_extent_records(trans, &path, rec->start);
if (ret < 0)
goto out;
@@ -7654,7 +7678,7 @@ repair_abort:
goto repair_abort;
}
- ret = btrfs_fix_block_accounting(trans, root);
+ ret = btrfs_fix_block_accounting(trans);
if (ret)
goto repair_abort;
ret = btrfs_commit_transaction(trans, root);
@@ -8349,94 +8373,6 @@ init:
return 0;
}
-static int pin_down_tree_blocks(struct btrfs_fs_info *fs_info,
- struct extent_buffer *eb, int tree_root)
-{
- struct extent_buffer *tmp;
- struct btrfs_root_item *ri;
- struct btrfs_key key;
- u64 bytenr;
- int level = btrfs_header_level(eb);
- int nritems;
- int ret;
- int i;
-
- /*
- * If we have pinned this block before, don't pin it again.
- * This can not only avoid forever loop with broken filesystem
- * but also give us some speedups.
- */
- if (test_range_bit(&fs_info->pinned_extents, eb->start,
- eb->start + eb->len - 1, EXTENT_DIRTY, 0))
- return 0;
-
- btrfs_pin_extent(fs_info, eb->start, eb->len);
-
- nritems = btrfs_header_nritems(eb);
- for (i = 0; i < nritems; i++) {
- if (level == 0) {
- btrfs_item_key_to_cpu(eb, &key, i);
- if (key.type != BTRFS_ROOT_ITEM_KEY)
- continue;
- /* Skip the extent root and reloc roots */
- if (key.objectid == BTRFS_EXTENT_TREE_OBJECTID ||
- key.objectid == BTRFS_TREE_RELOC_OBJECTID ||
- key.objectid == BTRFS_DATA_RELOC_TREE_OBJECTID)
- continue;
- ri = btrfs_item_ptr(eb, i, struct btrfs_root_item);
- bytenr = btrfs_disk_root_bytenr(eb, ri);
-
- /*
- * If at any point we start needing the real root we
- * will have to build a stump root for the root we are
- * in, but for now this doesn't actually use the root so
- * just pass in extent_root.
- */
- tmp = read_tree_block(fs_info, bytenr, 0);
- if (!extent_buffer_uptodate(tmp)) {
- fprintf(stderr, "Error reading root block\n");
- return -EIO;
- }
- ret = pin_down_tree_blocks(fs_info, tmp, 0);
- free_extent_buffer(tmp);
- if (ret)
- return ret;
- } else {
- bytenr = btrfs_node_blockptr(eb, i);
-
- /* If we aren't the tree root don't read the block */
- if (level == 1 && !tree_root) {
- btrfs_pin_extent(fs_info, bytenr,
- fs_info->nodesize);
- continue;
- }
-
- tmp = read_tree_block(fs_info, bytenr, 0);
- if (!extent_buffer_uptodate(tmp)) {
- fprintf(stderr, "Error reading tree block\n");
- return -EIO;
- }
- ret = pin_down_tree_blocks(fs_info, tmp, tree_root);
- free_extent_buffer(tmp);
- if (ret)
- return ret;
- }
- }
-
- return 0;
-}
-
-static int pin_metadata_blocks(struct btrfs_fs_info *fs_info)
-{
- int ret;
-
- ret = pin_down_tree_blocks(fs_info, fs_info->chunk_root->node, 0);
- if (ret)
- return ret;
-
- return pin_down_tree_blocks(fs_info, fs_info->tree_root->node, 1);
-}
-
static int reset_block_groups(struct btrfs_fs_info *fs_info)
{
struct btrfs_block_group_cache *cache;
@@ -8611,7 +8547,7 @@ out:
}
static int reinit_extent_tree(struct btrfs_trans_handle *trans,
- struct btrfs_fs_info *fs_info)
+ struct btrfs_fs_info *fs_info, bool pin)
{
u64 start = 0;
int ret;
@@ -8633,13 +8569,26 @@ static int reinit_extent_tree(struct btrfs_trans_handle *trans,
/*
* first we need to walk all of the trees except the extent tree and pin
- * down the bytes that are in use so we don't overwrite any existing
- * metadata.
+ * down/exclude the bytes that are in use so we don't overwrite any
+ * existing metadata.
+ * If pinnned, unpin will be done in the end of transaction.
+ * If excluded, cleanup will be done in check_chunks_and_extents_lowmem.
*/
- ret = pin_metadata_blocks(fs_info);
- if (ret) {
- fprintf(stderr, "error pinning down used bytes\n");
- return ret;
+again:
+ if (pin) {
+ ret = pin_metadata_blocks(fs_info);
+ if (ret) {
+ fprintf(stderr, "error pinning down used bytes\n");
+ return ret;
+ }
+ } else {
+ ret = exclude_metadata_blocks(fs_info);
+ if (ret) {
+ fprintf(stderr, "error excluding used bytes\n");
+ printf("try to pin down used bytes\n");
+ pin = true;
+ goto again;
+ }
}
/*
@@ -8684,7 +8633,7 @@ static int reinit_extent_tree(struct btrfs_trans_handle *trans,
fprintf(stderr, "Error adding block group\n");
return ret;
}
- btrfs_extent_post_op(trans, fs_info->extent_root);
+ btrfs_extent_post_op(trans);
}
ret = reset_balance(trans, fs_info);
@@ -9467,6 +9416,7 @@ int cmd_check(int argc, char **argv)
int clear_space_cache = 0;
int qgroup_report = 0;
int qgroups_repaired = 0;
+ int qgroup_report_ret;
unsigned ctree_flags = OPEN_CTREE_EXCLUSIVE;
int force = 0;
@@ -9709,7 +9659,7 @@ int cmd_check(int argc, char **argv)
ret = qgroup_verify_all(info);
err |= !!ret;
if (ret == 0)
- report_qgroups(1);
+ err |= !!report_qgroups(1);
goto close_out;
}
if (subvolid) {
@@ -9733,7 +9683,8 @@ int cmd_check(int argc, char **argv)
if (init_extent_tree) {
printf("Creating a new extent tree\n");
- ret = reinit_extent_tree(trans, info);
+ ret = reinit_extent_tree(trans, info,
+ check_mode == CHECK_MODE_ORIGINAL);
err |= !!ret;
if (ret)
goto close_out;
@@ -9841,13 +9792,19 @@ int cmd_check(int argc, char **argv)
goto out;
}
- fprintf(stderr, "checking csums\n");
+ if (check_data_csum)
+ fprintf(stderr, "checking csums against data\n");
+ else
+ fprintf(stderr,
+ "checking only csum items (without verifying data)\n");
ret = check_csums(root);
- err |= !!ret;
- if (ret) {
+ /*
+ * Data csum error is not fatal, and it may indicate more serious
+ * corruption, continue checking.
+ */
+ if (ret)
error("errors found in csum tree");
- goto out;
- }
+ err |= !!ret;
fprintf(stderr, "checking root refs\n");
/* For low memory mode, check_fs_roots_v2 handles root refs */
@@ -9894,13 +9851,14 @@ int cmd_check(int argc, char **argv)
error("failed to check quota groups");
goto out;
}
- report_qgroups(0);
+ qgroup_report_ret = report_qgroups(0);
ret = repair_qgroups(info, &qgroups_repaired);
- err |= !!ret;
- if (err) {
+ if (ret) {
error("failed to repair quota groups");
goto out;
}
+ if (qgroup_report_ret && (!qgroups_repaired || ret))
+ err |= qgroup_report_ret;
ret = 0;
}
diff --git a/check/mode-common.c b/check/mode-common.c
index e857d44d..15e2bbd1 100644
--- a/check/mode-common.c
+++ b/check/mode-common.c
@@ -379,18 +379,14 @@ int insert_inode_item(struct btrfs_trans_handle *trans,
time_t now = time(NULL);
int ret;
+ memset(&ii, 0, sizeof(ii));
btrfs_set_stack_inode_size(&ii, size);
btrfs_set_stack_inode_nbytes(&ii, nbytes);
btrfs_set_stack_inode_nlink(&ii, nlink);
btrfs_set_stack_inode_mode(&ii, mode);
btrfs_set_stack_inode_generation(&ii, trans->transid);
- btrfs_set_stack_timespec_nsec(&ii.atime, 0);
btrfs_set_stack_timespec_sec(&ii.ctime, now);
- btrfs_set_stack_timespec_nsec(&ii.ctime, 0);
btrfs_set_stack_timespec_sec(&ii.mtime, now);
- btrfs_set_stack_timespec_nsec(&ii.mtime, 0);
- btrfs_set_stack_timespec_sec(&ii.otime, 0);
- btrfs_set_stack_timespec_nsec(&ii.otime, 0);
ret = btrfs_insert_inode(trans, root, ino, &ii);
ASSERT(!ret);
@@ -605,3 +601,144 @@ void reset_cached_block_groups(struct btrfs_fs_info *fs_info)
start = cache->key.objectid + cache->key.offset;
}
}
+
+static int traverse_tree_blocks(struct btrfs_fs_info *fs_info,
+ struct extent_buffer *eb, int tree_root,
+ int pin)
+{
+ struct extent_buffer *tmp;
+ struct btrfs_root_item *ri;
+ struct btrfs_key key;
+ struct extent_io_tree *tree;
+ u64 bytenr;
+ int level = btrfs_header_level(eb);
+ int nritems;
+ int ret;
+ int i;
+ u64 end = eb->start + eb->len;
+
+ if (pin)
+ tree = &fs_info->pinned_extents;
+ else
+ tree = fs_info->excluded_extents;
+ /*
+ * If we have pinned/excluded this block before, don't do it again.
+ * This can not only avoid forever loop with broken filesystem
+ * but also give us some speedups.
+ */
+ if (test_range_bit(tree, eb->start, end - 1, EXTENT_DIRTY, 0))
+ return 0;
+
+ if (pin)
+ btrfs_pin_extent(fs_info, eb->start, eb->len);
+ else
+ set_extent_dirty(tree, eb->start, end - 1);
+
+ nritems = btrfs_header_nritems(eb);
+ for (i = 0; i < nritems; i++) {
+ if (level == 0) {
+ bool is_extent_root;
+ btrfs_item_key_to_cpu(eb, &key, i);
+ if (key.type != BTRFS_ROOT_ITEM_KEY)
+ continue;
+ /* Skip the extent root and reloc roots */
+ if (key.objectid == BTRFS_TREE_RELOC_OBJECTID ||
+ key.objectid == BTRFS_DATA_RELOC_TREE_OBJECTID)
+ continue;
+ is_extent_root =
+ key.objectid == BTRFS_EXTENT_TREE_OBJECTID;
+ /* If pin, skip the extent root */
+ if (pin && is_extent_root)
+ continue;
+ ri = btrfs_item_ptr(eb, i, struct btrfs_root_item);
+ bytenr = btrfs_disk_root_bytenr(eb, ri);
+
+ /*
+ * If at any point we start needing the real root we
+ * will have to build a stump root for the root we are
+ * in, but for now this doesn't actually use the root so
+ * just pass in extent_root.
+ */
+ tmp = read_tree_block(fs_info, bytenr, 0);
+ if (!extent_buffer_uptodate(tmp)) {
+ fprintf(stderr, "Error reading root block\n");
+ return -EIO;
+ }
+ ret = traverse_tree_blocks(fs_info, tmp, 0, pin);
+ free_extent_buffer(tmp);
+ if (ret)
+ return ret;
+ } else {
+ bytenr = btrfs_node_blockptr(eb, i);
+
+ /* If we aren't the tree root don't read the block */
+ if (level == 1 && !tree_root) {
+ btrfs_pin_extent(fs_info, bytenr,
+ fs_info->nodesize);
+ continue;
+ }
+
+ tmp = read_tree_block(fs_info, bytenr, 0);
+ if (!extent_buffer_uptodate(tmp)) {
+ fprintf(stderr, "Error reading tree block\n");
+ return -EIO;
+ }
+ ret = traverse_tree_blocks(fs_info, tmp, tree_root,
+ pin);
+ free_extent_buffer(tmp);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int pin_down_tree_blocks(struct btrfs_fs_info *fs_info,
+ struct extent_buffer *eb, int tree_root)
+{
+ return traverse_tree_blocks(fs_info, eb, tree_root, 1);
+}
+
+int pin_metadata_blocks(struct btrfs_fs_info *fs_info)
+{
+ int ret;
+
+ ret = pin_down_tree_blocks(fs_info, fs_info->chunk_root->node, 0);
+ if (ret)
+ return ret;
+
+ return pin_down_tree_blocks(fs_info, fs_info->tree_root->node, 1);
+}
+
+static int exclude_tree_blocks(struct btrfs_fs_info *fs_info,
+ struct extent_buffer *eb, int tree_root)
+{
+ return traverse_tree_blocks(fs_info, eb, tree_root, 0);
+}
+
+int exclude_metadata_blocks(struct btrfs_fs_info *fs_info)
+{
+ int ret;
+ struct extent_io_tree *excluded_extents;
+
+ excluded_extents = malloc(sizeof(*excluded_extents));
+ if (!excluded_extents)
+ return -ENOMEM;
+ extent_io_tree_init(excluded_extents);
+ fs_info->excluded_extents = excluded_extents;
+
+ ret = exclude_tree_blocks(fs_info, fs_info->chunk_root->node, 0);
+ if (ret)
+ return ret;
+ return exclude_tree_blocks(fs_info, fs_info->tree_root->node, 1);
+}
+
+void cleanup_excluded_extents(struct btrfs_fs_info *fs_info)
+{
+ if (fs_info->excluded_extents) {
+ extent_io_tree_cleanup(fs_info->excluded_extents);
+ free(fs_info->excluded_extents);
+ }
+ fs_info->excluded_extents = NULL;
+}
diff --git a/check/mode-common.h b/check/mode-common.h
index 877c1aa9..a4748578 100644
--- a/check/mode-common.h
+++ b/check/mode-common.h
@@ -98,5 +98,8 @@ void reada_walk_down(struct btrfs_root *root, struct extent_buffer *node,
int check_child_node(struct extent_buffer *parent, int slot,
struct extent_buffer *child);
void reset_cached_block_groups(struct btrfs_fs_info *fs_info);
+int pin_metadata_blocks(struct btrfs_fs_info *fs_info);
+int exclude_metadata_blocks(struct btrfs_fs_info *fs_info);
+void cleanup_excluded_extents(struct btrfs_fs_info *fs_info);
#endif
diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c
index bfe45aba..66da4531 100644
--- a/check/mode-lowmem.c
+++ b/check/mode-lowmem.c
@@ -28,6 +28,8 @@
#include "check/mode-common.h"
#include "check/mode-lowmem.h"
+static u64 last_allocated_chunk;
+
static int calc_extent_flag(struct btrfs_root *root, struct extent_buffer *eb,
u64 *flags_ret)
{
@@ -184,8 +186,8 @@ static int update_nodes_refs(struct btrfs_root *root, u64 bytenr,
if (bytenr != (u64)-1) {
/* the return value of this function seems a mistake */
- ret = btrfs_lookup_extent_info(NULL, root, bytenr,
- level, 1, &refs, &flags);
+ ret = btrfs_lookup_extent_info(NULL, root->fs_info, bytenr,
+ level, 1, &refs, &flags);
/* temporary fix */
if (ret < 0 && !check_all)
return ret;
@@ -234,17 +236,343 @@ static int update_nodes_refs(struct btrfs_root *root, u64 bytenr,
}
/*
+ * Mark all extents unfree in the block group. And set @block_group->cached
+ * according to @cache.
+ */
+static int modify_block_group_cache(struct btrfs_fs_info *fs_info,
+ struct btrfs_block_group_cache *block_group, int cache)
+{
+ struct extent_io_tree *free_space_cache = &fs_info->free_space_cache;
+ u64 start = block_group->key.objectid;
+ u64 end = start + block_group->key.offset;
+
+ if (cache && !block_group->cached) {
+ block_group->cached = 1;
+ clear_extent_dirty(free_space_cache, start, end - 1);
+ }
+
+ if (!cache && block_group->cached) {
+ block_group->cached = 0;
+ clear_extent_dirty(free_space_cache, start, end - 1);
+ }
+ return 0;
+}
+
+/*
+ * Modify block groups which have @flags unfree in free space cache.
+ *
+ * @cache: if 0, clear block groups cache state;
+ * not 0, mark blocks groups cached.
+ */
+static int modify_block_groups_cache(struct btrfs_fs_info *fs_info, u64 flags,
+ int cache)
+{
+ struct btrfs_root *root = fs_info->extent_root;
+ struct btrfs_key key;
+ struct btrfs_path path;
+ struct btrfs_block_group_cache *bg_cache;
+ struct btrfs_block_group_item *bi;
+ struct btrfs_block_group_item bg_item;
+ struct extent_buffer *eb;
+ int slot;
+ int ret;
+
+ key.objectid = 0;
+ key.type = BTRFS_BLOCK_GROUP_ITEM_KEY;
+ key.offset = 0;
+
+ btrfs_init_path(&path);
+ ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+ if (ret < 0) {
+ error("fail to search block groups due to %s", strerror(-ret));
+ goto out;
+ }
+
+ while (1) {
+ eb = path.nodes[0];
+ slot = path.slots[0];
+ btrfs_item_key_to_cpu(eb, &key, slot);
+ bg_cache = btrfs_lookup_block_group(fs_info, key.objectid);
+ if (!bg_cache) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ bi = btrfs_item_ptr(eb, slot, struct btrfs_block_group_item);
+ read_extent_buffer(eb, &bg_item, (unsigned long)bi,
+ sizeof(bg_item));
+ if (btrfs_block_group_flags(&bg_item) & flags)
+ modify_block_group_cache(fs_info, bg_cache, cache);
+
+ ret = btrfs_next_item(root, &path);
+ if (ret > 0) {
+ ret = 0;
+ goto out;
+ }
+ if (ret < 0)
+ goto out;
+ }
+
+out:
+ btrfs_release_path(&path);
+ return ret;
+}
+
+static int mark_block_groups_full(struct btrfs_fs_info *fs_info, u64 flags)
+{
+ return modify_block_groups_cache(fs_info, flags, 1);
+}
+
+static int clear_block_groups_full(struct btrfs_fs_info *fs_info, u64 flags)
+{
+ return modify_block_groups_cache(fs_info, flags, 0);
+}
+
+static int create_chunk_and_block_group(struct btrfs_fs_info *fs_info,
+ u64 flags, u64 *start, u64 *nbytes)
+{
+ struct btrfs_trans_handle *trans;
+ struct btrfs_root *root = fs_info->extent_root;
+ int ret;
+
+ if ((flags & BTRFS_BLOCK_GROUP_TYPE_MASK) == 0)
+ return -EINVAL;
+
+ trans = btrfs_start_transaction(root, 1);
+ if (IS_ERR(trans)) {
+ ret = PTR_ERR(trans);
+ error("error starting transaction %s", strerror(-ret));
+ return ret;
+ }
+ ret = btrfs_alloc_chunk(trans, fs_info, start, nbytes, flags);
+ if (ret) {
+ error("fail to allocate new chunk %s", strerror(-ret));
+ goto out;
+ }
+ ret = btrfs_make_block_group(trans, fs_info, 0, flags, *start,
+ *nbytes);
+ if (ret) {
+ error("fail to make block group for chunk %llu %llu %s",
+ *start, *nbytes, strerror(-ret));
+ goto out;
+ }
+out:
+ btrfs_commit_transaction(trans, root);
+ return ret;
+}
+
+static int force_cow_in_new_chunk(struct btrfs_fs_info *fs_info,
+ u64 *start_ret)
+{
+ struct btrfs_block_group_cache *bg;
+ u64 start;
+ u64 nbytes;
+ u64 alloc_profile;
+ u64 flags;
+ int ret;
+
+ alloc_profile = (fs_info->avail_metadata_alloc_bits &
+ fs_info->metadata_alloc_profile);
+ flags = BTRFS_BLOCK_GROUP_METADATA | alloc_profile;
+ if (btrfs_fs_incompat(fs_info, MIXED_GROUPS))
+ flags |= BTRFS_BLOCK_GROUP_DATA;
+
+ ret = create_chunk_and_block_group(fs_info, flags, &start, &nbytes);
+ if (ret)
+ goto err;
+ printf("Created new chunk [%llu %llu]\n", start, nbytes);
+
+ flags = BTRFS_BLOCK_GROUP_METADATA;
+ /* Mark all metadata block groups cached and full in free space*/
+ ret = mark_block_groups_full(fs_info, flags);
+ if (ret)
+ goto clear_bgs_full;
+
+ bg = btrfs_lookup_block_group(fs_info, start);
+ if (!bg) {
+ ret = -ENOENT;
+ error("fail to look up block group %llu %llu", start, nbytes);
+ goto clear_bgs_full;
+ }
+
+ /* Clear block group cache just allocated */
+ ret = modify_block_group_cache(fs_info, bg, 0);
+ if (ret)
+ goto clear_bgs_full;
+ if (start_ret)
+ *start_ret = start;
+ return 0;
+
+clear_bgs_full:
+ clear_block_groups_full(fs_info, flags);
+err:
+ return ret;
+}
+
+/*
+ * Returns 0 means not almost full.
+ * Returns >0 means almost full.
+ * Returns <0 means fatal error.
+ */
+static int is_chunk_almost_full(struct btrfs_fs_info *fs_info, u64 start)
+{
+ struct btrfs_path path;
+ struct btrfs_key key;
+ struct btrfs_root *root = fs_info->extent_root;
+ struct btrfs_block_group_item *bi;
+ struct btrfs_block_group_item bg_item;
+ struct extent_buffer *eb;
+ u64 used;
+ u64 total;
+ u64 min_free;
+ int ret;
+ int slot;
+
+ key.objectid = start;
+ key.type = BTRFS_BLOCK_GROUP_ITEM_KEY;
+ key.offset = (u64)-1;
+
+ btrfs_init_path(&path);
+ ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+ if (!ret)
+ ret = -EIO;
+ if (ret < 0)
+ goto out;
+ ret = btrfs_previous_item(root, &path, start,
+ BTRFS_BLOCK_GROUP_ITEM_KEY);
+ if (ret) {
+ error("failed to find block group %llu", start);
+ ret = -ENOENT;
+ goto out;
+ }
+
+ eb = path.nodes[0];
+ slot = path.slots[0];
+ btrfs_item_key_to_cpu(eb, &key, slot);
+ if (key.objectid != start) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ total = key.offset;
+ bi = btrfs_item_ptr(eb, slot, struct btrfs_block_group_item);
+ read_extent_buffer(eb, &bg_item, (unsigned long)bi, sizeof(bg_item));
+ used = btrfs_block_group_used(&bg_item);
+
+ /*
+ * if the free space in the chunk is less than %10 of total,
+ * or not not enough for CoW once, we think the chunk is almost full.
+ */
+ min_free = max_t(u64, (BTRFS_MAX_LEVEL + 1) * fs_info->nodesize,
+ div_factor(total, 1));
+
+ if ((total - used) > min_free)
+ ret = 0;
+ else
+ ret = 1;
+out:
+ btrfs_release_path(&path);
+ return ret;
+}
+
+/*
+ * Returns <0 for error.
+ * Returns 0 for success.
+ */
+static int try_to_force_cow_in_new_chunk(struct btrfs_fs_info *fs_info,
+ u64 old_start, u64 *new_start)
+{
+ int ret;
+
+ if (old_start) {
+ ret = is_chunk_almost_full(fs_info, old_start);
+ if (ret <= 0)
+ return ret;
+ }
+ ret = force_cow_in_new_chunk(fs_info, new_start);
+ return ret;
+}
+
+static int avoid_extents_overwrite(struct btrfs_fs_info *fs_info)
+{
+ int ret;
+ int mixed = btrfs_fs_incompat(fs_info, MIXED_GROUPS);
+
+ if (fs_info->excluded_extents)
+ return 0;
+
+ if (last_allocated_chunk != (u64)-1) {
+ ret = try_to_force_cow_in_new_chunk(fs_info,
+ last_allocated_chunk, &last_allocated_chunk);
+ if (!ret)
+ goto out;
+ /*
+ * If failed, do not try to allocate chunk again in
+ * next call.
+ * If there is no space left to allocate, try to exclude all
+ * metadata blocks. Mixed filesystem is unsupported.
+ */
+ last_allocated_chunk = (u64)-1;
+ if (ret != -ENOSPC || mixed)
+ goto out;
+ }
+
+ printf(
+ "Try to exclude all metadata blcoks and extents, it may be slow\n");
+ ret = exclude_metadata_blocks(fs_info);
+out:
+ if (ret)
+ error("failed to avoid extents overwrite %s", strerror(-ret));
+ return ret;
+}
+
+static int end_avoid_extents_overwrite(struct btrfs_fs_info *fs_info)
+{
+ int ret = 0;
+
+ cleanup_excluded_extents(fs_info);
+ if (last_allocated_chunk)
+ ret = clear_block_groups_full(fs_info,
+ BTRFS_BLOCK_GROUP_METADATA);
+ return ret;
+}
+
+/*
+ * Wrapper function for btrfs_fix_block_accounting().
+ *
+ * Returns 0 on success.
+ * Returns != 0 on error.
+ */
+static int repair_block_accounting(struct btrfs_fs_info *fs_info)
+{
+ struct btrfs_trans_handle *trans = NULL;
+ struct btrfs_root *root = fs_info->extent_root;
+ int ret;
+
+ trans = btrfs_start_transaction(root, 1);
+ if (IS_ERR(trans)) {
+ ret = PTR_ERR(trans);
+ error("fail to start transaction %s", strerror(-ret));
+ return ret;
+ }
+
+ ret = btrfs_fix_block_accounting(trans);
+ btrfs_commit_transaction(trans, root);
+ return ret;
+}
+
+/*
* This function only handles BACKREF_MISSING,
* If corresponding extent item exists, increase the ref, else insert an extent
* item and backref.
*
* Returns error bits after repair.
*/
-static int repair_tree_block_ref(struct btrfs_trans_handle *trans,
- struct btrfs_root *root,
+static int repair_tree_block_ref(struct btrfs_root *root,
struct extent_buffer *node,
struct node_refs *nrefs, int level, int err)
{
+ struct btrfs_trans_handle *trans = NULL;
struct btrfs_fs_info *fs_info = root->fs_info;
struct btrfs_root *extent_root = fs_info->extent_root;
struct btrfs_path path;
@@ -294,6 +622,16 @@ static int repair_tree_block_ref(struct btrfs_trans_handle *trans,
if (nrefs->full_backref[level] != 0)
flags |= BTRFS_BLOCK_FLAG_FULL_BACKREF;
+ ret = avoid_extents_overwrite(root->fs_info);
+ if (ret)
+ goto out;
+ trans = btrfs_start_transaction(extent_root, 1);
+ if (IS_ERR(trans)) {
+ ret = PTR_ERR(trans);
+ trans = NULL;
+ error("fail to start transaction %s", strerror(-ret));
+ goto out;
+ }
/* insert an extent item */
if (insert_extent) {
struct btrfs_disk_key copy_key;
@@ -359,6 +697,8 @@ static int repair_tree_block_ref(struct btrfs_trans_handle *trans,
nrefs->refs[level]++;
out:
+ if (trans)
+ btrfs_commit_transaction(trans, extent_root);
btrfs_release_path(&path);
if (ret) {
error(
@@ -390,7 +730,7 @@ static void account_bytes(struct btrfs_root *root, struct btrfs_path *path,
total_extent_tree_bytes += eb->len;
if (level == 0) {
- btree_space_waste += btrfs_leaf_free_space(root->fs_info, eb);
+ btree_space_waste += btrfs_leaf_free_space(eb);
} else {
free_nrs = (BTRFS_NODEPTRS_PER_BLOCK(root->fs_info) -
btrfs_header_nritems(eb));
@@ -635,6 +975,10 @@ int repair_ternary_lowmem(struct btrfs_root *root, u64 dir_ino, u64 ino,
goto out;
}
if (stage == 1) {
+ ret = btrfs_unlink(trans, root, ino, dir_ino, index, name,
+ name_len, 0);
+ if (ret)
+ goto out;
ret = btrfs_add_link(trans, root, ino, dir_ino, name, name_len,
filetype, &index, 1, 1);
goto out;
@@ -921,14 +1265,13 @@ next:
* @namelen: the length of name in the INODE_REF/INODE_EXTREF
* @index_ret: the index in the INODE_REF/INODE_EXTREF,
* value (64)-1 means do not check index
- * @ext_ref: the EXTENDED_IREF feature
*
* Return 0 if no error occurred.
* Return >0 for error bitmap
*/
static int find_inode_ref(struct btrfs_root *root, struct btrfs_key *key,
- char *name, int namelen, u64 *index_ret,
- unsigned int ext_ref)
+ char *name, int namelen, u64 *index_ret)
+
{
struct btrfs_path path;
struct btrfs_inode_ref *ref;
@@ -1001,8 +1344,9 @@ next_ref:
}
extref:
+
/* Skip if not support EXTENDED_IREF feature */
- if (!ext_ref)
+ if (!btrfs_fs_incompat(root->fs_info, EXTENDED_IREF))
goto out;
btrfs_release_path(&path);
@@ -1188,14 +1532,12 @@ static void print_dir_item_err(struct btrfs_root *root, struct btrfs_key *key,
* @key: the key of the INODE_REF/INODE_EXTREF
* @path: the path
* @size: the st_size of the INODE_ITEM
- * @ext_ref: the EXTENDED_IREF feature
*
* Return 0 if no error occurred.
* Return DIR_COUNT_AGAIN if the isize of the inode should be recalculated.
*/
static int check_dir_item(struct btrfs_root *root, struct btrfs_key *di_key,
- struct btrfs_path *path, u64 *size,
- unsigned int ext_ref)
+ struct btrfs_path *path, u64 *size)
{
struct btrfs_dir_item *di;
struct btrfs_inode_item *ii;
@@ -1217,14 +1559,6 @@ static int check_dir_item(struct btrfs_root *root, struct btrfs_key *di_key,
int tmp_err;
int need_research = 0;
- /*
- * For DIR_ITEM set index to (u64)-1, so that find_inode_ref
- * ignore index check.
- */
- if (di_key->type == BTRFS_DIR_INDEX_KEY)
- index = di_key->offset;
- else
- index = (u64)-1;
begin:
err = 0;
cur = 0;
@@ -1254,6 +1588,15 @@ begin:
memset(namebuf, 0, sizeof(namebuf) / sizeof(*namebuf));
while (cur < total) {
+ /*
+ * For DIR_ITEM set index to (u64)-1, so that find_inode_ref
+ * ignore index check.
+ */
+ if (di_key->type == BTRFS_DIR_INDEX_KEY)
+ index = di_key->offset;
+ else
+ index = (u64)-1;
+
data_len = btrfs_dir_data_len(node, di);
tmp_err = 0;
if (data_len)
@@ -1311,8 +1654,7 @@ begin:
key.objectid = location.objectid;
key.type = BTRFS_INODE_REF_KEY;
key.offset = di_key->objectid;
- tmp_err |= find_inode_ref(root, &key, namebuf, len,
- &index, ext_ref);
+ tmp_err |= find_inode_ref(root, &key, namebuf, len, &index);
/* check relative INDEX/ITEM */
key.objectid = di_key->objectid;
@@ -1921,13 +2263,10 @@ static bool has_orphan_item(struct btrfs_root *root, u64 ino)
* 2. check inode ref/extref
* 3. check dir item/index
*
- * @ext_ref: the EXTENDED_IREF feature
- *
* Return 0 if no error occurred.
* Return >0 for error or hit the traversal is done(by error bitmap)
*/
-static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path,
- unsigned int ext_ref)
+static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path)
{
struct extent_buffer *node;
struct btrfs_inode_item *ii;
@@ -1935,6 +2274,7 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path,
struct btrfs_key last_key;
u64 inode_id;
u32 mode;
+ u64 flags;
u64 nlink;
u64 nbytes;
u64 isize;
@@ -1968,10 +2308,19 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path,
isize = btrfs_inode_size(node, ii);
nbytes = btrfs_inode_nbytes(node, ii);
mode = btrfs_inode_mode(node, ii);
+ flags = btrfs_inode_flags(node, ii);
dir = imode_to_type(mode) == BTRFS_FT_DIR;
nlink = btrfs_inode_nlink(node, ii);
nodatasum = btrfs_inode_flags(node, ii) & BTRFS_INODE_NODATASUM;
+ if (S_ISLNK(mode) &&
+ flags & (BTRFS_INODE_IMMUTABLE | BTRFS_INODE_APPEND)) {
+ err |= INODE_FLAGS_ERROR;
+ error(
+"symlinks must never have immutable/append flags set, root %llu inode item %llu flags %llu may be corrupted",
+ root->objectid, inode_id, flags);
+ }
+
while (1) {
btrfs_item_key_to_cpu(path->nodes[0], &last_key, path->slots[0]);
ret = btrfs_next_item(root, path);
@@ -1996,6 +2345,9 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path,
err |= ret;
break;
case BTRFS_INODE_EXTREF_KEY:
+ {
+ bool ext_ref = btrfs_fs_incompat(root->fs_info,
+ EXTENDED_IREF);
if (key.type == BTRFS_INODE_EXTREF_KEY && !ext_ref)
warning("root %llu EXTREF[%llu %llu] isn't supported",
root->objectid, key.objectid,
@@ -2004,6 +2356,7 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path,
mode);
err |= ret;
break;
+ }
case BTRFS_DIR_ITEM_KEY:
case BTRFS_DIR_INDEX_KEY:
if (!dir) {
@@ -2012,7 +2365,7 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path,
imode_to_type(mode), key.objectid,
key.offset);
}
- ret = check_dir_item(root, &key, path, &size, ext_ref);
+ ret = check_dir_item(root, &key, path, &size);
err |= ret;
break;
case BTRFS_EXTENT_DATA_KEY:
@@ -2143,7 +2496,7 @@ out:
* Returns 0 No errors found
*/
static int process_one_leaf(struct btrfs_root *root, struct btrfs_path *path,
- struct node_refs *nrefs, int *level, int ext_ref)
+ struct node_refs *nrefs, int *level)
{
struct extent_buffer *cur = path->nodes[0];
struct btrfs_key key;
@@ -2174,7 +2527,7 @@ static int process_one_leaf(struct btrfs_root *root, struct btrfs_path *path,
path->slots[0] = i;
again:
- err |= check_inode_item(root, path, ext_ref);
+ err |= check_inode_item(root, path);
/* modify cur since check_inode_item may change path */
cur = path->nodes[0];
@@ -2491,12 +2844,12 @@ out:
*
* Returns error bits after reapir.
*/
-static int repair_extent_data_item(struct btrfs_trans_handle *trans,
- struct btrfs_root *root,
+static int repair_extent_data_item(struct btrfs_root *root,
struct btrfs_path *pathp,
struct node_refs *nrefs,
int err)
{
+ struct btrfs_trans_handle *trans = NULL;
struct btrfs_file_extent_item *fi;
struct btrfs_key fi_key;
struct btrfs_key key;
@@ -2513,6 +2866,7 @@ static int repair_extent_data_item(struct btrfs_trans_handle *trans,
u64 file_offset;
int generation;
int slot;
+ int need_insert = 0;
int ret = 0;
eb = pathp->nodes[0];
@@ -2531,6 +2885,11 @@ static int repair_extent_data_item(struct btrfs_trans_handle *trans,
extent_offset = btrfs_file_extent_offset(eb, fi);
offset = file_offset - extent_offset;
+ if (nrefs->full_backref[0])
+ parent = btrfs_header_bytenr(eb);
+ else
+ parent = 0;
+
/* now repair only adds backref */
if ((err & BACKREF_MISSING) == 0)
return err;
@@ -2546,9 +2905,20 @@ static int repair_extent_data_item(struct btrfs_trans_handle *trans,
ret = -EIO;
goto out;
}
+ need_insert = ret;
+ ret = avoid_extents_overwrite(root->fs_info);
+ if (ret)
+ goto out;
+ trans = btrfs_start_transaction(root, 1);
+ if (IS_ERR(trans)) {
+ ret = PTR_ERR(trans);
+ trans = NULL;
+ error("fail to start transaction %s", strerror(-ret));
+ goto out;
+ }
/* insert an extent item */
- if (ret > 0) {
+ if (need_insert) {
key.objectid = disk_bytenr;
key.type = BTRFS_EXTENT_ITEM_KEY;
key.offset = num_bytes;
@@ -2572,11 +2942,6 @@ static int repair_extent_data_item(struct btrfs_trans_handle *trans,
btrfs_release_path(&path);
}
- if (nrefs->full_backref[0])
- parent = btrfs_header_bytenr(eb);
- else
- parent = 0;
-
ret = btrfs_inc_extent_ref(trans, root, disk_bytenr, num_bytes, parent,
root->objectid,
parent ? BTRFS_FIRST_FREE_OBJECTID : fi_key.objectid,
@@ -2593,6 +2958,9 @@ static int repair_extent_data_item(struct btrfs_trans_handle *trans,
err &= ~BACKREF_MISSING;
out:
+ if (trans)
+ btrfs_commit_transaction(trans, root);
+ btrfs_release_path(&path);
if (ret)
error("can't repair root %llu extent data item[%llu %llu]",
root->objectid, disk_bytenr, num_bytes);
@@ -3326,40 +3694,55 @@ out:
* means error after repair
* Returns 0 nothing happened
*/
-static int repair_extent_item(struct btrfs_trans_handle *trans,
- struct btrfs_root *root, struct btrfs_path *path,
+static int repair_extent_item(struct btrfs_root *root, struct btrfs_path *path,
u64 bytenr, u64 num_bytes, u64 parent, u64 root_objectid,
u64 owner, u64 offset, int err)
{
+ struct btrfs_trans_handle *trans;
+ struct btrfs_root *extent_root = root->fs_info->extent_root;
struct btrfs_key old_key;
int freed = 0;
int ret;
btrfs_item_key_to_cpu(path->nodes[0], &old_key, path->slots[0]);
- if (err & (REFERENCER_MISSING | REFERENCER_MISMATCH)) {
- /* delete the backref */
- ret = btrfs_free_extent(trans, root->fs_info->fs_root, bytenr,
- num_bytes, parent, root_objectid, owner, offset);
- if (!ret) {
- freed = 1;
- err &= ~REFERENCER_MISSING;
- printf("Delete backref in extent [%llu %llu]\n",
- bytenr, num_bytes);
- } else {
- error("fail to delete backref in extent [%llu %llu]",
- bytenr, num_bytes);
- }
+ if ((err & (REFERENCER_MISSING | REFERENCER_MISMATCH)) == 0)
+ return err;
+
+ ret = avoid_extents_overwrite(root->fs_info);
+ if (ret)
+ return err;
+
+ trans = btrfs_start_transaction(extent_root, 1);
+ if (IS_ERR(trans)) {
+ ret = PTR_ERR(trans);
+ error("fail to start transaction %s", strerror(-ret));
+ /* nothing happened */
+ ret = 0;
+ goto out;
+ }
+ /* delete the backref */
+ ret = btrfs_free_extent(trans, root->fs_info->fs_root, bytenr,
+ num_bytes, parent, root_objectid, owner, offset);
+ if (!ret) {
+ freed = 1;
+ err &= ~REFERENCER_MISSING;
+ printf("Delete backref in extent [%llu %llu]\n",
+ bytenr, num_bytes);
+ } else {
+ error("fail to delete backref in extent [%llu %llu]",
+ bytenr, num_bytes);
}
+ btrfs_commit_transaction(trans, extent_root);
/* btrfs_free_extent may delete the extent */
btrfs_release_path(path);
ret = btrfs_search_slot(NULL, root, &old_key, path, 0, 0);
-
if (ret)
ret = -ENOENT;
else if (freed)
ret = err;
+out:
return ret;
}
@@ -3369,8 +3752,7 @@ static int repair_extent_item(struct btrfs_trans_handle *trans,
*
* Since we don't use extent_record anymore, introduce new error bit
*/
-static int check_extent_item(struct btrfs_trans_handle *trans,
- struct btrfs_fs_info *fs_info,
+static int check_extent_item(struct btrfs_fs_info *fs_info,
struct btrfs_path *path)
{
struct btrfs_extent_item *ei;
@@ -3501,7 +3883,7 @@ next:
}
if (err && repair) {
- ret = repair_extent_item(trans, fs_info->extent_root, path,
+ ret = repair_extent_item(fs_info->extent_root, path,
key.objectid, num_bytes, parent, root_objectid,
owner, owner_offset, ret);
if (ret < 0)
@@ -3764,13 +4146,14 @@ out:
*
* Returns error after repair.
*/
-static int repair_chunk_item(struct btrfs_trans_handle *trans,
- struct btrfs_root *chunk_root,
+static int repair_chunk_item(struct btrfs_root *chunk_root,
struct btrfs_path *path, int err)
{
struct btrfs_chunk *chunk;
struct btrfs_key chunk_key;
struct extent_buffer *eb = path->nodes[0];
+ struct btrfs_root *extent_root = chunk_root->fs_info->extent_root;
+ struct btrfs_trans_handle *trans;
u64 length;
int slot = path->slots[0];
u64 type;
@@ -3783,31 +4166,55 @@ static int repair_chunk_item(struct btrfs_trans_handle *trans,
type = btrfs_chunk_type(path->nodes[0], chunk);
length = btrfs_chunk_length(eb, chunk);
- if (err & REFERENCER_MISSING) {
- ret = btrfs_make_block_group(trans, chunk_root->fs_info, 0,
- type, chunk_key.offset, length);
- if (ret) {
- error("fail to add block group item[%llu %llu]",
- chunk_key.offset, length);
- goto out;
- } else {
- err &= ~REFERENCER_MISSING;
- printf("Added block group item[%llu %llu]\n",
- chunk_key.offset, length);
- }
+ /* now repair only adds block group */
+ if ((err & REFERENCER_MISSING) == 0)
+ return err;
+
+ ret = avoid_extents_overwrite(chunk_root->fs_info);
+ if (ret)
+ return ret;
+
+ trans = btrfs_start_transaction(extent_root, 1);
+ if (IS_ERR(trans)) {
+ ret = PTR_ERR(trans);
+ error("fail to start transaction %s", strerror(-ret));
+ return ret;
}
-out:
+ ret = btrfs_make_block_group(trans, chunk_root->fs_info, 0, type,
+ chunk_key.offset, length);
+ if (ret) {
+ error("fail to add block group item [%llu %llu]",
+ chunk_key.offset, length);
+ } else {
+ err &= ~REFERENCER_MISSING;
+ printf("Added block group item[%llu %llu]\n", chunk_key.offset,
+ length);
+ }
+
+ btrfs_commit_transaction(trans, extent_root);
+ if (ret)
+ error("fail to repair item(s) related to chunk item [%llu %llu]",
+ chunk_key.objectid, chunk_key.offset);
return err;
}
-static int delete_extent_tree_item(struct btrfs_trans_handle *trans,
- struct btrfs_root *root,
+static int delete_extent_tree_item(struct btrfs_root *root,
struct btrfs_path *path)
{
struct btrfs_key key;
+ struct btrfs_trans_handle *trans;
int ret = 0;
+ ret = avoid_extents_overwrite(root->fs_info);
+ if (ret)
+ return ret;
+ trans = btrfs_start_transaction(root, 1);
+ if (IS_ERR(trans)) {
+ ret = PTR_ERR(trans);
+ error("fail to start transaction %s", strerror(-ret));
+ goto out;
+ }
btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
btrfs_release_path(path);
ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
@@ -3825,6 +4232,7 @@ static int delete_extent_tree_item(struct btrfs_trans_handle *trans,
else
path->slots[0]--;
out:
+ btrfs_commit_transaction(trans, root);
if (ret)
error("failed to delete root %llu item[%llu, %u, %llu]",
root->objectid, key.objectid, key.type, key.offset);
@@ -3837,8 +4245,7 @@ out:
/*
* Main entry function to check known items and update related accounting info
*/
-static int check_leaf_items(struct btrfs_trans_handle *trans,
- struct btrfs_root *root, struct btrfs_path *path,
+static int check_leaf_items(struct btrfs_root *root, struct btrfs_path *path,
struct node_refs *nrefs, int account_bytes)
{
struct btrfs_fs_info *fs_info = root->fs_info;
@@ -3869,15 +4276,14 @@ again:
case BTRFS_EXTENT_DATA_KEY:
ret = check_extent_data_item(root, path, nrefs, account_bytes);
if (repair && ret)
- ret = repair_extent_data_item(trans, root, path, nrefs,
- ret);
+ ret = repair_extent_data_item(root, path, nrefs, ret);
err |= ret;
break;
case BTRFS_BLOCK_GROUP_ITEM_KEY:
ret = check_block_group_item(fs_info, eb, slot);
if (repair &&
ret & REFERENCER_MISSING)
- ret = delete_extent_tree_item(trans, root, path);
+ ret = delete_extent_tree_item(root, path);
err |= ret;
break;
case BTRFS_DEV_ITEM_KEY:
@@ -3887,7 +4293,7 @@ again:
case BTRFS_CHUNK_ITEM_KEY:
ret = check_chunk_item(fs_info, eb, slot);
if (repair && ret)
- ret = repair_chunk_item(trans, root, path, ret);
+ ret = repair_chunk_item(root, path, ret);
err |= ret;
break;
case BTRFS_DEV_EXTENT_KEY:
@@ -3896,7 +4302,7 @@ again:
break;
case BTRFS_EXTENT_ITEM_KEY:
case BTRFS_METADATA_ITEM_KEY:
- ret = check_extent_item(trans, fs_info, path);
+ ret = check_extent_item(fs_info, path);
err |= ret;
break;
case BTRFS_EXTENT_CSUM_KEY:
@@ -3908,7 +4314,7 @@ again:
key.objectid, -1);
if (repair &&
ret & (REFERENCER_MISMATCH | REFERENCER_MISSING))
- ret = delete_extent_tree_item(trans, root, path);
+ ret = delete_extent_tree_item(root, path);
err |= ret;
break;
case BTRFS_EXTENT_DATA_REF_KEY:
@@ -3921,7 +4327,7 @@ again:
btrfs_extent_data_ref_count(eb, dref));
if (repair &&
ret & (REFERENCER_MISMATCH | REFERENCER_MISSING))
- ret = delete_extent_tree_item(trans, root, path);
+ ret = delete_extent_tree_item(root, path);
err |= ret;
break;
case BTRFS_SHARED_BLOCK_REF_KEY:
@@ -3929,7 +4335,7 @@ again:
key.objectid, -1);
if (repair &&
ret & (REFERENCER_MISMATCH | REFERENCER_MISSING))
- ret = delete_extent_tree_item(trans, root, path);
+ ret = delete_extent_tree_item(root, path);
err |= ret;
break;
case BTRFS_SHARED_DATA_REF_KEY:
@@ -3937,7 +4343,7 @@ again:
key.objectid);
if (repair &&
ret & (REFERENCER_MISMATCH | REFERENCER_MISSING))
- ret = delete_extent_tree_item(trans, root, path);
+ ret = delete_extent_tree_item(root, path);
err |= ret;
break;
default:
@@ -3959,10 +4365,8 @@ out:
* Returns <0 Fatal error, must exit the whole check
* Returns 0 No errors found
*/
-static int walk_down_tree(struct btrfs_trans_handle *trans,
- struct btrfs_root *root, struct btrfs_path *path,
- int *level, struct node_refs *nrefs, int ext_ref,
- int check_all)
+static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path,
+ int *level, struct node_refs *nrefs, int check_all)
{
enum btrfs_tree_block_status status;
u64 bytenr;
@@ -4004,7 +4408,7 @@ static int walk_down_tree(struct btrfs_trans_handle *trans,
btrfs_header_owner(cur), nrefs);
if (repair && ret)
- ret = repair_tree_block_ref(trans, root,
+ ret = repair_tree_block_ref(root,
path->nodes[*level], nrefs, *level, ret);
err |= ret;
@@ -4032,10 +4436,9 @@ static int walk_down_tree(struct btrfs_trans_handle *trans,
ret = 0;
if (!check_all)
- ret = process_one_leaf(root, path, nrefs,
- level, ext_ref);
+ ret = process_one_leaf(root, path, nrefs, level);
else
- ret = check_leaf_items(trans, root, path,
+ ret = check_leaf_items(root, path,
nrefs, account_file_data);
err |= ret;
break;
@@ -4200,7 +4603,7 @@ out:
* returns >0 means error
* returns <0 means fatal error
*/
-static int check_fs_first_inode(struct btrfs_root *root, unsigned int ext_ref)
+static int check_fs_first_inode(struct btrfs_root *root)
{
struct btrfs_path path;
struct btrfs_key key;
@@ -4241,7 +4644,7 @@ static int check_fs_first_inode(struct btrfs_root *root, unsigned int ext_ref)
/* special index value */
index = 0;
- ret = find_inode_ref(root, &key, "..", strlen(".."), &index, ext_ref);
+ ret = find_inode_ref(root, &key, "..", strlen(".."), &index);
if (ret < 0)
goto out;
err |= ret;
@@ -4266,16 +4669,13 @@ out:
* blocks and integrity of fs tree items.
*
* @root: the root of the tree to be checked.
- * @ext_ref feature EXTENDED_IREF is enable or not.
* @account if NOT 0 means check the tree (including tree)'s treeblocks.
* otherwise means check fs tree(s) items relationship and
* @root MUST be a fs tree root.
* Returns 0 represents OK.
- * Returns not 0 represents error.
+ * Returns >0 represents error bits.
*/
-static int check_btrfs_root(struct btrfs_trans_handle *trans,
- struct btrfs_root *root, unsigned int ext_ref,
- int check_all)
+static int check_btrfs_root(struct btrfs_root *root, int check_all)
{
struct btrfs_path path;
struct node_refs nrefs;
@@ -4292,9 +4692,9 @@ static int check_btrfs_root(struct btrfs_trans_handle *trans,
* the first inode item in the leaf, if inode item (256) is
* missing we will skip it forever.
*/
- ret = check_fs_first_inode(root, ext_ref);
+ ret = check_fs_first_inode(root);
if (ret < 0)
- return ret;
+ return FATAL_ERROR;
}
@@ -4319,14 +4719,13 @@ static int check_btrfs_root(struct btrfs_trans_handle *trans,
}
while (1) {
- ret = walk_down_tree(trans, root, &path, &level, &nrefs,
- ext_ref, check_all);
-
- err |= !!ret;
+ ret = walk_down_tree(root, &path, &level, &nrefs, check_all);
+ if (ret > 0)
+ err |= ret;
/* if ret is negative, walk shall stop */
if (ret < 0) {
- ret = err;
+ ret = err | FATAL_ERROR;
break;
}
@@ -4347,15 +4746,14 @@ out:
* Iterate all items in the tree and call check_inode_item() to check.
*
* @root: the root of the tree to be checked.
- * @ext_ref: the EXTENDED_IREF feature
*
* Return 0 if no error found.
* Return <0 for error.
*/
-static int check_fs_root(struct btrfs_root *root, unsigned int ext_ref)
+static int check_fs_root(struct btrfs_root *root)
{
reset_cached_block_groups(root->fs_info);
- return check_btrfs_root(NULL, root, ext_ref, 0);
+ return check_btrfs_root(root, 0);
}
/*
@@ -4464,13 +4862,10 @@ int check_fs_roots_lowmem(struct btrfs_fs_info *fs_info)
struct btrfs_path path;
struct btrfs_key key;
struct extent_buffer *node;
- unsigned int ext_ref;
int slot;
int ret;
int err = 0;
- ext_ref = btrfs_fs_incompat(fs_info, EXTENDED_IREF);
-
btrfs_init_path(&path);
key.objectid = BTRFS_FS_TREE_OBJECTID;
key.offset = 0;
@@ -4508,7 +4903,7 @@ int check_fs_roots_lowmem(struct btrfs_fs_info *fs_info)
goto next;
}
- ret = check_fs_root(cur_root, ext_ref);
+ ret = check_fs_root(cur_root);
err |= ret;
if (key.objectid == BTRFS_TREE_RELOC_OBJECTID)
@@ -4538,7 +4933,6 @@ out:
*/
int check_chunks_and_extents_lowmem(struct btrfs_fs_info *fs_info)
{
- struct btrfs_trans_handle *trans = NULL;
struct btrfs_path path;
struct btrfs_key old_key;
struct btrfs_key key;
@@ -4550,20 +4944,12 @@ int check_chunks_and_extents_lowmem(struct btrfs_fs_info *fs_info)
root = fs_info->fs_root;
- if (repair) {
- trans = btrfs_start_transaction(fs_info->extent_root, 1);
- if (IS_ERR(trans)) {
- error("failed to start transaction before check");
- return PTR_ERR(trans);
- }
- }
-
root1 = root->fs_info->chunk_root;
- ret = check_btrfs_root(trans, root1, 0, 1);
+ ret = check_btrfs_root(root1, 1);
err |= ret;
root1 = root->fs_info->tree_root;
- ret = check_btrfs_root(trans, root1, 0, 1);
+ ret = check_btrfs_root(root1, 1);
err |= ret;
btrfs_init_path(&path);
@@ -4594,7 +4980,7 @@ int check_chunks_and_extents_lowmem(struct btrfs_fs_info *fs_info)
goto next;
}
- ret = check_btrfs_root(trans, cur_root, 0, 1);
+ ret = check_btrfs_root(cur_root, 1);
err |= ret;
if (key.objectid == BTRFS_TREE_RELOC_OBJECTID)
@@ -4612,19 +4998,21 @@ next:
}
out:
- /* if repair, update block accounting */
if (repair) {
- ret = btrfs_fix_block_accounting(trans, root);
+ ret = end_avoid_extents_overwrite(fs_info);
+ if (ret < 0)
+ ret = FATAL_ERROR;
+ err |= ret;
+
+ reset_cached_block_groups(fs_info);
+ /* update block accounting */
+ ret = repair_block_accounting(fs_info);
if (ret)
err |= ret;
else
err &= ~BG_ACCOUNTING_ERROR;
}
- if (trans)
- btrfs_commit_transaction(trans, root->fs_info->extent_root);
-
btrfs_release_path(&path);
-
return err;
}
diff --git a/check/mode-lowmem.h b/check/mode-lowmem.h
index 73d57999..91f7b6b1 100644
--- a/check/mode-lowmem.h
+++ b/check/mode-lowmem.h
@@ -43,6 +43,8 @@
#define DIR_INDEX_MISMATCH (1<<19) /* INODE_INDEX found but not match */
#define DIR_COUNT_AGAIN (1<<20) /* DIR isize should be recalculated */
#define BG_ACCOUNTING_ERROR (1<<21) /* Block group accounting error */
+#define FATAL_ERROR (1<<22) /* Fatal bit for errno */
+#define INODE_FLAGS_ERROR (1<<23) /* Invalid inode flags */
/*
* Error bit for low memory mode check.
diff --git a/check/mode-original.h b/check/mode-original.h
index 368de692..13cfa5b9 100644
--- a/check/mode-original.h
+++ b/check/mode-original.h
@@ -186,6 +186,7 @@ struct file_extent_hole {
#define I_ERR_LINK_COUNT_WRONG (1 << 13)
#define I_ERR_FILE_EXTENT_ORPHAN (1 << 14)
#define I_ERR_FILE_EXTENT_TOO_LARGE (1 << 15)
+#define I_ERR_ODD_INODE_FLAGS (1 << 16)
struct inode_record {
struct list_head backrefs;