diff options
Diffstat (limited to 'check')
-rw-r--r-- | check/main.c | 73 | ||||
-rw-r--r-- | check/mode-common.c | 256 | ||||
-rw-r--r-- | check/mode-common.h | 2 | ||||
-rw-r--r-- | check/mode-lowmem.c | 83 | ||||
-rw-r--r-- | check/mode-original.h | 1 |
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; |