]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
hfsplus: fix issue of direct writes beyond end-of-file
authorViacheslav Dubeyko <slava@dubeyko.com>
Fri, 17 Apr 2026 21:49:41 +0000 (14:49 -0700)
committerViacheslav Dubeyko <slava@dubeyko.com>
Mon, 27 Apr 2026 22:23:42 +0000 (15:23 -0700)
The xfstests' test-case generic/729 fails with error:

sudo ./check generic/729
FSTYP         -- hfsplus
PLATFORM      -- Linux/x86_64 hfsplus-testing-0001 7.0.0-rc1+ #36 SMP PREEMPT_DYNAMIC Fri Apr 17 12:40:51 PDT 2026
MKFS_OPTIONS  -- /dev/loop51
MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch

generic/729  23s ... [failed, exit status 1]- output mismatch

mmap-rw-fault: /mnt/test/mmap-rw-fault.tmp: Input/output error

The hfsplus_get_block() only allows creating the next
sequential block. It returns -EIO for direct writes
beyond EOF. This patch waits for any in-flight DIO on the inode
to finish. Then, it extends the file by calling
generic_cont_expand_simple() with the goal to guarantee
that blockdev_direct_IO() finds all needed blocks
already reachable sequentially. And, finally, it flushes and
invalidates the DIO range again so the page cache is clean
before the direct write begins.

sudo ./check generic/729
FSTYP         -- hfsplus
PLATFORM      -- Linux/x86_64 hfsplus-testing-0001 7.0.0-rc1+ #40 SMP PREEMPT_DYNAMIC Thu Apr 16 15:41:03 PDT 2026
MKFS_OPTIONS  -- /dev/loop51
MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch

generic/729  23s ...  32s
Ran: generic/729
Passed all 1 tests

Closes: https://github.com/hfs-linux-kernel/hfs-linux-kernel/issues/210
cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
cc: Yangtao Li <frank.li@vivo.com>
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
Link: https://lore.kernel.org/r/20260417214940.2735557-2-slava@dubeyko.com
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
fs/hfsplus/inode.c

index d05891ec492e3a5de80b2055c75ec51193cc3f78..036ca516d85146811913b04a5a7e34b5da2555f8 100644 (file)
@@ -125,9 +125,44 @@ static ssize_t hfsplus_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
        struct file *file = iocb->ki_filp;
        struct address_space *mapping = file->f_mapping;
        struct inode *inode = mapping->host;
+       loff_t isize;
        size_t count = iov_iter_count(iter);
+       loff_t end = iocb->ki_pos + count;
        ssize_t ret;
 
+       /*
+        * The hfsplus_get_block() only allows creating the next sequential block.
+        * For direct writes beyond EOF, expand the file first.
+        */
+       if (iov_iter_rw(iter) == WRITE && iocb->ki_pos > i_size_read(inode)) {
+               loff_t start_off, end_off;
+               loff_t start_page, end_page;
+
+               isize = i_size_read(inode);
+
+               /*
+                * Wait for any in-flight DIO on this inode to finish before
+                * calling generic_cont_expand_simple().
+                */
+               inode_dio_wait(inode);
+
+               ret = generic_cont_expand_simple(inode, iocb->ki_pos);
+               if (ret)
+                       return ret;
+
+               start_off = isize;
+               end_off = (end > 0) ? end - 1 : end;
+
+               ret = filemap_write_and_wait_range(mapping, start_off, end_off);
+               if (ret)
+                       return ret;
+
+               start_page = start_off >> PAGE_SHIFT;
+               end_page = end_off >> PAGE_SHIFT;
+
+               invalidate_inode_pages2_range(mapping, start_page, end_page);
+       }
+
        ret = blockdev_direct_IO(iocb, inode, iter, hfsplus_get_block);
 
        /*
@@ -135,8 +170,7 @@ static ssize_t hfsplus_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
         * blocks outside i_size. Trim these off again.
         */
        if (unlikely(iov_iter_rw(iter) == WRITE && ret < 0)) {
-               loff_t isize = i_size_read(inode);
-               loff_t end = iocb->ki_pos + count;
+               isize = i_size_read(inode);
 
                if (end > isize)
                        hfsplus_write_failed(mapping, end);