Skip to content

Commit 4751832

Browse files
Qu Wenruokdave
authored andcommitted
btrfs: fiemap: Cache and merge fiemap extent before submit it to user
[BUG] Cycle mount btrfs can cause fiemap to return different result. Like: # mount /dev/vdb5 /mnt/btrfs # dd if=/dev/zero bs=16K count=4 oflag=dsync of=/mnt/btrfs/file # xfs_io -c "fiemap -v" /mnt/btrfs/file /mnt/test/file: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: 25088..25215 128 0x1 # umount /mnt/btrfs # mount /dev/vdb5 /mnt/btrfs # xfs_io -c "fiemap -v" /mnt/btrfs/file /mnt/test/file: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..31]: 25088..25119 32 0x0 1: [32..63]: 25120..25151 32 0x0 2: [64..95]: 25152..25183 32 0x0 3: [96..127]: 25184..25215 32 0x1 But after above fiemap, we get correct merged result if we call fiemap again. # xfs_io -c "fiemap -v" /mnt/btrfs/file /mnt/test/file: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: 25088..25215 128 0x1 [REASON] Btrfs will try to merge extent map when inserting new extent map. btrfs_fiemap(start=0 len=(u64)-1) |- extent_fiemap(start=0 len=(u64)-1) |- get_extent_skip_holes(start=0 len=64k) | |- btrfs_get_extent_fiemap(start=0 len=64k) | |- btrfs_get_extent(start=0 len=64k) | | Found on-disk (ino, EXTENT_DATA, 0) | |- add_extent_mapping() | |- Return (em->start=0, len=16k) | |- fiemap_fill_next_extent(logic=0 phys=X len=16k) | |- get_extent_skip_holes(start=0 len=64k) | |- btrfs_get_extent_fiemap(start=0 len=64k) | |- btrfs_get_extent(start=16k len=48k) | | Found on-disk (ino, EXTENT_DATA, 16k) | |- add_extent_mapping() | | |- try_merge_map() | | Merge with previous em start=0 len=16k | | resulting em start=0 len=32k | |- Return (em->start=0, len=32K) << Merged result |- Stripe off the unrelated range (0~16K) of return em |- fiemap_fill_next_extent(logic=16K phys=X+16K len=16K) ^^^ Causing split fiemap extent. And since in add_extent_mapping(), em is already merged, in next fiemap() call, we will get merged result. [FIX] Here we introduce a new structure, fiemap_cache, which records previous fiemap extent. And will always try to merge current fiemap_cache result before calling fiemap_fill_next_extent(). Only when we failed to merge current fiemap extent with cached one, we will call fiemap_fill_next_extent() to submit cached one. So by this method, we can merge all fiemap extents. It can also be done in fs/ioctl.c, however the problem is if fieinfo->fi_extents_max == 0, we have no space to cache previous fiemap extent. So I choose to merge it in btrfs. Signed-off-by: Qu Wenruo <[email protected]> Reviewed-by: Liu Bo <[email protected]> Reviewed-by: David Sterba <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 9bcaaea commit 4751832

File tree

1 file changed

+122
-2
lines changed

1 file changed

+122
-2
lines changed

fs/btrfs/extent_io.c

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4377,6 +4377,123 @@ static struct extent_map *get_extent_skip_holes(struct inode *inode,
43774377
return NULL;
43784378
}
43794379

