Commit 3f24fcda authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull ext4 fixes from Ted Ts'o:
 "Miscellaneous bug fixes and cleanups in ext4's multi-block allocator
  and extent handling code"

* tag 'for-linus-6.8-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4: (23 commits)
  ext4: make ext4_set_iomap() recognize IOMAP_DELALLOC map type
  ext4: make ext4_map_blocks() distinguish delalloc only extent
  ext4: add a hole extent entry in cache after punch
  ext4: correct the hole length returned by ext4_map_blocks()
  ext4: convert to exclusive lock while inserting delalloc extents
  ext4: refactor ext4_da_map_blocks()
  ext4: remove 'needed' in trace_ext4_discard_preallocations
  ext4: remove unnecessary parameter "needed" in ext4_discard_preallocations
  ext4: remove unused return value of ext4_mb_release_group_pa
  ext4: remove unused return value of ext4_mb_release_inode_pa
  ext4: remove unused return value of ext4_mb_release
  ext4: remove unused ext4_allocation_context::ac_groups_considered
  ext4: remove unneeded return value of ext4_mb_release_context
  ext4: remove unused parameter ngroup in ext4_mb_choose_next_group_*()
  ext4: remove unused return value of __mb_check_buddy
  ext4: mark the group block bitmap as corrupted before reporting an error
  ext4: avoid allocating blocks from corrupted group in ext4_mb_find_by_goal()
  ext4: avoid allocating blocks from corrupted group in ext4_mb_try_best_found()
  ext4: avoid dividing by 0 in mb_update_avg_fragment_size() when block bitmap corrupt
  ext4: avoid bb_free and bb_fragments inconsistency in mb_free_blocks()
  ...
parents 9e28c7a2 ec9d669e
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -252,8 +252,10 @@ struct ext4_allocation_request {
#define EXT4_MAP_MAPPED		BIT(BH_Mapped)
#define EXT4_MAP_UNWRITTEN	BIT(BH_Unwritten)
#define EXT4_MAP_BOUNDARY	BIT(BH_Boundary)
#define EXT4_MAP_DELAYED	BIT(BH_Delay)
#define EXT4_MAP_FLAGS		(EXT4_MAP_NEW | EXT4_MAP_MAPPED |\
				 EXT4_MAP_UNWRITTEN | EXT4_MAP_BOUNDARY)
				 EXT4_MAP_UNWRITTEN | EXT4_MAP_BOUNDARY |\
				 EXT4_MAP_DELAYED)

