[BUG] After commit 4025185cf953 ("iomap: follow the alignment requirement for iomap_dio_hole_iter()"), generic/091 fails on both xfs and ext4: FSTYP -- ext4 PLATFORM -- Linux/x86_64 btrfs-vm 7.3.0-custom+ #6 SMP PREEMPT_DYNAMIC Mon Oct 26 16:04:56 ACDT 2026 MKFS_OPTIONS -- -F /dev/mapper/test-scratch1 MOUNT_OPTIONS -- -o acl,user_xattr /dev/mapper/test-scratch1 /mnt/scratch generic/091 6s ... [failed, exit status 1]- output mismatch (see /home/adam/xfstests/results//generic/091.out.bad) --- /home/adam/xfstests/tests/generic/091.out 2022-05-11 11:25:30.766666664 +0930 +++ /home/adam/xfstests/results//generic/091.out.bad 2026-10-26 16:43:49.387401669 +1030 @@ -1,7 +1,25 @@ QA output created by 091 fsx -N 10000 -l 500000 -r PSIZE -t BSIZE -w BSIZE -Z -R -W -fsx -N 10000 -o 8192 -l 500000 -r PSIZE -t BSIZE -w BSIZE -Z -R -W -fsx -N 10000 -o 32768 -l 500000 -r PSIZE -t BSIZE -w BSIZE -Z -R -W -fsx -N 10000 -o 8192 -l 500000 -r PSIZE -t BSIZE -w BSIZE -Z -R -W -fsx -N 10000 -o 32768 -l 500000 -r PSIZE -t BSIZE -w BSIZE -Z -R -W -fsx -N 10000 -o 128000 -l 500000 -r PSIZE -t BSIZE -w BSIZE -Z -W ... (Run 'diff -u /home/adam/xfstests/tests/generic/091.out /home/adam/xfstests/results//generic/091.out.bad' to see the entire diff) Ran: generic/091 Failures: generic/091 Failed 1 of 1 tests [CAUSE] The failed command of fsx is COPY_RANGE: copy range: 0x6800 to 0xa200 at 0x3f200 do_copy_range:: Resource temporarily unavailable LOG DUMP (8 total operations): 1( 1 mod 256): SKIPPED (no operation) 2( 2 mod 256): SKIPPED (no operation) 3( 3 mod 256): WRITE 0x14400 thru 0x211ff (0xce00 bytes) HOLE 4( 4 mod 256): FALLOC 0x72bf4 thru 0x76221 (0x362d bytes) EXTENDING 5( 5 mod 256): SKIPPED (no operation) 6( 6 mod 256): INSERT 0x11000 thru 0x13fff (0x3000 bytes) 7( 7 mod 256): TRUNCATE DOWN from 0x79221 to 0xa800 8( 8 mod 256): COPY 0x6800 thru 0xa1ff (0x3a00 bytes) to 0x3f200 thru 0x42bff Log of operations saved to "/mnt/test/junk.fsxops"; replay with --replay-ops Correct content saved for comparison (maybe hexdump "/mnt/test/junk" vs "/mnt/test/junk.fsxgood") Both ext4 and xfs only require direct IO to be aligned to the logical block size of the device, not the fs block size. So the 0x3a00 bytes read at 0x6800 is allowed, and should succeed. But that range is a hole, and that commit makes iomap_dio_hole_iter() round the zeroed length down to the fs block size unconditionally. The first iteration zeroes 0x3a00 bytes but only advances 0x3000 bytes, then the next iteration zeroes the remaining 0xa00 bytes, which is rounded down to 0, so -EFAULT is returned. As ext4 and xfs do not pass IOMAP_DIO_PARTIAL, the whole read fails with -EFAULT, which is then converted to -EAGAIN by copy_splice_read(). Every retry of fsx hits the same -EAGAIN, failing the run. That commit breaks all direct reads which range is not fs block aligned for ext4 and xfs. [FIX] Do not revert the range unless the dio has IOMAP_DIO_FSBLOCK_ALIGNED flag. iomap_dio_hole_iter() is only called for reads, and for now only btrfs reads can reach this path with the flag set. Btrfs has the proper fallback when dio returns -EFAULT. Also update the comment of IOMAP_DIO_FSBLOCK_ALIGNED flag, to indicate that both bio and hole zeroing are all fs block aligned. Fixes: 4025185cf953 ("iomap: follow the alignment requirement for iomap_dio_hole_iter()") Cc: stable@vger.kernel.org Signed-off-by: Qu Wenruo --- Changelog: v2: - Update the comment of IOMAP_DIO_FSBLOCK_ALIGNED To indicate that hole zeroing is also affected. --- fs/iomap/direct-io.c | 26 ++++++++++++++++---------- include/linux/iomap.h | 3 ++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index 45bb24934f3d..ad041ac3252a 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -590,18 +590,24 @@ static int iomap_dio_bio_iter(struct iomap_iter *iter, struct iomap_dio *dio) static int iomap_dio_hole_iter(struct iomap_iter *iter, struct iomap_dio *dio) { loff_t copied = iov_iter_zero(iomap_length(iter), dio->submit.iter); - unsigned int bs = i_blocksize(iter->inode); - loff_t aligned_copied = round_down(copied, bs); - /* - * If fs block size is larger than page size, page fault failure - * can cause @copied to be page aligned but not fs block aligned. - */ - iov_iter_revert(dio->submit.iter, copied - aligned_copied); - dio->size += aligned_copied; - if (!aligned_copied) + if (dio->flags & IOMAP_DIO_FSBLOCK_ALIGNED) { + loff_t aligned_copied = round_down(copied, + i_blocksize(iter->inode)); + + /* + * For fs passing IOMAP_DIO_FSBLOCK_ALIGNED flag, if fs block + * size is larger than page size, page fault failure + * can cause @copied to be page aligned but not fs block + * aligned. + */ + iov_iter_revert(dio->submit.iter, copied - aligned_copied); + copied = aligned_copied; + } + dio->size += copied; + if (!copied) return -EFAULT; - return iomap_iter_advance(iter, aligned_copied); + return iomap_iter_advance(iter, copied); } static int iomap_dio_inline_iter(struct iomap_iter *iomi, struct iomap_dio *dio) diff --git a/include/linux/iomap.h b/include/linux/iomap.h index 59718f73c15a..5254430844b2 100644 --- a/include/linux/iomap.h +++ b/include/linux/iomap.h @@ -693,7 +693,8 @@ struct iomap_dio_ops { #define IOMAP_DIO_PARTIAL (1 << 2) /* - * Ensure each bio is aligned to fs block size. + * Ensure every operation, including bio and hole zeroing, is aligned to fs + * block size. * * For filesystems which need to calculate/verify the checksum of each fs * block. Otherwise they may not be able to handle unaligned bios. -- 2.56.0