[PATCH] nilfs2: fix block-offset handling in iomap reads

Linmao Li <[email protected]> Fri, 31 Jul 2026 16:38:40 +0800
Newsgroups org.kernel.vger.linux-nilfs,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
nilfs_iomap_begin() looks up the filesystem block containing the requested
offset and returns the physical address of that block.  However, it sets
iomap->offset to the original, possibly sub-block, offset while leaving
iomap->addr at the start of the physical block.

iomap_sector() adds the difference between the I/O position and
iomap->offset to iomap->addr.  For an O_DIRECT read at offset 512 on a
filesystem with 4 KiB blocks, the two file offsets are equal and the I/O is
therefore submitted at the start of the physical block instead of 512 bytes
into it.  iomap direct I/O permits this alignment when the device logical
block size is 512 bytes.

Describe mapped and hole extents from the filesystem-block-aligned file
offset.  This makes the physical and file offsets refer to the same byte.
It also prevents a one-block hole mapping from extending into the following
block.

Fixes: b924d8d4e54f ("nilfs2: switch O_DIRECT reads to iomap")
Signed-off-by: Linmao Li <[email protected]>
---
 fs/nilfs2/iomap.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/fs/nilfs2/iomap.c b/fs/nilfs2/iomap.c
index 3ae3bf6ed3686..e130ed63abd9a 100644
--- a/fs/nilfs2/iomap.c
+++ b/fs/nilfs2/iomap.c
@@ -18,6 +18,7 @@ static int nilfs_iomap_begin(struct inode *inode, loff_t offset,
 	struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
 	struct nilfs_inode_info *ii = NILFS_I(inode);
 	sector_t blkoff = offset >> inode->i_blkbits;
+	loff_t iomap_offset = (loff_t)blkoff << inode->i_blkbits;
 	unsigned int maxblocks;
 	__u64 blknum = 0;
 	int ret;
@@ -50,15 +51,15 @@ static int nilfs_iomap_begin(struct inode *inode, loff_t offset,
 	if (ret == -ENOENT) {
 		iomap->type = IOMAP_HOLE;
 		iomap->addr = IOMAP_NULL_ADDR;
-		iomap->offset = offset;
-		iomap->length = min_t(loff_t, length, i_blocksize(inode));
+		iomap->offset = iomap_offset;
+		iomap->length = i_blocksize(inode);
 		return 0;
 	} else if (ret < 0)
 		return ret;
 
 	iomap->bdev = inode->i_sb->s_bdev;
-	iomap->offset = offset;
-	iomap->length = min_t(loff_t, length, (loff_t)ret << inode->i_blkbits);
+	iomap->offset = iomap_offset;
+	iomap->length = (loff_t)ret << inode->i_blkbits;
 	iomap->addr = (loff_t)blknum << inode->i_blkbits;
 	iomap->type = IOMAP_MAPPED;
 	iomap->flags = IOMAP_F_MERGED;

base-commit: d5c57d9bf03516d625521cd6cf5acfd93e3e63c7
-- 
2.25.1