filetype = btrfs_dir_type(eb, di);
rec->found_size += name_len;
- if (name_len <= BTRFS_NAME_LEN) {
+ if (cur + sizeof(*di) + name_len > total ||
+ name_len > BTRFS_NAME_LEN) {
+ error = REF_ERR_NAME_TOO_LONG;
+
+ if (cur + sizeof(*di) > total)
+ break;
+ len = min_t(u32, total - cur - sizeof(*di),
+ BTRFS_NAME_LEN);
+ } else {
len = name_len;
error = 0;
- } else {
- len = BTRFS_NAME_LEN;
- error = REF_ERR_NAME_TOO_LONG;
}
+
read_extent_buffer(eb, namebuf, (unsigned long)(di + 1), len);
if (location.type == BTRFS_INODE_ITEM_KEY) {
while (cur < total) {
name_len = btrfs_inode_ref_name_len(eb, ref);
index = btrfs_inode_ref_index(eb, ref);
- if (name_len <= BTRFS_NAME_LEN) {
+
+ /* inode_ref + namelen should not cross item boundary */
+ if (cur + sizeof(*ref) + name_len > total ||
+ name_len > BTRFS_NAME_LEN) {
+ if (total < cur + sizeof(*ref))
+ break;
+
+ /* Still try to read out the remaining part */
+ len = min_t(u32, total - cur - sizeof(*ref),
+ BTRFS_NAME_LEN);
+ error = REF_ERR_NAME_TOO_LONG;
+ } else {
len = name_len;
error = 0;
- } else {
- len = BTRFS_NAME_LEN;
- error = REF_ERR_NAME_TOO_LONG;
}
+
read_extent_buffer(eb, namebuf, (unsigned long)(ref + 1), len);
add_inode_backref(inode_cache, key->objectid, key->offset,
index, namebuf, len, 0, key->type, error);
static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path,
unsigned int ext_ref);
+/*
+ * Returns >0 Found error, not fatal, should continue
+ * Returns <0 Fatal error, must exit the whole check
+ * Returns 0 No errors found
+ */
static int process_one_leaf_v2(struct btrfs_root *root, struct btrfs_path *path,
struct node_refs *nrefs, int *level, int ext_ref)
{
}
out:
err &= ~LAST_ITEM;
- /*
- * Convert any error bitmap to -EIO, as we should avoid
- * mixing positive and negative return value to represent
- * error
- */
if (err && !ret)
- ret = -EIO;
+ ret = err;
return ret;
}
ret = check_child_node(cur, path->slots[*level], next);
if (ret) {
+ free_extent_buffer(next);
err = ret;
goto out;
}
static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path,
unsigned int ext_ref);
+/*
+ * Returns >0 Found error, should continue
+ * Returns <0 Fatal error, must exit the whole check
+ * Returns 0 No errors found
+ */
static int walk_down_tree_v2(struct btrfs_root *root, struct btrfs_path *path,
int *level, struct node_refs *nrefs, int ext_ref)
{
repaired++;
list_del(&backref->list);
free(backref);
+ continue;
}
if (!delete && !backref->found_dir_index &&
break;
repaired++;
if (backref->found_dir_item &&
- backref->found_dir_index &&
backref->found_dir_index) {
if (!backref->errors &&
backref->found_inode_ref) {
list_del(&backref->list);
free(backref);
+ continue;
}
}
}
if (imode_to_type(mode) != filetype)
goto next;
- if (name_len <= BTRFS_NAME_LEN) {
- len = name_len;
- } else {
- len = BTRFS_NAME_LEN;
+ if (cur + sizeof(*di) + name_len > total ||
+ name_len > BTRFS_NAME_LEN) {
warning("root %llu %s[%llu %llu] name too long %u, trimmed",
- root->objectid,
- key->type == BTRFS_DIR_ITEM_KEY ?
- "DIR_ITEM" : "DIR_INDEX",
- key->objectid, key->offset, name_len);
+ root->objectid,
+ key->type == BTRFS_DIR_ITEM_KEY ?
+ "DIR_ITEM" : "DIR_INDEX",
+ key->objectid, key->offset, name_len);
+
+ if (cur + sizeof(*di) > total)
+ break;
+ len = min_t(u32, total - cur - sizeof(*di),
+ BTRFS_NAME_LEN);
+ } else {
+ len = name_len;
}
+
read_extent_buffer(node, namebuf, (unsigned long)(di + 1), len);
if (len != namelen || strncmp(namebuf, name, len))
goto next;
index = btrfs_inode_ref_index(node, ref);
name_len = btrfs_inode_ref_name_len(node, ref);
- if (name_len <= BTRFS_NAME_LEN) {
- len = name_len;
- } else {
- len = BTRFS_NAME_LEN;
+ if (cur + sizeof(*ref) + name_len > total ||
+ name_len > BTRFS_NAME_LEN) {
warning("root %llu INODE_REF[%llu %llu] name too long",
root->objectid, ref_key->objectid, ref_key->offset);
+
+ if (total < cur + sizeof(*ref))
+ goto out;
+ len = min_t(u32, total - cur - sizeof(*ref), BTRFS_NAME_LEN);
+ } else {
+ len = name_len;
}
read_extent_buffer(node, namebuf, (unsigned long)(ref + 1), len);
if (cur < total)
goto next;
+out:
return err;
}
if (index != (u64)-1 && index != ref_index)
goto next_ref;
- if (ref_namelen <= BTRFS_NAME_LEN) {
- len = ref_namelen;
- } else {
- len = BTRFS_NAME_LEN;
+ if (cur + sizeof(*ref) + ref_namelen > total ||
+ ref_namelen > BTRFS_NAME_LEN) {
warning("root %llu INODE %s[%llu %llu] name too long",
root->objectid,
key->type == BTRFS_INODE_REF_KEY ?
"REF" : "EXTREF",
key->objectid, key->offset);
+
+ if (cur + sizeof(*ref) > total)
+ break;
+ len = min_t(u32, total - cur - sizeof(*ref),
+ BTRFS_NAME_LEN);
+ } else {
+ len = ref_namelen;
}
+
read_extent_buffer(node, ref_namebuf, (unsigned long)(ref + 1),
len);
key->objectid, key->offset, data_len);
name_len = btrfs_dir_name_len(node, di);
- if (name_len <= BTRFS_NAME_LEN) {
- len = name_len;
- } else {
- len = BTRFS_NAME_LEN;
+ if (cur + sizeof(*di) + name_len > total ||
+ name_len > BTRFS_NAME_LEN) {
warning("root %llu %s[%llu %llu] name too long",
root->objectid,
key->type == BTRFS_DIR_ITEM_KEY ?
"DIR_ITEM" : "DIR_INDEX",
key->objectid, key->offset);
+
+ if (cur + sizeof(*di) > total)
+ break;
+ len = min_t(u32, total - cur - sizeof(*di),
+ BTRFS_NAME_LEN);
+ } else {
+ len = name_len;
}
(*size) += name_len;
u64 disk_bytenr;
u64 disk_num_bytes;
u64 extent_num_bytes;
- u64 found;
+ u64 extent_offset;
+ 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 */
unsigned int extent_type;
unsigned int is_hole;
+ int compressed = 0;
int ret;
int err = 0;
fi = btrfs_item_ptr(node, slot, struct btrfs_file_extent_item);
+ /* Check inline extent */
extent_type = btrfs_file_extent_type(node, fi);
- /* Skip if file extent is inline */
if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
struct btrfs_item *e = btrfs_item_nr(slot);
u32 item_inline_len;
item_inline_len = btrfs_file_extent_inline_item_len(node, e);
extent_num_bytes = btrfs_file_extent_inline_len(node, slot, fi);
- if (extent_num_bytes == 0 ||
- extent_num_bytes != item_inline_len)
+ compressed = btrfs_file_extent_compression(node, fi);
+ if (extent_num_bytes == 0) {
+ error(
+ "root %llu EXTENT_DATA[%llu %llu] has empty inline extent",
+ root->objectid, fkey->objectid, fkey->offset);
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",
+ root->objectid, fkey->objectid, fkey->offset,
+ extent_num_bytes, item_inline_len);
+ err |= FILE_EXTENT_ERROR;
+ }
+ *end += extent_num_bytes;
*size += extent_num_bytes;
return err;
}
disk_bytenr = btrfs_file_extent_disk_bytenr(node, fi);
disk_num_bytes = btrfs_file_extent_disk_num_bytes(node, fi);
extent_num_bytes = btrfs_file_extent_num_bytes(node, fi);
+ extent_offset = btrfs_file_extent_offset(node, fi);
+ compressed = btrfs_file_extent_compression(node, fi);
is_hole = (disk_bytenr == 0) && (disk_num_bytes == 0);
- /* Check EXTENT_DATA datasum */
- ret = count_csum_range(root, disk_bytenr, disk_num_bytes, &found);
- if (found > 0 && nodatasum) {
+ /*
+ * Check EXTENT_DATA csum
+ *
+ * For plain (uncompressed) extent, we should only check the range
+ * we're referring to, as it's possible that part of prealloc extent
+ * has been written, and has csum:
+ *
+ * |<--- Original large preallocated extent A ---->|
+ * |<- Prealloc File Extent ->|<- Regular Extent ->|
+ * No csum Has csum
+ *
+ * For compressed extent, we should check the whole range.
+ */
+ if (!compressed) {
+ search_start = disk_bytenr + extent_offset;
+ search_len = extent_num_bytes;
+ } else {
+ search_start = disk_bytenr;
+ search_len = disk_num_bytes;
+ }
+ ret = count_csum_range(root, search_start, search_len, &csum_found);
+ if (csum_found > 0 && nodatasum) {
err |= ODD_CSUM_ITEM;
error("root %llu EXTENT_DATA[%llu %llu] nodatasum shouldn't have datasum",
root->objectid, fkey->objectid, fkey->offset);
} else if (extent_type == BTRFS_FILE_EXTENT_REG && !nodatasum &&
- !is_hole &&
- (ret < 0 || found == 0 || found < disk_num_bytes)) {
+ !is_hole && (ret < 0 || csum_found < search_len)) {
err |= CSUM_ITEM_MISSING;
- error("root %llu EXTENT_DATA[%llu %llu] datasum missing",
- root->objectid, fkey->objectid, fkey->offset);
- } else if (extent_type == BTRFS_FILE_EXTENT_PREALLOC && found > 0) {
+ error("root %llu EXTENT_DATA[%llu %llu] csum missing, have: %llu, expected: %llu",
+ root->objectid, fkey->objectid, fkey->offset,
+ 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 datasum",
- root->objectid, fkey->objectid, fkey->offset);
+ 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 */
- if (no_holes && is_hole) {
- err |= FILE_EXTENT_ERROR;
- error("root %llu EXTENT_DATA[%llu %llu] shouldn't be hole",
- root->objectid, fkey->objectid, fkey->offset);
- } else if (!no_holes && *end != fkey->offset) {
+ if (!no_holes && *end != fkey->offset) {
err |= FILE_EXTENT_ERROR;
error("root %llu EXTENT_DATA[%llu %llu] interrupt",
root->objectid, fkey->objectid, fkey->offset);
if (ret > 0) {
ret = 0;
err |= INODE_ITEM_MISSING;
+ error("first inode item of root %llu is missing",
+ root->objectid);
}
err |= check_inode_item(root, &path, ext_ref);
struct btrfs_path path;
struct node_refs nrefs;
struct btrfs_root_item *root_item = &root->root_item;
- int ret, wret;
+ int ret;
int level;
+ int err = 0;
/*
* We need to manually check the first inode item(256)
}
while (1) {
- wret = walk_down_tree_v2(root, &path, &level, &nrefs, ext_ref);
- if (wret < 0)
- ret = wret;
- if (wret != 0)
+ ret = walk_down_tree_v2(root, &path, &level, &nrefs, ext_ref);
+ err |= !!ret;
+
+ /* if ret is negative, walk shall stop */
+ if (ret < 0) {
+ ret = err;
break;
+ }
- wret = walk_up_tree_v2(root, &path, &level);
- if (wret < 0)
- ret = wret;
- if (wret != 0)
+ ret = walk_up_tree_v2(root, &path, &level);
+ if (ret != 0) {
+ /* Normal exit, reset ret to err */
+ ret = err;
break;
+ }
}
out:
struct extent_record *rec;
int ret = 0;
+ BUG_ON(tmpl->max_size == 0);
rec = malloc(sizeof(*rec));
if (!rec)
return -ENOMEM;
tmpl.start = bytenr;
tmpl.nr = 1;
tmpl.metadata = 1;
+ tmpl.max_size = 1;
ret = add_extent_rec_nolookup(extent_cache, &tmpl);
if (ret)
ret = add_tree_backref(extent_cache, key.objectid,
0, offset, 0);
if (ret < 0)
- error("add_tree_backref failed: %s",
+ error(
+ "add_tree_backref failed (extent items tree block): %s",
strerror(-ret));
break;
case BTRFS_SHARED_BLOCK_REF_KEY:
ret = add_tree_backref(extent_cache, key.objectid,
offset, 0, 0);
if (ret < 0)
- error("add_tree_backref failed: %s",
+ error(
+ "add_tree_backref failed (extent items shared block): %s",
strerror(-ret));
break;
case BTRFS_EXTENT_DATA_REF_KEY:
ret = add_tree_backref(extent_cache,
key.objectid, 0, key.offset, 0);
if (ret < 0)
- error("add_tree_backref failed: %s",
+ error(
+ "add_tree_backref failed (leaf tree block): %s",
strerror(-ret));
continue;
}
ret = add_tree_backref(extent_cache,
key.objectid, key.offset, 0, 0);
if (ret < 0)
- error("add_tree_backref failed: %s",
+ error(
+ "add_tree_backref failed (leaf shared block): %s",
strerror(-ret));
continue;
}
ret = add_tree_backref(extent_cache, ptr, parent,
owner, 1);
if (ret < 0) {
- error("add_tree_backref failed: %s",
+ error(
+ "add_tree_backref failed (non-leaf block): %s",
strerror(-ret));
continue;
}
free_extent_cache_tree(&pending);
free_extent_cache_tree(&reada);
free_extent_cache_tree(&nodes);
+ free_root_item_list(&normal_trees);
+ free_root_item_list(&dropping_trees);
return ret;
loop:
free_corrupt_blocks_tree(root->fs_info->corrupt_blocks);
dbref_key.offset = btrfs_file_extent_disk_num_bytes(eb, fi);
ret = btrfs_search_slot(NULL, extent_root, &dbref_key, &path, 0, 0);
- if (ret) {
- err |= BACKREF_MISSING;
- goto error;
- }
+ if (ret)
+ goto out;
leaf = path.nodes[0];
slot = path.slots[0];
ptr += btrfs_extent_inline_ref_size(type);
}
- /* Didn't found inlined data backref, try EXTENT_DATA_REF_KEY */
if (!found_dbackref) {
btrfs_release_path(&path);
- btrfs_init_path(&path);
+ /* 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,
ret = btrfs_search_slot(NULL, root->fs_info->extent_root,
&dbref_key, &path, 0, 0);
- if (!ret)
+ if (!ret) {
+ found_dbackref = 1;
+ goto out;
+ }
+
+ btrfs_release_path(&path);
+
+ /*
+ * Neither inlined nor EXTENT_DATA_REF found, try
+ * SHARED_DATA_REF as last chance.
+ */
+ dbref_key.objectid = disk_bytenr;
+ dbref_key.type = BTRFS_SHARED_DATA_REF_KEY;
+ dbref_key.offset = eb->start;
+
+ ret = btrfs_search_slot(NULL, root->fs_info->extent_root,
+ &dbref_key, &path, 0, 0);
+ if (!ret) {
found_dbackref = 1;
+ goto out;
+ }
}
+out:
if (!found_dbackref)
err |= BACKREF_MISSING;
-error:
btrfs_release_path(&path);
if (err & BACKREF_MISSING) {
error("data extent[%llu %llu] backref lost",
}
end = (unsigned long)ei + item_size;
- if (ptr >= end) {
+next:
+ /* Reached extent item end normally */
+ if (ptr == end)
+ goto out;
+
+ /* Beyond extent item end, wrong item size */
+ if (ptr > end) {
err |= ITEM_SIZE_MISMATCH;
+ error("extent item at bytenr %llu slot %d has wrong size",
+ eb->start, slot);
goto out;
}
/* Now check every backref in this extent item */
-next:
iref = (struct btrfs_extent_inline_ref *)ptr;
type = btrfs_extent_inline_ref_type(eb, iref);
offset = btrfs_extent_inline_ref_offset(eb, iref);
}
ptr += btrfs_extent_inline_ref_size(type);
- if (ptr < end)
- goto next;
+ goto next;
out:
return err;