diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 724a27e8be61..1edc4a94bb7d 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -3905,6 +3905,9 @@ extern void ext4_ext_tree_init(handle_t *handle, struct inode *inode); extern int ext4_ext_index_trans_blocks(struct inode *inode, int extents); extern int ext4_ext_map_blocks(handle_t *handle, struct inode *inode, struct ext4_map_blocks *map, int flags); +ext4_lblk_t ext4_determine_insert_hole(struct inode *inode, ext4_lblk_t lblk, + ext4_lblk_t hole_start, + ext4_lblk_t hole_len); extern int ext4_ext_truncate(handle_t *, struct inode *); extern int ext4_ext_remove_space(struct inode *inode, ext4_lblk_t start, ext4_lblk_t end); diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 76038b6c3655..5409b4c07c3c 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -4190,21 +4190,19 @@ static int get_implied_cluster_alloc(struct super_block *sb, } /* - * Determine hole length around the given logical block, first try to - * locate and expand the hole from the given @path, and then adjust it - * if it's partially or completely converted to delayed extents, insert - * it into the extent cache tree if it's indeed a hole, finally return - * the length of the determined extent. + * Adjust the hole [@hole_start, @hole_start + @hole_len) the caller found + * around the queried block @lblk if it's partially or completely converted + * to delayed extents, insert it into the extent cache tree if it's indeed a + * hole, finally return the length of the determined extent starting at + * @lblk. @hole_start must not be behind @lblk. */ -static ext4_lblk_t ext4_ext_determine_insert_hole(struct inode *inode, - struct ext4_ext_path *path, - ext4_lblk_t lblk) +ext4_lblk_t ext4_determine_insert_hole(struct inode *inode, ext4_lblk_t lblk, + ext4_lblk_t hole_start, + ext4_lblk_t hole_len) { - ext4_lblk_t hole_start, len; + ext4_lblk_t len = hole_len; struct extent_status es; - hole_start = lblk; - len = ext4_ext_find_hole(inode, path, &hole_start); again: ext4_es_find_extent_range(inode, &ext4_es_is_delayed, hole_start, hole_start + len - 1, &es); @@ -4371,9 +4369,11 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode, * we couldn't try to create block if flags doesn't contain EXT4_GET_BLOCKS_CREATE */ if ((flags & EXT4_GET_BLOCKS_CREATE) == 0) { - ext4_lblk_t len; + ext4_lblk_t hole_start = map->m_lblk, len; - len = ext4_ext_determine_insert_hole(inode, path, map->m_lblk); + len = ext4_ext_find_hole(inode, path, &hole_start); + len = ext4_determine_insert_hole(inode, map->m_lblk, + hole_start, len); map->m_pblk = 0; map->m_len = min_t(unsigned int, map->m_len, len); diff --git a/fs/ext4/indirect.c b/fs/ext4/indirect.c index 5aec759eed70..a495080df710 100644 --- a/fs/ext4/indirect.c +++ b/fs/ext4/indirect.c @@ -586,6 +586,23 @@ int ext4_ind_map_blocks(handle_t *handle, struct inode *inode, for (i = partial - chain + 1; i < depth; i++) count = count * epb + (epb - offsets[i] - 1); count++; + + /* + * The count knows nothing about delayed allocation, so let + * the common helper reconcile it with the extent status + * tree. Only when the lookup really did find a hole: + * ext4_get_branch() also returns a non-NULL partial when it + * failed, and caching a hole for a range it could not read + * would answer every later read from that cache instead of + * retrying. Clamp the count first, a subtree can be bigger + * than the logical block space when the block size is large. + */ + if (!err) { + count = umin(count, EXT_MAX_BLOCKS - map->m_lblk); + count = ext4_determine_insert_hole(inode, map->m_lblk, + map->m_lblk, count); + } + /* Fill in size of a hole we found */ map->m_pblk = 0; map->m_len = umin(map->m_len, count);