4380+
/*
4381+
* To cache previous fiemap extent
4382+
*
4383+
* Will be used for merging fiemap extent
4384+
*/
4385+
struct fiemap_cache {
4386+
u64 offset;
4387+
u64 phys;
4388+
u64 len;
4389+
u32 flags;
4390+
bool cached;
4391+
};
4392+
4393+
/*
4394+
* Helper to submit fiemap extent.
4395+
*
4396+
* Will try to merge current fiemap extent specified by @offset, @phys,
4397+
* @len and @flags with cached one.
4398+
* And only when we fails to merge, cached one will be submitted as
4399+
* fiemap extent.
4400+
*
4401+
* Return value is the same as fiemap_fill_next_extent().
4402+
*/
4403+
static int emit_fiemap_extent(struct fiemap_extent_info *fieinfo,
4404+
struct fiemap_cache *cache,
4405+
u64 offset, u64 phys, u64 len, u32 flags)
4406+
{
4407+
int ret = 0;
4408+
4409+
if (!cache->cached)
4410+
goto assign;
4411+
4412+
/*
4413+
* Sanity check, extent_fiemap() should have ensured that new
4414+
* fiemap extent won't overlap with cahced one.
4415+
* Not recoverable.
4416+
*
4417+
* NOTE: Physical address can overlap, due to compression
4418+
*/
4419+
if (cache->offset + cache->len > offset) {
4420+
WARN_ON(1);
4421+
return -EINVAL;
4422+
}
4423+
4424+
/*
4425+
* Only merges fiemap extents if
4426+
* 1) Their logical addresses are continuous
4427+
*
4428+
* 2) Their physical addresses are continuous
4429+
* So truly compressed (physical size smaller than logical size)
4430+
* extents won't get merged with each other
4431+
*
4432+
* 3) Share same flags except FIEMAP_EXTENT_LAST
4433+
* So regular extent won't get merged with prealloc extent
4434+
*/
4435+
if (cache->offset + cache->len == offset &&
4436+
cache->phys + cache->len == phys &&
4437+
(cache->flags & ~FIEMAP_EXTENT_LAST) ==
4438+
(flags & ~FIEMAP_EXTENT_LAST)) {
4439+
cache->len += len;
4440+
cache->flags |= flags;
4441+
goto try_submit_last;
4442+
}
4443+
4444+
/* Not mergeable, need to submit cached one */
4445+
ret = fiemap_fill_next_extent(fieinfo, cache->offset, cache->phys,
4446+
cache->len, cache->flags);
4447+
cache->cached = false;
4448+
if (ret)
4449+
return ret;
4450+
assign:
4451+
cache->cached = true;
4452+
cache->offset = offset;
4453+
cache->phys = phys;
4454+
cache->len = len;
4455+
cache->flags = flags;
4456+
try_submit_last:
4457+
if (cache->flags & FIEMAP_EXTENT_LAST) {
4458+
ret = fiemap_fill_next_extent(fieinfo, cache->offset,
4459+
cache->phys, cache->len, cache->flags);
4460+
cache->cached = false;
4461+
}
4462+
return ret;
4463+
}
4464+
4465+
/*
4466+
* Sanity check for fiemap cache
4467+
*
4468+
* All fiemap cache should be submitted by emit_fiemap_extent()
4469+
* Iteration should be terminated either by last fiemap extent or
4470+
* fieinfo->fi_extents_max.
4471+
* So no cached fiemap should exist.
4472+
*/
4473+
static int check_fiemap_cache(struct btrfs_fs_info *fs_info,
4474+
struct fiemap_extent_info *fieinfo,
4475+
struct fiemap_cache *cache)
4476+
{
4477+
int ret;
4478+
4479+
if (!cache->cached)
4480+
return 0;
4481+
4482+
/* Small and recoverbale problem, only to info developer */
4483+
#ifdef CONFIG_BTRFS_DEBUG
4484+
WARN_ON(1);
4485+
#endif
4486+
btrfs_warn(fs_info,
4487+
"unhandled fiemap cache detected: offset=%llu phys=%llu len=%llu flags=0x%x",
4488+
cache->offset, cache->phys, cache->len, cache->flags);
4489+
ret = fiemap_fill_next_extent(fieinfo, cache->offset, cache->phys,
4490+
cache->len, cache->flags);
4491+
cache->cached = false;
4492+
if (ret > 0)
4493+
ret = 0;
4494+
return ret;
4495+
}
4496+
43804497
int extent_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
43814498
__u64 start, __u64 len, get_extent_t *get_extent)
43824499
{
@@ -4394,6 +4511,7 @@ int extent_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
43944511
struct extent_state *cached_state = NULL;
43954512
struct btrfs_path *path;
43964513
struct btrfs_root *root = BTRFS_I(inode)->root;
4514+
struct fiemap_cache cache = { 0 };
43974515
int end = 0;
43984516
u64 em_start = 0;
43994517
u64 em_len = 0;
@@ -4573,15 +4691,17 @@ int extent_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
45734691
flags |= FIEMAP_EXTENT_LAST;
45744692
end = 1;
45754693
}
4576-
ret = fiemap_fill_next_extent(fieinfo, em_start, disko,
4577-
em_len, flags);
4694+
ret = emit_fiemap_extent(fieinfo, &cache, em_start, disko,
4695+
em_len, flags);
45784696
if (ret) {
45794697
if (ret == 1)
45804698
ret = 0;
45814699
goto out_free;
45824700
}
45834701
}
45844702
out_free:
4703+
if (!ret)
4704+
ret = check_fiemap_cache(root->fs_info, fieinfo, &cache);
45854705
free_extent_map(em);
45864706
out:
45874707
btrfs_free_path(path);

0 commit comments

Comments
 (0)