diff options
Diffstat (limited to 'fs/btrfs/disk-io.c')
| -rw-r--r-- | fs/btrfs/disk-io.c | 75 | 
1 files changed, 74 insertions, 1 deletions
| diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index eff0dd1ae62f..f09db62e61a1 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -226,7 +226,7 @@ int btrfs_read_extent_buffer(struct extent_buffer *eb,  	while (1) {  		clear_bit(EXTENT_BUFFER_CORRUPT, &eb->bflags); -		ret = read_extent_buffer_pages(eb, WAIT_COMPLETE, mirror_num, check); +		ret = read_extent_buffer_pages(eb, mirror_num, check);  		if (!ret)  			break; @@ -1258,6 +1258,7 @@ void btrfs_free_fs_info(struct btrfs_fs_info *fs_info)  {  	struct percpu_counter *em_counter = &fs_info->evictable_extent_maps; +	percpu_counter_destroy(&fs_info->stats_read_blocks);  	percpu_counter_destroy(&fs_info->dirty_metadata_bytes);  	percpu_counter_destroy(&fs_info->delalloc_bytes);  	percpu_counter_destroy(&fs_info->ordered_bytes); @@ -2327,6 +2328,71 @@ out:  	return ret;  } +static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, +				    const struct btrfs_super_block *sb) +{ +	unsigned int cur = 0; /* Offset inside the sys chunk array */ +	/* +	 * At sb read time, fs_info is not fully initialized. Thus we have +	 * to use super block sectorsize, which should have been validated. +	 */ +	const u32 sectorsize = btrfs_super_sectorsize(sb); +	u32 sys_array_size = btrfs_super_sys_array_size(sb); + +	if (sys_array_size > BTRFS_SYSTEM_CHUNK_ARRAY_SIZE) { +		btrfs_err(fs_info, "system chunk array too big %u > %u", +			  sys_array_size, BTRFS_SYSTEM_CHUNK_ARRAY_SIZE); +		return -EUCLEAN; +	} + +	while (cur < sys_array_size) { +		struct btrfs_disk_key *disk_key; +		struct btrfs_chunk *chunk; +		struct btrfs_key key; +		u64 type; +		u16 num_stripes; +		u32 len; +		int ret; + +		disk_key = (struct btrfs_disk_key *)(sb->sys_chunk_array + cur); +		len = sizeof(*disk_key); + +		if (cur + len > sys_array_size) +			goto short_read; +		cur += len; + +		btrfs_disk_key_to_cpu(&key, disk_key); +		if (key.type != BTRFS_CHUNK_ITEM_KEY) { +			btrfs_err(fs_info, +			    "unexpected item type %u in sys_array at offset %u", +				  key.type, cur); +			return -EUCLEAN; +		} +		chunk = (struct btrfs_chunk *)(sb->sys_chunk_array + cur); +		num_stripes = btrfs_stack_chunk_num_stripes(chunk); +		if (cur + btrfs_chunk_item_size(num_stripes) > sys_array_size) +			goto short_read; +		type = btrfs_stack_chunk_type(chunk); +		if (!(type & BTRFS_BLOCK_GROUP_SYSTEM)) { +			btrfs_err(fs_info, +			"invalid chunk type %llu in sys_array at offset %u", +				  type, cur); +			return -EUCLEAN; +		} +		ret = btrfs_check_chunk_valid(fs_info, NULL, chunk, key.offset, +					      sectorsize); +		if (ret < 0) +			return ret; +		cur += btrfs_chunk_item_size(num_stripes); +	} +	return 0; +short_read: +	btrfs_err(fs_info, +	"super block sys chunk array short read, cur=%u sys_array_size=%u", +		  cur, sys_array_size); +	return -EUCLEAN; +} +  /*   * Real super block validation   * NOTE: super csum type and incompat features will not be checked here. @@ -2495,6 +2561,8 @@ int btrfs_validate_super(const struct btrfs_fs_info *fs_info,  		ret = -EINVAL;  	} +	ret = validate_sys_chunk_array(fs_info, sb); +  	/*  	 * Obvious sys_chunk_array corruptions, it must hold at least one key  	 * and one chunk @@ -2856,6 +2924,10 @@ static int init_mount_fs_info(struct btrfs_fs_info *fs_info, struct super_block  	if (ret)  		return ret; +	ret = percpu_counter_init(&fs_info->stats_read_blocks, 0, GFP_KERNEL); +	if (ret) +		return ret; +  	fs_info->dirty_metadata_batch = PAGE_SIZE *  					(1 + ilog2(nr_cpu_ids)); @@ -3321,6 +3393,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device  	fs_info->sectors_per_page = (PAGE_SIZE >> fs_info->sectorsize_bits);  	fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) / fs_info->csum_size;  	fs_info->stripesize = stripesize; +	fs_info->fs_devices->fs_info = fs_info;  	/*  	 * Handle the space caching options appropriately now that we have the | 
