diff options
Diffstat (limited to 'check')
-rw-r--r-- | check/main.c | 235 | ||||
-rw-r--r-- | check/mode-common.c | 51 | ||||
-rw-r--r-- | check/mode-common.h | 4 | ||||
-rw-r--r-- | check/mode-lowmem.c | 89 | ||||
-rw-r--r-- | check/mode-lowmem.h | 1 | ||||
-rw-r--r-- | check/mode-original.h | 14 |
6 files changed, 379 insertions, 15 deletions
diff --git a/check/main.c b/check/main.c index db18827b..7547209c 100644 --- a/check/main.c +++ b/check/main.c @@ -462,6 +462,8 @@ static struct inode_record *clone_inode_rec(struct inode_record *orig_rec) struct inode_backref *tmp; struct orphan_data_extent *src_orphan; struct orphan_data_extent *dst_orphan; + struct mismatch_dir_hash_record *hash_record; + struct mismatch_dir_hash_record *new_record; struct rb_node *rb; size_t size; int ret; @@ -473,6 +475,7 @@ static struct inode_record *clone_inode_rec(struct inode_record *orig_rec) rec->refs = 1; INIT_LIST_HEAD(&rec->backrefs); INIT_LIST_HEAD(&rec->orphan_extents); + INIT_LIST_HEAD(&rec->mismatch_dir_hash); rec->holes = RB_ROOT; list_for_each_entry(orig, &orig_rec->backrefs, list) { @@ -494,6 +497,16 @@ static struct inode_record *clone_inode_rec(struct inode_record *orig_rec) memcpy(dst_orphan, src_orphan, sizeof(*src_orphan)); list_add_tail(&dst_orphan->list, &rec->orphan_extents); } + list_for_each_entry(hash_record, &orig_rec->mismatch_dir_hash, list) { + size = sizeof(*hash_record) + hash_record->namelen; + new_record = malloc(size); + if (!new_record) { + ret = -ENOMEM; + goto cleanup; + } + memcpy(&new_record, hash_record, size); + list_add_tail(&new_record->list, &rec->mismatch_dir_hash); + } ret = copy_file_extent_holes(&rec->holes, &orig_rec->holes); if (ret < 0) goto cleanup_rb; @@ -522,6 +535,13 @@ cleanup: list_del(&orig->list); free(orig); } + if (!list_empty(&rec->mismatch_dir_hash)) { + list_for_each_entry_safe(hash_record, new_record, + &rec->mismatch_dir_hash, list) { + list_del(&hash_record->list); + free(hash_record); + } + } free(rec); @@ -621,6 +641,25 @@ static void print_inode_error(struct btrfs_root *root, struct inode_record *rec) round_up(rec->isize, root->fs_info->sectorsize)); } + + /* Print dir item with mismatch hash */ + if (errors & I_ERR_MISMATCH_DIR_HASH) { + struct mismatch_dir_hash_record *hash_record; + + fprintf(stderr, "Dir items with mismatch hash:\n"); + list_for_each_entry(hash_record, &rec->mismatch_dir_hash, + list) { + char *namebuf = (char *)(hash_record + 1); + u32 crc; + + crc = btrfs_name_hash(namebuf, hash_record->namelen); + fprintf(stderr, + "\tname: %.*s namelen: %u wanted 0x%08x has 0x%08llx\n", + hash_record->namelen, namebuf, + hash_record->namelen, crc, + hash_record->key.offset); + } + } } static void print_ref_error(int errors) @@ -682,6 +721,7 @@ static struct inode_record *get_inode_rec(struct cache_tree *inode_cache, rec->refs = 1; INIT_LIST_HEAD(&rec->backrefs); INIT_LIST_HEAD(&rec->orphan_extents); + INIT_LIST_HEAD(&rec->mismatch_dir_hash); rec->holes = RB_ROOT; node = malloc(sizeof(*node)); @@ -718,6 +758,8 @@ static void free_orphan_data_extents(struct list_head *orphan_extents) static void free_inode_rec(struct inode_record *rec) { struct inode_backref *backref; + struct mismatch_dir_hash_record *hash; + struct mismatch_dir_hash_record *next; if (--rec->refs > 0) return; @@ -727,6 +769,8 @@ static void free_inode_rec(struct inode_record *rec) list_del(&backref->list); free(backref); } + list_for_each_entry_safe(hash, next, &rec->mismatch_dir_hash, list) + free(hash); free_orphan_data_extents(&rec->orphan_extents); free_file_extent_holes(&rec->holes); free(rec); @@ -1273,6 +1317,25 @@ out: return has_parent ? 0 : 2; } +static int add_mismatch_dir_hash(struct inode_record *dir_rec, + struct btrfs_key *key, const char *namebuf, + int namelen) +{ + struct mismatch_dir_hash_record *hash_record; + + hash_record = malloc(sizeof(*hash_record) + namelen); + if (!hash_record) { + error("failed to allocate memory for mismatch dir hash rec"); + return -ENOMEM; + } + memcpy(&hash_record->key, key, sizeof(*key)); + memcpy(hash_record + 1, namebuf, namelen); + hash_record->namelen = namelen; + + list_add(&hash_record->list, &dir_rec->mismatch_dir_hash); + return 0; +} + static int process_dir_item(struct extent_buffer *eb, int slot, struct btrfs_key *key, struct shared_node *active_node) @@ -1300,6 +1363,8 @@ static int process_dir_item(struct extent_buffer *eb, di = btrfs_item_ptr(eb, slot, struct btrfs_dir_item); total = btrfs_item_size_nr(eb, slot); while (cur < total) { + int ret; + nritems++; btrfs_dir_item_key_to_cpu(eb, di, &location); name_len = btrfs_dir_name_len(eb, di); @@ -1324,10 +1389,12 @@ static int process_dir_item(struct extent_buffer *eb, if (key->type == BTRFS_DIR_ITEM_KEY && key->offset != btrfs_name_hash(namebuf, len)) { - rec->errors |= I_ERR_ODD_DIR_ITEM; - error("DIR_ITEM[%llu %llu] name %s namelen %u filetype %u mismatch with its hash, wanted %llu have %llu", - key->objectid, key->offset, namebuf, len, filetype, - key->offset, btrfs_name_hash(namebuf, len)); + rec->errors |= I_ERR_MISMATCH_DIR_HASH; + ret = add_mismatch_dir_hash(rec, key, namebuf, len); + /* Fatal error, ENOMEM */ + if (ret < 0) + return ret; + goto next; } if (location.type == BTRFS_INODE_ITEM_KEY) { @@ -1348,6 +1415,7 @@ static int process_dir_item(struct extent_buffer *eb, len, filetype, key->type, error); } +next: len = sizeof(*di) + name_len + data_len; di = (struct btrfs_dir_item *)((char *)di + len); cur += len; @@ -2590,6 +2658,41 @@ out: return ret; } +static int repair_mismatch_dir_hash(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct inode_record *rec) +{ + struct mismatch_dir_hash_record *hash; + int ret; + + printf( + "Deleting bad dir items with invalid hash for root %llu ino %llu\n", + root->root_key.objectid, rec->ino); + while (!list_empty(&rec->mismatch_dir_hash)) { + char *namebuf; + + hash = list_entry(rec->mismatch_dir_hash.next, + struct mismatch_dir_hash_record, list); + namebuf = (char *)(hash + 1); + + ret = delete_corrupted_dir_item(trans, root, &hash->key, + namebuf, hash->namelen); + if (ret < 0) + break; + + /* Also reduce dir isize */ + rec->found_size -= hash->namelen; + list_del(&hash->list); + free(hash); + } + if (!ret) { + rec->errors &= ~I_ERR_MISMATCH_DIR_HASH; + /* We rely on later dir isize repair to reset dir isize */ + rec->errors |= I_ERR_DIR_ISIZE_WRONG; + } + return ret; +} + static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec) { struct btrfs_trans_handle *trans; @@ -2603,7 +2706,8 @@ static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec) I_ERR_FILE_EXTENT_ORPHAN | I_ERR_FILE_EXTENT_DISCOUNT | I_ERR_FILE_NBYTES_WRONG | - I_ERR_INLINE_RAM_BYTES_WRONG))) + I_ERR_INLINE_RAM_BYTES_WRONG | + I_ERR_MISMATCH_DIR_HASH))) return rec->errors; /* @@ -2618,6 +2722,8 @@ static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec) return PTR_ERR(trans); btrfs_init_path(&path); + if (!ret && rec->errors & I_ERR_MISMATCH_DIR_HASH) + ret = repair_mismatch_dir_hash(trans, root, rec); if (rec->errors & I_ERR_NO_INODE_ITEM) ret = repair_inode_no_item(trans, root, &path, rec); if (!ret && rec->errors & I_ERR_FILE_EXTENT_ORPHAN) @@ -2712,6 +2818,11 @@ static int check_inode_recs(struct btrfs_root *root, rec = get_inode_rec(inode_cache, root_dirid, 0); BUG_ON(IS_ERR(rec)); if (rec) { + if (repair) { + ret = try_repair_inode(root, rec); + if (ret < 0) + error++; + } ret = check_root_dir(rec); if (ret) { fprintf(stderr, "root %llu root dir %llu error\n", @@ -3362,9 +3473,10 @@ skip_walking: printf("Try to repair the btree for root %llu\n", root->root_key.objectid); ret = repair_btree(root, &corrupt_blocks); - if (ret < 0) + if (ret < 0) { errno = -ret; fprintf(stderr, "Failed to repair btree: %m\n"); + } if (!ret) printf("Btree for root %llu is fixed\n", root->root_key.objectid); @@ -7944,6 +8056,13 @@ static int check_device_used(struct device_record *dev_rec, struct device_extent_record *dev_extent_rec; u64 total_byte = 0; + if (dev_rec->byte_used > dev_rec->total_byte) { + error( + "device %llu has incorrect used bytes %llu > total bytes %llu", + dev_rec->devid, dev_rec->byte_used, dev_rec->total_byte); + return -EUCLEAN; + } + cache = search_cache_extent2(&dext_cache->tree, dev_rec->devid, 0); while (cache) { dev_extent_rec = container_of(cache, @@ -8230,6 +8349,102 @@ out: return ret; } +/* + * Check if all dev extents are valid (not overlapping nor beyond device + * boundary). + * + * Dev extents <-> chunk cross checking is already done in check_chunks(). + */ +static int check_dev_extents(struct btrfs_fs_info *fs_info) +{ + struct btrfs_path path; + struct btrfs_key key; + struct btrfs_root *dev_root = fs_info->dev_root; + int ret; + u64 prev_devid = 0; + u64 prev_dev_ext_end = 0; + + btrfs_init_path(&path); + + key.objectid = 1; + key.type = BTRFS_DEV_EXTENT_KEY; + key.offset = 0; + + ret = btrfs_search_slot(NULL, dev_root, &key, &path, 0, 0); + if (ret < 0) { + errno = -ret; + error("failed to search device tree: %m"); + goto out; + } + if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) { + ret = btrfs_next_leaf(dev_root, &path); + if (ret < 0) { + errno = -ret; + error("failed to find next leaf: %m"); + goto out; + } + if (ret > 0) { + ret = 0; + goto out; + } + } + + while (1) { + struct btrfs_dev_extent *dev_ext; + struct btrfs_device *dev; + u64 devid; + u64 physical_offset; + u64 physical_len; + + btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); + if (key.type != BTRFS_DEV_EXTENT_KEY) + break; + dev_ext = btrfs_item_ptr(path.nodes[0], path.slots[0], + struct btrfs_dev_extent); + devid = key.objectid; + physical_offset = key.offset; + physical_len = btrfs_dev_extent_length(path.nodes[0], dev_ext); + + dev = btrfs_find_device(fs_info, devid, NULL, NULL); + if (!dev) { + error("failed to find device with devid %llu", devid); + ret = -EUCLEAN; + goto out; + } + if (prev_devid == devid && prev_dev_ext_end > physical_offset) { + error( +"dev extent devid %llu physical offset %llu overlap with previous dev extent end %llu", + devid, physical_offset, prev_dev_ext_end); + ret = -EUCLEAN; + goto out; + } + if (physical_offset + physical_len > dev->total_bytes) { + error( +"dev extent devid %llu physical offset %llu len %llu is beyond device boudnary %llu", + devid, physical_offset, physical_len, + dev->total_bytes); + ret = -EUCLEAN; + goto out; + } + prev_devid = devid; + prev_dev_ext_end = physical_offset + physical_len; + + ret = btrfs_next_item(dev_root, &path); + if (ret < 0) { + errno = -ret; + error("failed to find next leaf: %m"); + goto out; + } + if (ret > 0) { + ret = 0; + break; + } + } +out: + btrfs_release_path(&path); + return ret; +} + static int check_chunks_and_extents(struct btrfs_fs_info *fs_info) { struct rb_root dev_cache; @@ -8324,6 +8539,12 @@ again: goto out; } + ret = check_dev_extents(fs_info); + if (ret < 0) { + err = ret; + goto out; + } + ret = check_chunks(&chunk_cache, &block_group_cache, &dev_extent_cache, NULL, NULL, NULL, 0); if (ret) { @@ -8424,7 +8645,7 @@ static int btrfs_fsck_reinit_root(struct btrfs_trans_handle *trans, btrfs_set_header_backref_rev(c, BTRFS_MIXED_BACKREF_REV); btrfs_set_header_owner(c, root->root_key.objectid); - write_extent_buffer(c, root->fs_info->fsid, + write_extent_buffer(c, root->fs_info->fs_devices->metadata_uuid, btrfs_header_fsid(), BTRFS_FSID_SIZE); write_extent_buffer(c, root->fs_info->chunk_tree_uuid, diff --git a/check/mode-common.c b/check/mode-common.c index 2efa4dc5..fed102b0 100644 --- a/check/mode-common.c +++ b/check/mode-common.c @@ -744,3 +744,54 @@ void cleanup_excluded_extents(struct btrfs_fs_info *fs_info) } fs_info->excluded_extents = NULL; } + +/* + * Delete one corrupted dir item whose hash doesn't match its name. + * + * Since its hash is incorrect, we can't use btrfs_name_hash() to calculate + * the search key, but rely on @di_key parameter to do the search. + */ +int delete_corrupted_dir_item(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct btrfs_key *di_key, char *namebuf, + u32 namelen) +{ + struct btrfs_dir_item *di_item; + struct btrfs_path path; + int ret; + + btrfs_init_path(&path); + ret = btrfs_search_slot(trans, root, di_key, &path, 0, 1); + if (ret > 0) { + error("key (%llu %u %llu) doesn't exist in root %llu", + di_key->objectid, di_key->type, di_key->offset, + root->root_key.objectid); + ret = -ENOENT; + goto out; + } + if (ret < 0) { + error("failed to search root %llu: %d", + root->root_key.objectid, ret); + goto out; + } + + di_item = btrfs_match_dir_item_name(root, &path, namebuf, namelen); + if (!di_item) { + /* + * This is possible if the dir_item has incorrect namelen. + * But in that case, we shouldn't reach repair path here. + */ + error("no dir item named '%s' found with key (%llu %u %llu)", + namebuf, di_key->objectid, di_key->type, + di_key->offset); + ret = -ENOENT; + goto out; + } + ret = btrfs_delete_one_dir_name(trans, root, &path, di_item); + if (ret < 0) + error("failed to delete one dir name: %d", ret); + +out: + btrfs_release_path(&path); + return ret; +} diff --git a/check/mode-common.h b/check/mode-common.h index 6b05f8ba..1fb3cda7 100644 --- a/check/mode-common.h +++ b/check/mode-common.h @@ -121,5 +121,9 @@ 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); +int delete_corrupted_dir_item(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct btrfs_key *di_key, char *namebuf, + u32 namelen); #endif diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c index 6fb397ab..fc6228a0 100644 --- a/check/mode-lowmem.c +++ b/check/mode-lowmem.c @@ -1474,16 +1474,52 @@ out: } /* + * A wrapper for delete_corrupted_dir_item(), with support part like + * start/commit transaction. + */ +static int lowmem_delete_corrupted_dir_item(struct btrfs_root *root, + struct btrfs_key *di_key, + char *namebuf, u32 name_len) +{ + struct btrfs_trans_handle *trans; + int ret; + + trans = btrfs_start_transaction(root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + error("failed to start transaction: %d", ret); + return ret; + } + + ret = delete_corrupted_dir_item(trans, root, di_key, namebuf, name_len); + if (ret < 0) { + btrfs_abort_transaction(trans, ret); + } else { + ret = btrfs_commit_transaction(trans, root); + if (ret < 0) + error("failed to commit transaction: %d", ret); + } + return ret; +} + +/* * Call repair_inode_item_missing and repair_ternary_lowmem to repair * * Returns error after repair */ -static int repair_dir_item(struct btrfs_root *root, u64 dirid, u64 ino, - u64 index, u8 filetype, char *namebuf, u32 name_len, - int err) +static int repair_dir_item(struct btrfs_root *root, struct btrfs_key *di_key, + u64 ino, u64 index, u8 filetype, char *namebuf, + u32 name_len, int err) { + u64 dirid = di_key->objectid; int ret; + if (err & (DIR_ITEM_HASH_MISMATCH)) { + ret = lowmem_delete_corrupted_dir_item(root, di_key, namebuf, + name_len); + if (!ret) + err &= ~(DIR_ITEM_HASH_MISMATCH); + } if (err & INODE_ITEM_MISSING) { ret = repair_inode_item_missing(root, ino, filetype); if (!ret) @@ -1631,11 +1667,12 @@ begin: if (di_key->type == BTRFS_DIR_ITEM_KEY && di_key->offset != btrfs_name_hash(namebuf, len)) { - err |= -EIO; error("root %llu DIR_ITEM[%llu %llu] name %s namelen %u filetype %u mismatch with its hash, wanted %llu have %llu", root->objectid, di_key->objectid, di_key->offset, namebuf, len, filetype, di_key->offset, btrfs_name_hash(namebuf, len)); + tmp_err |= DIR_ITEM_HASH_MISMATCH; + goto next; } btrfs_dir_item_key_to_cpu(node, di, &location); @@ -1683,7 +1720,7 @@ begin: next: if (tmp_err && repair) { - ret = repair_dir_item(root, di_key->objectid, + ret = repair_dir_item(root, di_key, location.objectid, index, imode_to_type(mode), namebuf, name_len, tmp_err); @@ -2142,7 +2179,7 @@ out: error("failed to set nbytes in inode %llu root %llu", ino, root->root_key.objectid); else - printf("Set nbytes in inode item %llu root %llu\n to %llu", ino, + printf("Set nbytes in inode item %llu root %llu to %llu\n", ino, root->root_key.objectid, nbytes); /* research path */ @@ -4095,6 +4132,8 @@ static int check_dev_item(struct btrfs_fs_info *fs_info, u64 dev_id; u64 used; u64 total = 0; + u64 prev_devid = 0; + u64 prev_dev_ext_end = 0; int ret; dev_item = btrfs_item_ptr(eb, slot, struct btrfs_dev_item); @@ -4102,6 +4141,12 @@ static int check_dev_item(struct btrfs_fs_info *fs_info, used = btrfs_device_bytes_used(eb, dev_item); total_bytes = btrfs_device_total_bytes(eb, dev_item); + if (used > total_bytes) { + error( + "device %llu has incorrect used bytes %llu > total bytes %llu", + dev_id, used, total_bytes); + return ACCOUNTING_MISMATCH; + } key.objectid = dev_id; key.type = BTRFS_DEV_EXTENT_KEY; key.offset = 0; @@ -4116,8 +4161,16 @@ static int check_dev_item(struct btrfs_fs_info *fs_info, return REFERENCER_MISSING; } - /* Iterate dev_extents to calculate the used space of a device */ + /* + * Iterate dev_extents to calculate the used space of a device + * + * Also make sure no dev extents overlap and end beyond device boundary + */ while (1) { + u64 devid; + u64 physical_offset; + u64 physical_len; + if (path.slots[0] >= btrfs_header_nritems(path.nodes[0])) goto next; @@ -4129,7 +4182,27 @@ static int check_dev_item(struct btrfs_fs_info *fs_info, ptr = btrfs_item_ptr(path.nodes[0], path.slots[0], struct btrfs_dev_extent); - total += btrfs_dev_extent_length(path.nodes[0], ptr); + devid = key.objectid; + physical_offset = key.offset; + physical_len = btrfs_dev_extent_length(path.nodes[0], ptr); + + if (prev_devid == devid && physical_offset < prev_dev_ext_end) { + error( +"dev extent devid %llu offset %llu len %llu overlap with previous dev extent end %llu", + devid, physical_offset, physical_len, + prev_dev_ext_end); + return ACCOUNTING_MISMATCH; + } + if (physical_offset + physical_len > total_bytes) { + error( +"dev extent devid %llu offset %llu len %llu is beyond device boundary %llu", + devid, physical_offset, physical_len, + total_bytes); + return ACCOUNTING_MISMATCH; + } + prev_devid = devid; + prev_dev_ext_end = physical_offset + physical_len; + total += physical_len; next: ret = btrfs_next_item(dev_root, &path); if (ret) diff --git a/check/mode-lowmem.h b/check/mode-lowmem.h index 0ad2a9e3..46b9b191 100644 --- a/check/mode-lowmem.h +++ b/check/mode-lowmem.h @@ -45,6 +45,7 @@ #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 */ +#define DIR_ITEM_HASH_MISMATCH (1<<24) /* Dir item hash mismatch */ /* * Error bit for low memory mode check. diff --git a/check/mode-original.h b/check/mode-original.h index ec2842e0..25ca2741 100644 --- a/check/mode-original.h +++ b/check/mode-original.h @@ -188,6 +188,7 @@ struct file_extent_hole { #define I_ERR_FILE_EXTENT_TOO_LARGE (1 << 15) #define I_ERR_ODD_INODE_FLAGS (1 << 16) #define I_ERR_INLINE_RAM_BYTES_WRONG (1 << 17) +#define I_ERR_MISMATCH_DIR_HASH (1 << 18) struct inode_record { struct list_head backrefs; @@ -213,10 +214,23 @@ struct inode_record { u64 extent_end; struct rb_root holes; struct list_head orphan_extents; + struct list_head mismatch_dir_hash; u32 refs; }; +/* + * To record one dir_item with mismatch hash. + * + * Since the hash is incorrect, we must record the hash (key). + */ +struct mismatch_dir_hash_record { + struct list_head list; + struct btrfs_key key; + int namelen; + /* namebuf follows here */ +}; + struct root_backref { struct list_head list; unsigned int found_dir_item:1; |