[PATCH 1/2] hfs: fall back to buffered I/O for direct writes past phys_size

From: Hengyu Liang

Date: Fri Oct 30 2026 - 00:31:12 EST


hfs_direct_IO() passes direct writes to blockdev_direct_IO(), which
does not go through hfs_write_begin(). When such a write needs a new
block, hfs_get_block() only adds the block size to phys_size. phys_size
starts at i_size when the inode is read, which does not have to be
block aligned, so after the write phys_size is smaller than i_size.

However, at the last close, hfs_file_truncate() sees i_size larger than
phys_size and calls hfs_write_begin() at i_size, which zeroes the file
from phys_size up to i_size. As of now, part of the data that a direct
write has just written past the old end of the file is lost, and fsck
reports no error.

The issue can be reproduced with:

truncate -s 32M hfs.img
mkfs.hfsplus -h hfs.img
mount -t hfs -o loop hfs.img /mnt
printf 'A%.0s' $(seq 100) > /mnt/f
umount /mnt
mount -t hfs -o loop hfs.img /mnt
xfs_io -d -c 'pwrite -S 0x42 512 512' /mnt/f
umount /mnt
mount -t hfs -o loop hfs.img /mnt
od -An -tx1 -j 512 -N 512 /mnt/f

The last 412 bytes read back as zeros instead of 0x42.

This patch will make a direct write that ends past phys_size fall back
to a buffered write, as fat_direct_IO() and affs_direct_IO() do.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Hengyu Liang <hengyul@xxxxxxxxxx>
---
fs/hfs/inode.c | 10 ++++++++++
1 file changed, 10 insertions(+)

diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c
index d9abab2a1e61..d476a16f4ef9 100644
--- a/fs/hfs/inode.c
+++ b/fs/hfs/inode.c
@@ -139,6 +139,16 @@ static ssize_t hfs_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
size_t count = iov_iter_count(iter);
ssize_t ret;

+ /*
+ * blockdev_direct_IO() does not go through ->write_begin(), so it
+ * neither zeroes the file from phys_size up to the write nor keeps
+ * phys_size in step with i_size. Return 0 for a write that ends
+ * past phys_size, so that it falls back to a buffered write.
+ */
+ if (iov_iter_rw(iter) == WRITE &&
+ iocb->ki_pos + count > HFS_I(inode)->phys_size)
+ return 0;
+
ret = blockdev_direct_IO(iocb, inode, iter, hfs_get_block);

/*
--
2.53.0