summaryrefslogtreecommitdiff
path: root/check
diff options
context:
space:
mode:
Diffstat (limited to 'check')
-rw-r--r--check/main.c73
-rw-r--r--check/mode-common.c256
-rw-r--r--check/mode-common.h2
-rw-r--r--check/mode-lowmem.c83
-rw-r--r--check/mode-original.h1
5 files changed, 370 insertions, 45 deletions
diff --git a/check/main.c b/check/main.c
index 97baae58..c4a1801f 100644
--- a/check/main.c
+++ b/check/main.c
@@ -560,6 +560,8 @@ static void print_inode_error(struct btrfs_root *root, struct inode_record *rec)
fprintf(stderr, ", bad file extent");
if (errors & I_ERR_FILE_EXTENT_OVERLAP)
fprintf(stderr, ", file extent overlap");
+ if (errors & I_ERR_FILE_EXTENT_TOO_LARGE)
+ fprintf(stderr, ", inline file extent too large");
if (errors & I_ERR_FILE_EXTENT_DISCOUNT)
fprintf(stderr, ", file extent discount");
if (errors & I_ERR_DIR_ISIZE_WRONG)
@@ -1433,6 +1435,8 @@ static int process_file_extent(struct btrfs_root *root,
u64 disk_bytenr = 0;
u64 extent_offset = 0;
u64 mask = root->fs_info->sectorsize - 1;
+ u32 max_inline_size = min_t(u32, mask,
+ BTRFS_MAX_INLINE_DATA_SIZE(root->fs_info));
int extent_type;
int ret;
@@ -1458,9 +1462,21 @@ static int process_file_extent(struct btrfs_root *root,
extent_type = btrfs_file_extent_type(eb, fi);
if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
+ u8 compression = btrfs_file_extent_compression(eb, fi);
+ struct btrfs_item *item = btrfs_item_nr(slot);
+
num_bytes = btrfs_file_extent_inline_len(eb, slot, fi);
if (num_bytes == 0)
rec->errors |= I_ERR_BAD_FILE_EXTENT;
+ if (compression) {
+ if (btrfs_file_extent_inline_item_len(eb, item) >
+ max_inline_size ||
+ num_bytes > root->fs_info->sectorsize)
+ rec->errors |= I_ERR_FILE_EXTENT_TOO_LARGE;
+ } else {
+ if (num_bytes > max_inline_size)
+ rec->errors |= I_ERR_FILE_EXTENT_TOO_LARGE;
+ }
rec->found_size += num_bytes;
num_bytes = (num_bytes + mask) & ~mask;
} else if (extent_type == BTRFS_FILE_EXTENT_REG ||
@@ -1511,8 +1527,15 @@ static int process_file_extent(struct btrfs_root *root,
if (found < num_bytes)
rec->some_csum_missing = 1;
} else if (extent_type == BTRFS_FILE_EXTENT_PREALLOC) {
- if (found > 0)
- rec->errors |= I_ERR_ODD_CSUM_ITEM;
+ if (found > 0) {
+ ret = check_prealloc_extent_written(root->fs_info,
+ disk_bytenr,
+ num_bytes);
+ if (ret < 0)
+ return ret;
+ if (ret == 0)
+ rec->errors |= I_ERR_ODD_CSUM_ITEM;
+ }
}
}
return 0;
@@ -5339,7 +5362,9 @@ static int check_space_cache(struct btrfs_root *root)
error += ret;
} else {
ret = load_free_space_cache(root->fs_info, cache);
- if (!ret)
+ if (ret < 0)
+ error++;
+ if (ret <= 0)
continue;
}
@@ -5576,6 +5601,7 @@ static int check_csums(struct btrfs_root *root)
int ret;
u64 data_len;
unsigned long leaf_offset;
+ bool verify_csum = !!check_data_csum;
root = root->fs_info->csum_root;
if (!extent_buffer_uptodate(root->node)) {
@@ -5598,6 +5624,16 @@ static int check_csums(struct btrfs_root *root)
path.slots[0]--;
ret = 0;
+ /*
+ * For metadata dump (btrfs-image) all data is wiped so verifying data
+ * csum is meaningless and will always report csum error.
+ */
+ if (check_data_csum && (btrfs_super_flags(root->fs_info->super_copy) &
+ (BTRFS_SUPER_FLAG_METADUMP | BTRFS_SUPER_FLAG_METADUMP_V2))) {
+ printf("skip data csum verification for metadata dump\n");
+ verify_csum = false;
+ }
+
while (1) {
if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) {
ret = btrfs_next_leaf(root, &path);
@@ -5619,7 +5655,7 @@ static int check_csums(struct btrfs_root *root)
data_len = (btrfs_item_size_nr(leaf, path.slots[0]) /
csum_size) * root->fs_info->sectorsize;
- if (!check_data_csum)
+ if (!verify_csum)
goto skip_csum_check;
leaf_offset = btrfs_item_ptr_offset(leaf, path.slots[0]);
ret = check_extent_csums(root, key.offset, data_len,
@@ -5934,7 +5970,7 @@ static int run_next_block(struct btrfs_root *root,
goto out;
if (btrfs_is_leaf(buf)) {
- btree_space_waste += btrfs_leaf_free_space(root, buf);
+ btree_space_waste += btrfs_leaf_free_space(fs_info, buf);
for (i = 0; i < nritems; i++) {
struct btrfs_file_extent_item *fi;
@@ -6078,12 +6114,7 @@ static int run_next_block(struct btrfs_root *root,
}
} 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++) {
struct extent_record tmpl;
@@ -7638,28 +7669,6 @@ repair_abort:
return err;
}
-u64 calc_stripe_length(u64 type, u64 length, int num_stripes)
-{
- u64 stripe_size;
-
- if (type & BTRFS_BLOCK_GROUP_RAID0) {
- stripe_size = length;
- stripe_size /= num_stripes;
- } else if (type & BTRFS_BLOCK_GROUP_RAID10) {
- stripe_size = length * 2;
- stripe_size /= num_stripes;
- } else if (type & BTRFS_BLOCK_GROUP_RAID5) {
- stripe_size = length;
- stripe_size /= (num_stripes - 1);
- } else if (type & BTRFS_BLOCK_GROUP_RAID6) {
- stripe_size = length;
- stripe_size /= (num_stripes - 2);
- } else {
- stripe_size = length;
- }
- return stripe_size;
-}
-
/*
* Check the chunk with its block group/dev list ref:
* Return 0 if all refs seems valid.
diff --git a/check/mode-common.c b/check/mode-common.c
index 1b56a968..e857d44d 100644
--- a/check/mode-common.c
+++ b/check/mode-common.c
@@ -24,6 +24,262 @@
#include "check/mode-common.h"
/*
+ * Check if the inode referenced by the given data reference uses the extent
+ * at disk_bytenr as a non-prealloc extent.
+ *
+ * Returns 1 if true, 0 if false and < 0 on error.
+ */
+static int check_prealloc_data_ref(struct btrfs_fs_info *fs_info,
+ u64 disk_bytenr,
+ struct btrfs_extent_data_ref *dref,
+ struct extent_buffer *eb)
+{
+ u64 rootid = btrfs_extent_data_ref_root(eb, dref);
+ u64 objectid = btrfs_extent_data_ref_objectid(eb, dref);
+ u64 offset = btrfs_extent_data_ref_offset(eb, dref);
+ struct btrfs_root *root;
+ struct btrfs_key key;
+ struct btrfs_path path;
+ int ret;
+
+ btrfs_init_path(&path);
+ key.objectid = rootid;
+ key.type = BTRFS_ROOT_ITEM_KEY;
+ key.offset = (u64)-1;
+ root = btrfs_read_fs_root(fs_info, &key);
+ if (IS_ERR(root)) {
+ ret = PTR_ERR(root);
+ goto out;
+ }
+
+ key.objectid = objectid;
+ key.type = BTRFS_EXTENT_DATA_KEY;
+ key.offset = offset;
+ ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+ if (ret > 0) {
+ fprintf(stderr,
+ "Missing file extent item for inode %llu, root %llu, offset %llu",
+ objectid, rootid, offset);
+ ret = -ENOENT;
+ }
+ if (ret < 0)
+ goto out;
+
+ while (true) {
+ struct btrfs_file_extent_item *fi;
+ int extent_type;
+
+ if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) {
+ ret = btrfs_next_leaf(root, &path);
+ if (ret < 0)
+ goto out;
+ if (ret > 0)
+ break;
+ }
+
+ btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
+ if (key.objectid != objectid ||
+ key.type != BTRFS_EXTENT_DATA_KEY)
+ break;
+
+ fi = btrfs_item_ptr(path.nodes[0], path.slots[0],
+ struct btrfs_file_extent_item);
+ extent_type = btrfs_file_extent_type(path.nodes[0], fi);
+ if (extent_type != BTRFS_FILE_EXTENT_REG &&
+ extent_type != BTRFS_FILE_EXTENT_PREALLOC)
+ goto next;
+
+ if (btrfs_file_extent_disk_bytenr(path.nodes[0], fi) !=
+ disk_bytenr)
+ break;
+
+ if (extent_type == BTRFS_FILE_EXTENT_REG) {
+ ret = 1;
+ goto out;
+ }
+next:
+ path.slots[0]++;
+ }
+ ret = 0;
+ out:
+ btrfs_release_path(&path);
+ return ret;
+}
+
+/*
+ * Check if a shared data reference points to a node that has a file extent item
+ * pointing to the extent at @disk_bytenr that is not of type prealloc.
+ *
+ * Returns 1 if true, 0 if false and < 0 on error.
+ */
+static int check_prealloc_shared_data_ref(struct btrfs_fs_info *fs_info,
+ u64 parent, u64 disk_bytenr)
+{
+ struct extent_buffer *eb;
+ u32 nr;
+ int i;
+ int ret = 0;
+
+ eb = read_tree_block(fs_info, parent, 0);
+ if (!extent_buffer_uptodate(eb)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ nr = btrfs_header_nritems(eb);
+ for (i = 0; i < nr; i++) {
+ struct btrfs_key key;
+ struct btrfs_file_extent_item *fi;
+ int extent_type;
+
+ btrfs_item_key_to_cpu(eb, &key, i);
+ if (key.type != BTRFS_EXTENT_DATA_KEY)
+ continue;
+
+ fi = btrfs_item_ptr(eb, i, struct btrfs_file_extent_item);
+ extent_type = btrfs_file_extent_type(eb, fi);
+ if (extent_type != BTRFS_FILE_EXTENT_REG &&
+ extent_type != BTRFS_FILE_EXTENT_PREALLOC)
+ continue;
+
+ if (btrfs_file_extent_disk_bytenr(eb, fi) == disk_bytenr &&
+ extent_type == BTRFS_FILE_EXTENT_REG) {
+ ret = 1;
+ break;
+ }
+ }
+ out:
+ free_extent_buffer(eb);
+ return ret;
+}
+
+/*
+ * Check if a prealloc extent is shared by multiple inodes and if any inode has
+ * already written to that extent. This is to avoid emitting invalid warnings
+ * about odd csum items (a inode has an extent entirely marked as prealloc
+ * but another inode shares it and has already written to it).
+ *
+ * Note: right now it does not check if the number of checksum items in the
+ * csum tree matches the number of bytes written into the ex-prealloc extent.
+ * It's complex to deal with that because the prealloc extent might have been
+ * partially written through multiple inodes and we would have to keep track of
+ * ranges, merging them and notice ranges that fully or partially overlap, to
+ * avoid false reports of csum items missing for areas of the prealloc extent
+ * that were not written to - for example if we have a 1M prealloc extent, we
+ * can have only the first half of it written, but 2 different inodes refer to
+ * the its first half (through reflinks/cloning), so keeping a counter of bytes
+ * covered by checksum items is not enough, as the correct value would be 512K
+ * and not 1M (whence the need to track ranges).
+ *
+ * Returns 0 if the prealloc extent was not written yet by any inode, 1 if
+ * at least one other inode has written to it, and < 0 on error.
+ */
+int check_prealloc_extent_written(struct btrfs_fs_info *fs_info,
+ u64 disk_bytenr, u64 num_bytes)
+{
+ struct btrfs_path path;
+ struct btrfs_key key;
+ int ret;
+ struct btrfs_extent_item *ei;
+ u32 item_size;
+ unsigned long ptr;
+ unsigned long end;
+
+ key.objectid = disk_bytenr;
+ key.type = BTRFS_EXTENT_ITEM_KEY;
+ key.offset = num_bytes;
+
+ btrfs_init_path(&path);
+ ret = btrfs_search_slot(NULL, fs_info->extent_root, &key, &path, 0, 0);
+ if (ret > 0) {
+ fprintf(stderr,
+ "Missing extent item in extent tree for disk_bytenr %llu, num_bytes %llu\n",
+ disk_bytenr, num_bytes);
+ ret = -ENOENT;
+ }
+ if (ret < 0)
+ goto out;
+
+ /* First check all inline refs. */
+ ei = btrfs_item_ptr(path.nodes[0], path.slots[0],
+ struct btrfs_extent_item);
+ item_size = btrfs_item_size_nr(path.nodes[0], path.slots[0]);
+ ptr = (unsigned long)(ei + 1);
+ end = (unsigned long)ei + item_size;
+ while (ptr < end) {
+ struct btrfs_extent_inline_ref *iref;
+ int type;
+
+ iref = (struct btrfs_extent_inline_ref *)ptr;
+ type = btrfs_extent_inline_ref_type(path.nodes[0], iref);
+ ASSERT(type == BTRFS_EXTENT_DATA_REF_KEY ||
+ type == BTRFS_SHARED_DATA_REF_KEY);
+
+ if (type == BTRFS_EXTENT_DATA_REF_KEY) {
+ struct btrfs_extent_data_ref *dref;
+
+ dref = (struct btrfs_extent_data_ref *)(&iref->offset);
+ ret = check_prealloc_data_ref(fs_info, disk_bytenr,
+ dref, path.nodes[0]);
+ if (ret != 0)
+ goto out;
+ } else if (type == BTRFS_SHARED_DATA_REF_KEY) {
+ u64 parent;
+
+ parent = btrfs_extent_inline_ref_offset(path.nodes[0],
+ iref);
+ ret = check_prealloc_shared_data_ref(fs_info,
+ parent,
+ disk_bytenr);
+ if (ret != 0)
+ goto out;
+ }
+
+ ptr += btrfs_extent_inline_ref_size(type);
+ }
+
+ /* Now check if there are any non-inlined refs. */
+ path.slots[0]++;
+ while (true) {
+ if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) {
+ ret = btrfs_next_leaf(fs_info->extent_root, &path);
+ if (ret < 0)
+ goto out;
+ if (ret > 0) {
+ ret = 0;
+ break;
+ }
+ }
+
+ btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
+ if (key.objectid != disk_bytenr)
+ break;
+
+ if (key.type == BTRFS_EXTENT_DATA_REF_KEY) {
+ struct btrfs_extent_data_ref *dref;
+
+ dref = btrfs_item_ptr(path.nodes[0], path.slots[0],
+ struct btrfs_extent_data_ref);
+ ret = check_prealloc_data_ref(fs_info, disk_bytenr,
+ dref, path.nodes[0]);
+ if (ret != 0)
+ goto out;
+ } else if (key.type == BTRFS_SHARED_DATA_REF_KEY) {
+ ret = check_prealloc_shared_data_ref(fs_info,
+ key.offset,
+ disk_bytenr);
+ if (ret != 0)
+ goto out;
+ }
+
+ path.slots[0]++;
+ }
+out:
+ btrfs_release_path(&path);
+ return ret;
+}
+
+/*
* Search in csum tree to find how many bytes of range [@start, @start + @len)
* has the corresponding csum item.
*
diff --git a/check/mode-common.h b/check/mode-common.h
index ffae782b..877c1aa9 100644
--- a/check/mode-common.h
+++ b/check/mode-common.h
@@ -80,6 +80,8 @@ static inline int fs_root_objectid(u64 objectid)
return is_fstree(objectid);
}
+int check_prealloc_extent_written(struct btrfs_fs_info *fs_info,
+ u64 disk_bytenr, u64 num_bytes);
int count_csum_range(struct btrfs_fs_info *fs_info, u64 start,
u64 len, u64 *found);
int insert_inode_item(struct btrfs_trans_handle *trans,
diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c
index 62bcf3d2..bfe45aba 100644
--- a/check/mode-lowmem.c
+++ b/check/mode-lowmem.c
@@ -390,7 +390,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, eb);
+ btree_space_waste += btrfs_leaf_free_space(root->fs_info, eb);
} else {
free_nrs = (BTRFS_NODEPTRS_PER_BLOCK(root->fs_info) -
btrfs_header_nritems(eb));
@@ -1417,6 +1417,8 @@ static int check_file_extent(struct btrfs_root *root, struct btrfs_key *fkey,
u64 csum_found; /* In byte size, sectorsize aligned */
u64 search_start; /* Logical range start we search for csum */
u64 search_len; /* Logical range len we search for csum */
+ u32 max_inline_extent_size = min_t(u32, root->fs_info->sectorsize - 1,
+ BTRFS_MAX_INLINE_DATA_SIZE(root->fs_info));
unsigned int extent_type;
unsigned int is_hole;
int compressed = 0;
@@ -1440,6 +1442,32 @@ static int check_file_extent(struct btrfs_root *root, struct btrfs_key *fkey,
root->objectid, fkey->objectid, fkey->offset);
err |= FILE_EXTENT_ERROR;
}
+ if (compressed) {
+ if (extent_num_bytes > root->fs_info->sectorsize) {
+ error(
+"root %llu EXTENT_DATA[%llu %llu] too large inline extent ram size, have %llu, max: %u",
+ root->objectid, fkey->objectid,
+ fkey->offset, extent_num_bytes,
+ root->fs_info->sectorsize - 1);
+ err |= FILE_EXTENT_ERROR;
+ }
+ if (item_inline_len > max_inline_extent_size) {
+ error(
+"root %llu EXTENT_DATA[%llu %llu] too large inline extent on-disk size, have %u, max: %u",
+ root->objectid, fkey->objectid,
+ fkey->offset, item_inline_len,
+ max_inline_extent_size);
+ err |= FILE_EXTENT_ERROR;
+ }
+ } else {
+ if (extent_num_bytes > max_inline_extent_size) {
+ error(
+ "root %llu EXTENT_DATA[%llu %llu] too large inline extent size, have %llu, max: %u",
+ root->objectid, fkey->objectid, fkey->offset,
+ extent_num_bytes, max_inline_extent_size);
+ err |= FILE_EXTENT_ERROR;
+ }
+ }
if (!compressed && extent_num_bytes != item_inline_len) {
error(
"root %llu EXTENT_DATA[%llu %llu] wrong inline size, have: %llu, expected: %u",
@@ -1503,9 +1531,17 @@ static int check_file_extent(struct btrfs_root *root, struct btrfs_key *fkey,
csum_found, search_len);
} else if (extent_type == BTRFS_FILE_EXTENT_PREALLOC &&
csum_found > 0) {
- err |= ODD_CSUM_ITEM;
- error("root %llu EXTENT_DATA[%llu %llu] prealloc shouldn't have csum, but has: %llu",
- root->objectid, fkey->objectid, fkey->offset, csum_found);
+ ret = check_prealloc_extent_written(root->fs_info,
+ disk_bytenr, disk_num_bytes);
+ if (ret < 0)
+ return ret;
+ if (ret == 0) {
+ err |= ODD_CSUM_ITEM;
+ error(
+"root %llu EXTENT_DATA[%llu %llu] prealloc shouldn't have csum, but has: %llu",
+ root->objectid, fkey->objectid, fkey->offset,
+ csum_found);
+ }
}
/* Check EXTENT_DATA hole */
@@ -1861,6 +1897,24 @@ out:
return ret;
}
+static bool has_orphan_item(struct btrfs_root *root, u64 ino)
+{
+ struct btrfs_path path;
+ struct btrfs_key key;
+ int ret;
+
+ btrfs_init_path(&path);
+ key.objectid = BTRFS_ORPHAN_OBJECTID;
+ key.type = BTRFS_ORPHAN_ITEM_KEY;
+ key.offset = ino;
+
+ ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+ btrfs_release_path(&path);
+ if (ret == 0)
+ return true;
+ return false;
+}
+
/*
* Check INODE_ITEM and related ITEMs (the same inode number)
* 1. check link count
@@ -1890,6 +1944,7 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path,
u64 extent_size = 0;
unsigned int dir;
unsigned int nodatasum;
+ bool is_orphan = false;
int slot;
int ret;
int err = 0;
@@ -2040,10 +2095,11 @@ out:
root->objectid, inode_id, nlink, refs);
}
} else if (!nlink) {
- if (repair)
+ is_orphan = has_orphan_item(root, inode_id);
+ if (!is_orphan && repair)
ret = repair_inode_orphan_item_lowmem(root,
path, inode_id);
- if (!repair || ret) {
+ if (!is_orphan && (!repair || ret)) {
err |= ORPHAN_ITEM;
error("root %llu INODE[%llu] is orphan item",
root->objectid, inode_id);
@@ -2631,9 +2687,9 @@ static int check_extent_data_item(struct btrfs_root *root,
if (!(extent_flags & BTRFS_EXTENT_FLAG_DATA)) {
error(
- "extent[%llu %llu] backref type mismatch, wanted bit: %llx",
- disk_bytenr, disk_num_bytes,
- BTRFS_EXTENT_FLAG_DATA);
+"file extent[%llu %llu] root %llu owner %llu backref type mismatch, wanted bit: %llx",
+ fi_key.objectid, fi_key.offset, root->objectid, owner,
+ BTRFS_EXTENT_FLAG_DATA);
err |= BACKREF_MISMATCH;
}
@@ -2689,8 +2745,8 @@ static int check_extent_data_item(struct btrfs_root *root,
/* Didn't find inlined data backref, try EXTENT_DATA_REF_KEY */
dbref_key.objectid = btrfs_file_extent_disk_bytenr(eb, fi);
dbref_key.type = BTRFS_EXTENT_DATA_REF_KEY;
- dbref_key.offset = hash_extent_data_ref(root->objectid,
- fi_key.objectid, fi_key.offset - offset);
+ dbref_key.offset = hash_extent_data_ref(owner, fi_key.objectid,
+ fi_key.offset - offset);
ret = btrfs_search_slot(NULL, root->fs_info->extent_root,
&dbref_key, &path, 0, 0);
@@ -2722,8 +2778,9 @@ out:
err |= BACKREF_MISSING;
btrfs_release_path(&path);
if (err & BACKREF_MISSING) {
- error("data extent[%llu %llu] backref lost",
- disk_bytenr, disk_num_bytes);
+ error(
+ "file extent[%llu %llu] root %llu owner %llu backref lost",
+ fi_key.objectid, fi_key.offset, root->objectid, owner);
}
return err;
}
diff --git a/check/mode-original.h b/check/mode-original.h
index f859af47..368de692 100644
--- a/check/mode-original.h
+++ b/check/mode-original.h
@@ -185,6 +185,7 @@ struct file_extent_hole {
#define I_ERR_SOME_CSUM_MISSING (1 << 12)
#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)
struct inode_record {
struct list_head backrefs;