]> git.ipfire.org Git - thirdparty/e2fsprogs.git/commit
libext2fs: fix ind_punch recursive block computation
authorDarrick J. Wong <djwong@kernel.org>
Wed, 8 Oct 2025 19:04:49 +0000 (12:04 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Fri, 17 Oct 2025 23:34:21 +0000 (16:34 -0700)
commitc49656b16a7dd7e5fa6e2b754e02287bc0cf4908
tree7046df128d614cc3f4b52ca9aeea585d614d0104
parent4832dc95d2385c4efdec28d4ca95380be8234f49
libext2fs: fix ind_punch recursive block computation

generic/742 writes a large file and calls punch-alternating to punch out
every other block in the file.  Unfortunately, there's a bug in
ind_punch that causes it to turn a request to punch out file block 2060
into punching out every file block *starting* at block 2060.  The
emptying out of the block mapping causes a FIEMAP call in that test to
loop forever, which causes fuse4fs testing of ext3 to halt.

ext2fs_punch_ind recursively calls ind_punch on each level of the
indirect map to unmap file blocks.  The start and count parameters to
ind_punch are (I think) the file range to punch given a particular block
and level within the indirect map, but shifted down by the start of the
logical file range mapped by that level in the indirect tree.  To call
ind_punch for the next level down, we need to compute the new range.
But first, a diversion back to the failing testcase:

Note that file block 2060 is the first block mapped by the second
double-indirect block in the file:

(0-11):935-946, (IND):947, (12-1035):948-1971, (DIND):1972, (IND):1973,
(1036-2059):1974-2997, (IND):2998, (2060-3083):2999-4022, (IND):4023,
(3084-4107):4024-5047, (IND):5048, (4108-5131):5049-6072, (IND):6073,
(5132-6155):6074-7097, (IND):7098, (6156-7179):7099-8122, (IND):8123,
(7180-8203):8124-9147, (IND):9148, (8204-9227):9149-10172, (IND):10173,
(9228-9999):10174-10945 TOTAL: 10011

At this point, enabling PUNCH_DEBUG produces the following:

FUSE4FS (sda): tid=72529 fuse4fs_punch_range: ino=53 mode=0x3 offset=0x80c000 len=0x1000 start=0x80c end=0x80d
Main loop level 0, start 2060 count 1 max 12 num 12
Main loop level 1, start 2048 count 1 max 1024 num 1
Main loop level 2, start 1024 count 1 max 1048576 num 1
Entering ind_punch, level 2, start 1024, count 1, max 1
Reading indirect block 1972
start 1024 offset 0 start2 1024 count 1 count2 1
Entering ind_punch, level 1, start 1024, count 1, max 1024
Reading indirect block 2998
start 1024 offset 1024 start2 0 count 1 count2 1
Entering ind_punch, level 0, start 0, count 18446744073709550593, max 1024

This is wrong, because we want to punch *one* block, not some gigantic
number of blocks!  This huge value is actually -1023, which is the
result of the expression (count - offset).  Ooops, that's why we unmap
every block in this indirect block!

Note the suspicious 7th argument to the nested ind_punch call:

count - offset

This doesn't smell right, because we're subtracting a position from a
length.  The end of the range for the next level down should be the
difference between the end and the start of the range in the current
level after accounting for our current position within that level.  In
other words, the smaller of end - start and end - offset.

Cc: <linux-ext4@vger.kernel.org> # v1.42
Fixes: 3adb9374fb9273 ("libext2fs: Add new function ext2fs_punch()")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
lib/ext2fs/punch.c