Commit 4a622e4d authored by Theodore Ts'o's avatar Theodore Ts'o
Browse files

ext4: fix FS_IOC_GETFSMAP handling



The original implementation ext4's FS_IOC_GETFSMAP handling only
worked when the range of queried blocks included at least one free
(unallocated) block range.  This is because how the metadata blocks
were emitted was as a side effect of ext4_mballoc_query_range()
calling ext4_getfsmap_datadev_helper(), and that function was only
called when a free block range was identified.  As a result, this
caused generic/365 to fail.

Fix this by creating a new function ext4_getfsmap_meta_helper() which
gets called so that blocks before the first free block range in a
block group can get properly reported.

Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
Cc: stable@vger.kernel.org
parent 40eb3104
Loading
Loading
Loading
Loading
+53 −1
Original line number Diff line number Diff line
@@ -185,6 +185,56 @@ static inline ext4_fsblk_t ext4_fsmap_next_pblk(struct ext4_fsmap *fmr)
	return fmr->fmr_physical + fmr->fmr_length;
}

static int ext4_getfsmap_meta_helper(struct super_block *sb,
				     ext4_group_t agno, ext4_grpblk_t start,
				     ext4_grpblk_t len, void *priv)
{
	struct ext4_getfsmap_info *info = priv;
	struct ext4_fsmap *p;
	struct ext4_fsmap *tmp;
	struct ext4_sb_info *sbi = EXT4_SB(sb);
	ext4_fsblk_t fsb, fs_start, fs_end;
	int error;

	fs_start = fsb = (EXT4_C2B(sbi, start) +
			  ext4_group_first_block_no(sb, agno));
	fs_end = fs_start + EXT4_C2B(sbi, len);

	/* Return relevant extents from the meta_list */
	list_for_each_entry_safe(p, tmp, &info->gfi_meta_list, fmr_list) {
		if (p->fmr_physical < info->gfi_next_fsblk) {
			list_del(&p->fmr_list);
			kfree(p);
			continue;
		}
		if (p->fmr_physical <= fs_start ||
		    p->fmr_physical + p->fmr_length <= fs_end) {
			/* Emit the retained free extent record if present */
			if (info->gfi_lastfree.fmr_owner) {
				error = ext4_getfsmap_helper(sb, info,
							&info->gfi_lastfree);
				if (error)
					return error;
				info->gfi_lastfree.fmr_owner = 0;
			}
			error = ext4_getfsmap_helper(sb, info, p);
			if (error)
				return error;
			fsb = p->fmr_physical + p->fmr_length;
			if (info->gfi_next_fsblk < fsb)
				info->gfi_next_fsblk = fsb;
			list_del(&p->fmr_list);
			kfree(p);
			continue;
		}
	}
	if (info->gfi_next_fsblk < fsb)
		info->gfi_next_fsblk = fsb;

	return 0;
}


/* Transform a blockgroup's free record into a fsmap */
static int ext4_getfsmap_datadev_helper(struct super_block *sb,
					ext4_group_t agno, ext4_grpblk_t start,
@@ -539,6 +589,7 @@ static int ext4_getfsmap_datadev(struct super_block *sb,
		error = ext4_mballoc_query_range(sb, info->gfi_agno,
				EXT4_B2C(sbi, info->gfi_low.fmr_physical),
				EXT4_B2C(sbi, info->gfi_high.fmr_physical),
				ext4_getfsmap_meta_helper,
				ext4_getfsmap_datadev_helper, info);
		if (error)
			goto err;
@@ -560,7 +611,8 @@ static int ext4_getfsmap_datadev(struct super_block *sb,

	/* Report any gaps at the end of the bg */
	info->gfi_last = true;
	error = ext4_getfsmap_datadev_helper(sb, end_ag, last_cluster, 0, info);
	error = ext4_getfsmap_datadev_helper(sb, end_ag, last_cluster + 1,
					     0, info);
	if (error)
		goto err;

+14 −4
Original line number Diff line number Diff line
@@ -6999,13 +6999,14 @@ int
ext4_mballoc_query_range(
	struct super_block		*sb,
	ext4_group_t			group,
	ext4_grpblk_t			start,
	ext4_grpblk_t			first,
	ext4_grpblk_t			end,
	ext4_mballoc_query_range_fn	meta_formatter,
	ext4_mballoc_query_range_fn	formatter,
	void				*priv)
{
	void				*bitmap;
	ext4_grpblk_t			next;
	ext4_grpblk_t			start, next;
	struct ext4_buddy		e4b;
	int				error;

@@ -7016,10 +7017,19 @@ ext4_mballoc_query_range(

	ext4_lock_group(sb, group);

	start = max(e4b.bd_info->bb_first_free, start);
	start = max(e4b.bd_info->bb_first_free, first);
	if (end >= EXT4_CLUSTERS_PER_GROUP(sb))
		end = EXT4_CLUSTERS_PER_GROUP(sb) - 1;

	if (meta_formatter && start != first) {
		if (start > end)
			start = end;
		ext4_unlock_group(sb, group);
		error = meta_formatter(sb, group, first, start - first,
				       priv);
		if (error)
			goto out_unload;
		ext4_lock_group(sb, group);
	}
	while (start <= end) {
		start = mb_find_next_zero_bit(bitmap, end + 1, start);
		if (start > end)
+1 −0
Original line number Diff line number Diff line
@@ -259,6 +259,7 @@ ext4_mballoc_query_range(
	ext4_group_t			agno,
	ext4_grpblk_t			start,
	ext4_grpblk_t			end,
	ext4_mballoc_query_range_fn	meta_formatter,
	ext4_mballoc_query_range_fn	formatter,
	void				*priv);