diff options
| -rw-r--r-- | fs/ext4/ext4.h | 18 | ||||
| -rw-r--r-- | fs/ext4/extents.c | 12 | ||||
| -rw-r--r-- | fs/ext4/inode.c | 90 | 
3 files changed, 112 insertions, 8 deletions
| diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 8fda9cdfc13d..635a95a7f715 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -256,9 +256,19 @@ struct ext4_allocation_request {  #define EXT4_MAP_UNWRITTEN	BIT(BH_Unwritten)  #define EXT4_MAP_BOUNDARY	BIT(BH_Boundary)  #define EXT4_MAP_DELAYED	BIT(BH_Delay) +/* + * This is for use in ext4_map_query_blocks() for a special case where we can + * have a physically and logically contiguous blocks split across two leaf + * nodes instead of a single extent. This is required in case of atomic writes + * to know whether the returned extent is last in leaf. If yes, then lookup for + * next in leaf block in ext4_map_query_blocks_next_in_leaf(). + * - This is never going to be added to any buffer head state. + * - We use the next available bit after BH_BITMAP_UPTODATE. + */ +#define EXT4_MAP_QUERY_LAST_IN_LEAF	BIT(BH_BITMAP_UPTODATE + 1)  #define EXT4_MAP_FLAGS		(EXT4_MAP_NEW | EXT4_MAP_MAPPED |\  				 EXT4_MAP_UNWRITTEN | EXT4_MAP_BOUNDARY |\ -				 EXT4_MAP_DELAYED) +				 EXT4_MAP_DELAYED | EXT4_MAP_QUERY_LAST_IN_LEAF)  struct ext4_map_blocks {  	ext4_fsblk_t m_pblk; @@ -728,6 +738,12 @@ enum {  					 EXT4_GET_BLOCKS_IO_SUBMIT)  	/* Caller is in the atomic contex, find extent if it has been cached */  #define EXT4_GET_BLOCKS_CACHED_NOWAIT		0x0800 +/* + * Atomic write caller needs this to query in the slow path of mixed mapping + * case, when a contiguous extent can be split across two adjacent leaf nodes. + * Look EXT4_MAP_QUERY_LAST_IN_LEAF. + */ +#define EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF	0x1000  /*   * The bit position of these flags must not overlap with any of the diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 995be1051cfa..ba91ad3dfcbc 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -4437,6 +4437,18 @@ got_allocated_blocks:  	allocated = map->m_len;  	ext4_ext_show_leaf(inode, path);  out: +	/* +	 * We never use EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF with CREATE flag. +	 * So we know that the depth used here is correct, since there was no +	 * block allocation done if EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF is set. +	 * If tomorrow we start using this QUERY flag with CREATE, then we will +	 * need to re-calculate the depth as it might have changed due to block +	 * allocation. +	 */ +	if (flags & EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF) +		if (!err && ex && (ex == EXT_LAST_EXTENT(path[depth].p_hdr))) +			map->m_flags |= EXT4_MAP_QUERY_LAST_IN_LEAF; +  	ext4_free_ext_path(path);  	trace_ext4_ext_map_blocks_exit(inode, flags, map, diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 72dc1314925f..14c19a38cd4f 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -483,15 +483,73 @@ static void ext4_map_blocks_es_recheck(handle_t *handle,  }  #endif /* ES_AGGRESSIVE_TEST */ +static int ext4_map_query_blocks_next_in_leaf(handle_t *handle, +			struct inode *inode, struct ext4_map_blocks *map, +			unsigned int orig_mlen) +{ +	struct ext4_map_blocks map2; +	unsigned int status, status2; +	int retval; + +	status = map->m_flags & EXT4_MAP_UNWRITTEN ? +		EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; + +	WARN_ON_ONCE(!(map->m_flags & EXT4_MAP_QUERY_LAST_IN_LEAF)); +	WARN_ON_ONCE(orig_mlen <= map->m_len); + +	/* Prepare map2 for lookup in next leaf block */ +	map2.m_lblk = map->m_lblk + map->m_len; +	map2.m_len = orig_mlen - map->m_len; +	map2.m_flags = 0; +	retval = ext4_ext_map_blocks(handle, inode, &map2, 0); + +	if (retval <= 0) { +		ext4_es_insert_extent(inode, map->m_lblk, map->m_len, +				      map->m_pblk, status, false); +		return map->m_len; +	} + +	if (unlikely(retval != map2.m_len)) { +		ext4_warning(inode->i_sb, +			     "ES len assertion failed for inode " +			     "%lu: retval %d != map->m_len %d", +			     inode->i_ino, retval, map2.m_len); +		WARN_ON(1); +	} + +	status2 = map2.m_flags & EXT4_MAP_UNWRITTEN ? +		EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; + +	/* +	 * If map2 is contiguous with map, then let's insert it as a single +	 * extent in es cache and return the combined length of both the maps. +	 */ +	if (map->m_pblk + map->m_len == map2.m_pblk && +			status == status2) { +		ext4_es_insert_extent(inode, map->m_lblk, +				      map->m_len + map2.m_len, map->m_pblk, +				      status, false); +		map->m_len += map2.m_len; +	} else { +		ext4_es_insert_extent(inode, map->m_lblk, map->m_len, +				      map->m_pblk, status, false); +	} + +	return map->m_len; +} +  static int ext4_map_query_blocks(handle_t *handle, struct inode *inode,  				 struct ext4_map_blocks *map, int flags)  {  	unsigned int status;  	int retval; +	unsigned int orig_mlen = map->m_len; +	unsigned int query_flags = flags & EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF;  	flags &= EXT4_EX_FILTER;  	if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) -		retval = ext4_ext_map_blocks(handle, inode, map, flags); +		retval = ext4_ext_map_blocks(handle, inode, map, +					     flags | query_flags);  	else  		retval = ext4_ind_map_blocks(handle, inode, map, flags); @@ -506,11 +564,23 @@ static int ext4_map_query_blocks(handle_t *handle, struct inode *inode,  		WARN_ON(1);  	} -	status = map->m_flags & EXT4_MAP_UNWRITTEN ? -			EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; -	ext4_es_insert_extent(inode, map->m_lblk, map->m_len, -			      map->m_pblk, status, false); -	return retval; +	/* +	 * No need to query next in leaf: +	 * - if returned extent is not last in leaf or +	 * - if the last in leaf is the full requested range +	 */ +	if (!(map->m_flags & EXT4_MAP_QUERY_LAST_IN_LEAF) || +			((map->m_flags & EXT4_MAP_QUERY_LAST_IN_LEAF) && +			 (map->m_len == orig_mlen))) { +		status = map->m_flags & EXT4_MAP_UNWRITTEN ? +				EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; +		ext4_es_insert_extent(inode, map->m_lblk, map->m_len, +				      map->m_pblk, status, false); +		return retval; +	} + +	return ext4_map_query_blocks_next_in_leaf(handle, inode, map, +						  orig_mlen);  }  static int ext4_map_create_blocks(handle_t *handle, struct inode *inode, @@ -624,6 +694,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,  	struct extent_status es;  	int retval;  	int ret = 0; +	unsigned int orig_mlen = map->m_len;  #ifdef ES_AGGRESSIVE_TEST  	struct ext4_map_blocks orig_map; @@ -685,7 +756,12 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,  		ext4_map_blocks_es_recheck(handle, inode, map,  					   &orig_map, flags);  #endif -		goto found; +		if (!(flags & EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF) || +				orig_mlen == map->m_len) +			goto found; + +		if (flags & EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF) +			map->m_len = orig_mlen;  	}  	/*  	 * In the query cache no-wait mode, nothing we can do more if we | 