struct ext4_map_blocks {
	ext4_fsblk_t m_pblk;
@@ -2912,10 +2914,10 @@ extern const struct seq_operations ext4_mb_seq_groups_ops;
extern const struct seq_operations ext4_mb_seq_structs_summary_ops;
extern int ext4_seq_mb_stats_show(struct seq_file *seq, void *offset);
extern int ext4_mb_init(struct super_block *);
extern int ext4_mb_release(struct super_block *);
extern void ext4_mb_release(struct super_block *);
extern ext4_fsblk_t ext4_mb_new_blocks(handle_t *,
				struct ext4_allocation_request *, int *);
extern void ext4_discard_preallocations(struct inode *, unsigned int);
extern void ext4_discard_preallocations(struct inode *);
extern int __init ext4_init_mballoc(void);
extern void ext4_exit_mballoc(void);
extern ext4_group_t ext4_mb_prefetch(struct super_block *sb,
+78 −46
Original line number Diff line number Diff line
@@ -100,7 +100,7 @@ static int ext4_ext_trunc_restart_fn(struct inode *inode, int *dropped)
	 * i_rwsem. So we can safely drop the i_data_sem here.
	 */
	BUG_ON(EXT4_JOURNAL(inode) == NULL);
	ext4_discard_preallocations(inode, 0);
	ext4_discard_preallocations(inode);
	up_write(&EXT4_I(inode)->i_data_sem);
	*dropped = 1;
	return 0;
@@ -2229,7 +2229,7 @@ static int ext4_fill_es_cache_info(struct inode *inode,


/*
 * ext4_ext_determine_hole - determine hole around given block
 * ext4_ext_find_hole - find hole around given block according to the given path
 * @inode:	inode we lookup in
 * @path:	path in extent tree to @lblk
 * @lblk:	pointer to logical block around which we want to determine hole
@@ -2241,7 +2241,7 @@ static int ext4_fill_es_cache_info(struct inode *inode,
 * The function returns the length of a hole starting at @lblk. We update @lblk
 * to the beginning of the hole if we managed to find it.
 */
static ext4_lblk_t ext4_ext_determine_hole(struct inode *inode,
static ext4_lblk_t ext4_ext_find_hole(struct inode *inode,
				      struct ext4_ext_path *path,
				      ext4_lblk_t *lblk)
{
@@ -2270,30 +2270,6 @@ static ext4_lblk_t ext4_ext_determine_hole(struct inode *inode,
	return len;
}

/*
 * ext4_ext_put_gap_in_cache:
 * calculate boundaries of the gap that the requested block fits into
 * and cache this gap
 */
static void
ext4_ext_put_gap_in_cache(struct inode *inode, ext4_lblk_t hole_start,
			  ext4_lblk_t hole_len)
{
	struct extent_status es;

	ext4_es_find_extent_range(inode, &ext4_es_is_delayed, hole_start,
				  hole_start + hole_len - 1, &es);
	if (es.es_len) {
		/* There's delayed extent containing lblock? */
		if (es.es_lblk <= hole_start)
			return;
		hole_len = min(es.es_lblk - hole_start, hole_len);
	}
	ext_debug(inode, " -> %u:%u\n", hole_start, hole_len);
	ext4_es_insert_extent(inode, hole_start, hole_len, ~0,
			      EXTENT_STATUS_HOLE);
}

/*
 * ext4_ext_rm_idx:
 * removes index from the index block.
@@ -4062,6 +4038,72 @@ static int get_implied_cluster_alloc(struct super_block *sb,
	return 0;
}

/*
 * 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.
 */
static ext4_lblk_t ext4_ext_determine_insert_hole(struct inode *inode,
						  struct ext4_ext_path *path,
						  ext4_lblk_t lblk)
{
	ext4_lblk_t hole_start, 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);
	if (!es.es_len)
		goto insert_hole;

	/*
	 * There's a delalloc extent in the hole, handle it if the delalloc
	 * extent is in front of, behind and straddle the queried range.
	 */
	if (lblk >= es.es_lblk + es.es_len) {
		/*
		 * The delalloc extent is in front of the queried range,
		 * find again from the queried start block.
		 */
		len -= lblk - hole_start;
		hole_start = lblk;
		goto again;
	} else if (in_range(lblk, es.es_lblk, es.es_len)) {
		/*
		 * The delalloc extent containing lblk, it must have been
		 * added after ext4_map_blocks() checked the extent status
		 * tree so we are not holding i_rwsem and delalloc info is
		 * only stabilized by i_data_sem we are going to release
		 * soon. Don't modify the extent status tree and report
		 * extent as a hole, just adjust the length to the delalloc
		 * extent's after lblk.
		 */
		len = es.es_lblk + es.es_len - lblk;
		return len;
	} else {
		/*
		 * The delalloc extent is partially or completely behind
		 * the queried range, update hole length until the
		 * beginning of the delalloc extent.
		 */
		len = min(es.es_lblk - hole_start, len);
	}

insert_hole:
	/* Put just found gap into cache to speed up subsequent requests */
	ext_debug(inode, " -> %u:%u\n", hole_start, len);
	ext4_es_insert_extent(inode, hole_start, len, ~0, EXTENT_STATUS_HOLE);

	/* Update hole_len to reflect hole size after lblk */
	if (hole_start != lblk)
		len -= lblk - hole_start;

	return len;
}

/*
 * Block allocation/map/preallocation routine for extents based files
@@ -4179,22 +4221,12 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
	 * we couldn't try to create block if create flag is zero
	 */
	if ((flags & EXT4_GET_BLOCKS_CREATE) == 0) {
		ext4_lblk_t hole_start, hole_len;
		ext4_lblk_t len;

		hole_start = map->m_lblk;
		hole_len = ext4_ext_determine_hole(inode, path, &hole_start);
		/*
		 * put just found gap into cache to speed up
		 * subsequent requests
		 */
		ext4_ext_put_gap_in_cache(inode, hole_start, hole_len);
		len = ext4_ext_determine_insert_hole(inode, path, map->m_lblk);

		/* Update hole_len to reflect hole size after map->m_lblk */
		if (hole_start != map->m_lblk)
			hole_len -= map->m_lblk - hole_start;
		map->m_pblk = 0;
		map->m_len = min_t(unsigned int, map->m_len, hole_len);

		map->m_len = min_t(unsigned int, map->m_len, len);
		goto out;
	}

@@ -4313,7 +4345,7 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
			 * not a good idea to call discard here directly,
			 * but otherwise we'd need to call it every free().
			 */
			ext4_discard_preallocations(inode, 0);
			ext4_discard_preallocations(inode);
			if (flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE)
				fb_flags = EXT4_FREE_BLOCKS_NO_QUOT_UPDATE;
			ext4_free_blocks(handle, inode, NULL, newblock,
@@ -5357,7 +5389,7 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len)
	ext4_fc_mark_ineligible(sb, EXT4_FC_REASON_FALLOC_RANGE, handle);

	down_write(&EXT4_I(inode)->i_data_sem);
	ext4_discard_preallocations(inode, 0);
	ext4_discard_preallocations(inode);
	ext4_es_remove_extent(inode, punch_start, EXT_MAX_BLOCKS - punch_start);

	ret = ext4_ext_remove_space(inode, punch_start, punch_stop - 1);
@@ -5365,7 +5397,7 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len)
		up_write(&EXT4_I(inode)->i_data_sem);
		goto out_stop;
	}
	ext4_discard_preallocations(inode, 0);
	ext4_discard_preallocations(inode);

	ret = ext4_ext_shift_extents(inode, handle, punch_stop,
				     punch_stop - punch_start, SHIFT_LEFT);
@@ -5497,7 +5529,7 @@ static int ext4_insert_range(struct file *file, loff_t offset, loff_t len)
		goto out_stop;

	down_write(&EXT4_I(inode)->i_data_sem);
	ext4_discard_preallocations(inode, 0);
	ext4_discard_preallocations(inode);

	path = ext4_find_extent(inode, offset_lblk, NULL, 0);
	if (IS_ERR(path)) {
+1 −1
Original line number Diff line number Diff line
@@ -174,7 +174,7 @@ static int ext4_release_file(struct inode *inode, struct file *filp)
			(atomic_read(&inode->i_writecount) == 1) &&
			!EXT4_I(inode)->i_reserved_data_blocks) {
		down_write(&EXT4_I(inode)->i_data_sem);
		ext4_discard_preallocations(inode, 0);
		ext4_discard_preallocations(inode);
		up_write(&EXT4_I(inode)->i_data_sem);
	}
	if (is_dx(inode) && filp->private_data)
+1 −1
Original line number Diff line number Diff line
@@ -714,7 +714,7 @@ static int ext4_ind_trunc_restart_fn(handle_t *handle, struct inode *inode,
	 * i_rwsem. So we can safely drop the i_data_sem here.
	 */
	BUG_ON(EXT4_JOURNAL(inode) == NULL);
	ext4_discard_preallocations(inode, 0);
	ext4_discard_preallocations(inode);
	up_write(&EXT4_I(inode)->i_data_sem);
	*dropped = 1;
	return 0;
+30 −60
Original line number Diff line number Diff line
@@ -371,7 +371,7 @@ void ext4_da_update_reserve_space(struct inode *inode,
	 */
	if ((ei->i_reserved_data_blocks == 0) &&
	    !inode_is_open_for_write(inode))
		ext4_discard_preallocations(inode, 0);
		ext4_discard_preallocations(inode);
}

static int __check_block_validity(struct inode *inode, const char *func,
@@ -515,6 +515,8 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
			map->m_len = retval;
		} else if (ext4_es_is_delayed(&es) || ext4_es_is_hole(&es)) {
			map->m_pblk = 0;
			map->m_flags |= ext4_es_is_delayed(&es) ?
					EXT4_MAP_DELAYED : 0;
			retval = es.es_len - (map->m_lblk - es.es_lblk);
			if (retval > map->m_len)
				retval = map->m_len;
@@ -1703,11 +1705,8 @@ static int ext4_da_map_blocks(struct inode *inode, sector_t iblock,

	/* Lookup extent status tree firstly */
	if (ext4_es_lookup_extent(inode, iblock, NULL, &es)) {
		if (ext4_es_is_hole(&es)) {
			retval = 0;
			down_read(&EXT4_I(inode)->i_data_sem);
		if (ext4_es_is_hole(&es))
			goto add_delayed;
		}

		/*
		 * Delayed extent could be allocated by fallocate.
@@ -1749,26 +1748,11 @@ static int ext4_da_map_blocks(struct inode *inode, sector_t iblock,
		retval = ext4_ext_map_blocks(NULL, inode, map, 0);
	else
		retval = ext4_ind_map_blocks(NULL, inode, map, 0);

add_delayed:
	if (retval == 0) {
		int ret;

		/*
		 * XXX: __block_prepare_write() unmaps passed block,
		 * is it OK?
		 */

		ret = ext4_insert_delayed_block(inode, map->m_lblk);
		if (ret != 0) {
			retval = ret;
			goto out_unlock;
	if (retval < 0) {
		up_read(&EXT4_I(inode)->i_data_sem);
		return retval;
	}

		map_bh(bh, inode->i_sb, invalid_block);
		set_buffer_new(bh);
		set_buffer_delay(bh);
	} else if (retval > 0) {
	if (retval > 0) {
		unsigned int status;

		if (unlikely(retval != map->m_len)) {
@@ -1783,11 +1767,21 @@ static int ext4_da_map_blocks(struct inode *inode, sector_t iblock,
				EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;
		ext4_es_insert_extent(inode, map->m_lblk, map->m_len,
				      map->m_pblk, status);
		up_read(&EXT4_I(inode)->i_data_sem);
		return retval;
	}
	up_read(&EXT4_I(inode)->i_data_sem);

out_unlock:
	up_read((&EXT4_I(inode)->i_data_sem));
add_delayed:
	down_write(&EXT4_I(inode)->i_data_sem);
	retval = ext4_insert_delayed_block(inode, map->m_lblk);
	up_write(&EXT4_I(inode)->i_data_sem);
	if (retval)
		return retval;

	map_bh(bh, inode->i_sb, invalid_block);
	set_buffer_new(bh);
	set_buffer_delay(bh);
	return retval;
}

@@ -3268,6 +3262,9 @@ static void ext4_set_iomap(struct inode *inode, struct iomap *iomap,
		iomap->addr = (u64) map->m_pblk << blkbits;
		if (flags & IOMAP_DAX)
			iomap->addr += EXT4_SB(inode->i_sb)->s_dax_part_off;
	} else if (map->m_flags & EXT4_MAP_DELAYED) {
		iomap->type = IOMAP_DELALLOC;
		iomap->addr = IOMAP_NULL_ADDR;
	} else {
		iomap->type = IOMAP_HOLE;
		iomap->addr = IOMAP_NULL_ADDR;
@@ -3430,35 +3427,11 @@ const struct iomap_ops ext4_iomap_overwrite_ops = {
	.iomap_end		= ext4_iomap_end,
};

static bool ext4_iomap_is_delalloc(struct inode *inode,
				   struct ext4_map_blocks *map)
{
	struct extent_status es;
	ext4_lblk_t offset = 0, end = map->m_lblk + map->m_len - 1;

	ext4_es_find_extent_range(inode, &ext4_es_is_delayed,
				  map->m_lblk, end, &es);

	if (!es.es_len || es.es_lblk > end)
		return false;

	if (es.es_lblk > map->m_lblk) {
		map->m_len = es.es_lblk - map->m_lblk;
		return false;
	}

	offset = map->m_lblk - es.es_lblk;
	map->m_len = es.es_len - offset;

	return true;
}

static int ext4_iomap_begin_report(struct inode *inode, loff_t offset,
				   loff_t length, unsigned int flags,
				   struct iomap *iomap, struct iomap *srcmap)
{
	int ret;
	bool delalloc = false;
	struct ext4_map_blocks map;
	u8 blkbits = inode->i_blkbits;

@@ -3499,13 +3472,8 @@ static int ext4_iomap_begin_report(struct inode *inode, loff_t offset,
	ret = ext4_map_blocks(NULL, inode, &map, 0);
	if (ret < 0)
		return ret;
	if (ret == 0)
		delalloc = ext4_iomap_is_delalloc(inode, &map);

set_iomap:
	ext4_set_iomap(inode, iomap, &map, offset, length, flags);
	if (delalloc && iomap->type == IOMAP_HOLE)
		iomap->type = IOMAP_DELALLOC;

	return 0;
}
@@ -4015,12 +3983,12 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length)

	/* If there are blocks to remove, do it */
	if (stop_block > first_block) {
		ext4_lblk_t hole_len = stop_block - first_block;

		down_write(&EXT4_I(inode)->i_data_sem);
		ext4_discard_preallocations(inode, 0);
		ext4_discard_preallocations(inode);

		ext4_es_remove_extent(inode, first_block,
				      stop_block - first_block);
		ext4_es_remove_extent(inode, first_block, hole_len);

		if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
			ret = ext4_ext_remove_space(inode, first_block,
@@ -4029,6 +3997,8 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length)
			ret = ext4_ind_remove_space(handle, inode, first_block,
						    stop_block);

		ext4_es_insert_extent(inode, first_block, hole_len, ~0,
				      EXTENT_STATUS_HOLE);
		up_write(&EXT4_I(inode)->i_data_sem);
	}
	ext4_fc_track_range(handle, inode, first_block, stop_block);
@@ -4170,7 +4140,7 @@ int ext4_truncate(struct inode *inode)

	down_write(&EXT4_I(inode)->i_data_sem);

	ext4_discard_preallocations(inode, 0);
	ext4_discard_preallocations(inode);

	if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
		err = ext4_ext_truncate(handle, inode);
Loading