diff options
Diffstat (limited to 'fs/ext4/extents_status.c')
| -rw-r--r-- | fs/ext4/extents_status.c | 212 | 
1 files changed, 207 insertions, 5 deletions
| diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c index 95796a1b752..fe3337a85ed 100644 --- a/fs/ext4/extents_status.c +++ b/fs/ext4/extents_status.c @@ -333,17 +333,27 @@ static void ext4_es_free_extent(struct inode *inode, struct extent_status *es)  static int ext4_es_can_be_merged(struct extent_status *es1,  				 struct extent_status *es2)  { -	if (es1->es_lblk + es1->es_len != es2->es_lblk) +	if (ext4_es_status(es1) != ext4_es_status(es2))  		return 0; -	if (ext4_es_status(es1) != ext4_es_status(es2)) +	if (((__u64) es1->es_len) + es2->es_len > 0xFFFFFFFFULL)  		return 0; -	if ((ext4_es_is_written(es1) || ext4_es_is_unwritten(es1)) && -	    (ext4_es_pblock(es1) + es1->es_len != ext4_es_pblock(es2))) +	if (((__u64) es1->es_lblk) + es1->es_len != es2->es_lblk)  		return 0; -	return 1; +	if ((ext4_es_is_written(es1) || ext4_es_is_unwritten(es1)) && +	    (ext4_es_pblock(es1) + es1->es_len == ext4_es_pblock(es2))) +		return 1; + +	if (ext4_es_is_hole(es1)) +		return 1; + +	/* we need to check delayed extent is without unwritten status */ +	if (ext4_es_is_delayed(es1) && !ext4_es_is_unwritten(es1)) +		return 1; + +	return 0;  }  static struct extent_status * @@ -389,6 +399,179 @@ ext4_es_try_to_merge_right(struct inode *inode, struct extent_status *es)  	return es;  } +#ifdef ES_AGGRESSIVE_TEST +static void ext4_es_insert_extent_ext_check(struct inode *inode, +					    struct extent_status *es) +{ +	struct ext4_ext_path *path = NULL; +	struct ext4_extent *ex; +	ext4_lblk_t ee_block; +	ext4_fsblk_t ee_start; +	unsigned short ee_len; +	int depth, ee_status, es_status; + +	path = ext4_ext_find_extent(inode, es->es_lblk, NULL); +	if (IS_ERR(path)) +		return; + +	depth = ext_depth(inode); +	ex = path[depth].p_ext; + +	if (ex) { + +		ee_block = le32_to_cpu(ex->ee_block); +		ee_start = ext4_ext_pblock(ex); +		ee_len = ext4_ext_get_actual_len(ex); + +		ee_status = ext4_ext_is_uninitialized(ex) ? 1 : 0; +		es_status = ext4_es_is_unwritten(es) ? 1 : 0; + +		/* +		 * Make sure ex and es are not overlap when we try to insert +		 * a delayed/hole extent. +		 */ +		if (!ext4_es_is_written(es) && !ext4_es_is_unwritten(es)) { +			if (in_range(es->es_lblk, ee_block, ee_len)) { +				pr_warn("ES insert assertation failed for " +					"inode: %lu we can find an extent " +					"at block [%d/%d/%llu/%c], but we " +					"want to add an delayed/hole extent " +					"[%d/%d/%llu/%llx]\n", +					inode->i_ino, ee_block, ee_len, +					ee_start, ee_status ? 'u' : 'w', +					es->es_lblk, es->es_len, +					ext4_es_pblock(es), ext4_es_status(es)); +			} +			goto out; +		} + +		/* +		 * We don't check ee_block == es->es_lblk, etc. because es +		 * might be a part of whole extent, vice versa. +		 */ +		if (es->es_lblk < ee_block || +		    ext4_es_pblock(es) != ee_start + es->es_lblk - ee_block) { +			pr_warn("ES insert assertation failed for inode: %lu " +				"ex_status [%d/%d/%llu/%c] != " +				"es_status [%d/%d/%llu/%c]\n", inode->i_ino, +				ee_block, ee_len, ee_start, +				ee_status ? 'u' : 'w', es->es_lblk, es->es_len, +				ext4_es_pblock(es), es_status ? 'u' : 'w'); +			goto out; +		} + +		if (ee_status ^ es_status) { +			pr_warn("ES insert assertation failed for inode: %lu " +				"ex_status [%d/%d/%llu/%c] != " +				"es_status [%d/%d/%llu/%c]\n", inode->i_ino, +				ee_block, ee_len, ee_start, +				ee_status ? 'u' : 'w', es->es_lblk, es->es_len, +				ext4_es_pblock(es), es_status ? 'u' : 'w'); +		} +	} else { +		/* +		 * We can't find an extent on disk.  So we need to make sure +		 * that we don't want to add an written/unwritten extent. +		 */ +		if (!ext4_es_is_delayed(es) && !ext4_es_is_hole(es)) { +			pr_warn("ES insert assertation failed for inode: %lu " +				"can't find an extent at block %d but we want " +				"to add an written/unwritten extent " +				"[%d/%d/%llu/%llx]\n", inode->i_ino, +				es->es_lblk, es->es_lblk, es->es_len, +				ext4_es_pblock(es), ext4_es_status(es)); +		} +	} +out: +	if (path) { +		ext4_ext_drop_refs(path); +		kfree(path); +	} +} + +static void ext4_es_insert_extent_ind_check(struct inode *inode, +					    struct extent_status *es) +{ +	struct ext4_map_blocks map; +	int retval; + +	/* +	 * Here we call ext4_ind_map_blocks to lookup a block mapping because +	 * 'Indirect' structure is defined in indirect.c.  So we couldn't +	 * access direct/indirect tree from outside.  It is too dirty to define +	 * this function in indirect.c file. +	 */ + +	map.m_lblk = es->es_lblk; +	map.m_len = es->es_len; + +	retval = ext4_ind_map_blocks(NULL, inode, &map, 0); +	if (retval > 0) { +		if (ext4_es_is_delayed(es) || ext4_es_is_hole(es)) { +			/* +			 * We want to add a delayed/hole extent but this +			 * block has been allocated. +			 */ +			pr_warn("ES insert assertation failed for inode: %lu " +				"We can find blocks but we want to add a " +				"delayed/hole extent [%d/%d/%llu/%llx]\n", +				inode->i_ino, es->es_lblk, es->es_len, +				ext4_es_pblock(es), ext4_es_status(es)); +			return; +		} else if (ext4_es_is_written(es)) { +			if (retval != es->es_len) { +				pr_warn("ES insert assertation failed for " +					"inode: %lu retval %d != es_len %d\n", +					inode->i_ino, retval, es->es_len); +				return; +			} +			if (map.m_pblk != ext4_es_pblock(es)) { +				pr_warn("ES insert assertation failed for " +					"inode: %lu m_pblk %llu != " +					"es_pblk %llu\n", +					inode->i_ino, map.m_pblk, +					ext4_es_pblock(es)); +				return; +			} +		} else { +			/* +			 * We don't need to check unwritten extent because +			 * indirect-based file doesn't have it. +			 */ +			BUG_ON(1); +		} +	} else if (retval == 0) { +		if (ext4_es_is_written(es)) { +			pr_warn("ES insert assertation failed for inode: %lu " +				"We can't find the block but we want to add " +				"an written extent [%d/%d/%llu/%llx]\n", +				inode->i_ino, es->es_lblk, es->es_len, +				ext4_es_pblock(es), ext4_es_status(es)); +			return; +		} +	} +} + +static inline void ext4_es_insert_extent_check(struct inode *inode, +					       struct extent_status *es) +{ +	/* +	 * We don't need to worry about the race condition because +	 * caller takes i_data_sem locking. +	 */ +	BUG_ON(!rwsem_is_locked(&EXT4_I(inode)->i_data_sem)); +	if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) +		ext4_es_insert_extent_ext_check(inode, es); +	else +		ext4_es_insert_extent_ind_check(inode, es); +} +#else +static inline void ext4_es_insert_extent_check(struct inode *inode, +					       struct extent_status *es) +{ +} +#endif +  static int __es_insert_extent(struct inode *inode, struct extent_status *newes)  {  	struct ext4_es_tree *tree = &EXT4_I(inode)->i_es_tree; @@ -471,6 +654,8 @@ int ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk,  	ext4_es_store_status(&newes, status);  	trace_ext4_es_insert_extent(inode, &newes); +	ext4_es_insert_extent_check(inode, &newes); +  	write_lock(&EXT4_I(inode)->i_es_lock);  	err = __es_remove_extent(inode, lblk, end);  	if (err != 0) @@ -669,6 +854,23 @@ int ext4_es_remove_extent(struct inode *inode, ext4_lblk_t lblk,  	return err;  } +int ext4_es_zeroout(struct inode *inode, struct ext4_extent *ex) +{ +	ext4_lblk_t  ee_block; +	ext4_fsblk_t ee_pblock; +	unsigned int ee_len; + +	ee_block  = le32_to_cpu(ex->ee_block); +	ee_len    = ext4_ext_get_actual_len(ex); +	ee_pblock = ext4_ext_pblock(ex); + +	if (ee_len == 0) +		return 0; + +	return ext4_es_insert_extent(inode, ee_block, ee_len, ee_pblock, +				     EXTENT_STATUS_WRITTEN); +} +  static int ext4_es_shrink(struct shrinker *shrink, struct shrink_control *sc)  {  	struct ext4_sb_info *sbi = container_of(shrink, |