* [PATCH v2] hfs: report errors when extending files with setattr
@ 2026-10-21 14:56 Hengyu Liang
2026-10-21 21:22 ` Viacheslav Dubeyko
0 siblings, 1 reply; 4+ messages in thread
From: Hengyu Liang @ 2026-10-21 14:56 UTC (permalink / raw)
To: slava; +Cc: glaubitz, frank.li, linux-fsdevel, linux-kernel
hfs_inode_setattr() sets i_size and calls hfs_file_truncate() to change
the allocation. If expansion fails, hfs_file_truncate() restores i_size
to the allocated size, but its void return type leaves setattr reporting
success.
On a freshly formatted 64 MiB HFS volume, ftruncate() to 128 MiB returns
zero while the file remains empty. When the requested size fits within
the volume but exceeds the free space, it also returns zero, leaving
the file only partially extended.
Call generic_cont_expand_simple() when growing a file and propagate its
error. Keep truncate_setsize() and hfs_file_truncate() in the shrinking
path. This makes failed extensions report EFBIG or ENOSPC.
Signed-off-by: Hengyu Liang <hengyul@cs.unc.edu>
---
Changes in v2:
- Keep truncate_setsize() and hfs_file_truncate() in the shrinking path,
as suggested by Viacheslav Dubeyko.
- Replace the generic/736 and fsx runs with a comparison of the whole
xfstests set selected for HFS, as requested.
v1: https://lore.kernel.org/r/20261017160612.3740256-1-hengyul@cs.unc.edu
Compared unpatched and patched v7.3-rc7 with identical KASAN configs.
All 805 tests selected for HFS by "check -n -g all" were attempted,
using freshly formatted 2 GiB test and scratch volumes for each case:
passed failed not run
unpatched 109 27 669
v2 109 27 669
The different results were generic/113, /394, /464 and /795. /113 and
/464 passed and failed in replays on the unpatched kernel, and the /113
failures are the same existing workqueue warning from hfs_file_fsync().
/795 failed on all three kernels in replays. On the 2 GiB volume, /394
passes without the fix only because ftruncate() returns success after a
partial extension; v1 and v2 report ENOSPC. It passes on all three
kernels with a 4 GiB volume.
Also ran 39 functional and 18 recovery cases per kernel on 32/64/128 MiB
volumes. Content and read-only remount checks passed on unpatched, v1
and v2 kernels. All three had the same 12 post-ENOSPC fsck failures;
recovery fsck checks were clean.
Functional/recovery cases and generic/394 were repeated on unpatched
and v2 kernels at the hfs 7.4 merge (dd7b15aeac80), with the same results.
fs/hfs/inode.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c
index 2aef3c36a150..9bdaa60fc392 100644
--- a/fs/hfs/inode.c
+++ b/fs/hfs/inode.c
@@ -684,8 +684,14 @@ int hfs_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
if (error)
return error;
- truncate_setsize(inode, attr->ia_size);
- hfs_file_truncate(inode);
+ if (attr->ia_size > inode->i_size) {
+ error = generic_cont_expand_simple(inode, attr->ia_size);
+ if (error)
+ return error;
+ } else {
+ truncate_setsize(inode, attr->ia_size);
+ hfs_file_truncate(inode);
+ }
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
}
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH v2] hfs: report errors when extending files with setattr 2026-10-21 14:56 [PATCH v2] hfs: report errors when extending files with setattr Hengyu Liang @ 2026-10-21 21:22 ` Viacheslav Dubeyko 2026-10-22 13:51 ` Hengyu Liang 0 siblings, 1 reply; 4+ messages in thread From: Viacheslav Dubeyko @ 2026-10-21 21:22 UTC (permalink / raw) To: Hengyu Liang; +Cc: glaubitz, frank.li, linux-fsdevel, linux-kernel On Wed, 2026-10-21 at 10:56 -0400, Hengyu Liang wrote: > hfs_inode_setattr() sets i_size and calls hfs_file_truncate() to > change > the allocation. If expansion fails, hfs_file_truncate() restores > i_size > to the allocated size, but its void return type leaves setattr > reporting > success. > > On a freshly formatted 64 MiB HFS volume, ftruncate() to 128 MiB > returns > zero while the file remains empty. When the requested size fits > within > the volume but exceeds the free space, it also returns zero, leaving > the file only partially extended. > > Call generic_cont_expand_simple() when growing a file and propagate > its > error. Keep truncate_setsize() and hfs_file_truncate() in the > shrinking > path. This makes failed extensions report EFBIG or ENOSPC. I think now this code [1] becomes dead. And we need to rework it: void hfs_file_truncate(struct inode *inode) { ... hfs_dbg(...); if (WARN_ON_ONCE(inode->i_size > HFS_I(inode)->phys_size)) return; if (inode->i_size == HFS_I(inode)->phys_size) return; size = inode->i_size + HFS_SB(sb)->alloc_blksz - 1; ... /* existing shrink logic unchanged */ } It will be great if you could share another patch with the fix. > > Signed-off-by: Hengyu Liang <hengyul@cs.unc.edu> > --- > Changes in v2: > - Keep truncate_setsize() and hfs_file_truncate() in the shrinking > path, > as suggested by Viacheslav Dubeyko. > - Replace the generic/736 and fsx runs with a comparison of the whole > xfstests set selected for HFS, as requested. > > v1: > https://lore.kernel.org/r/20261017160612.3740256-1-hengyul@cs.unc.edu > > Compared unpatched and patched v7.3-rc7 with identical KASAN configs. > All 805 tests selected for HFS by "check -n -g all" were attempted, > using freshly formatted 2 GiB test and scratch volumes for each case: > passed failed not run > unpatched 109 27 669 > v2 109 27 669 > > The different results were generic/113, /394, /464 and /795. /113 and > /464 passed and failed in replays on the unpatched kernel, and the > /113 > failures are the same existing workqueue warning from > hfs_file_fsync(). > /795 failed on all three kernels in replays. On the 2 GiB volume, > /394 > passes without the fix only because ftruncate() returns success after > a > partial extension; v1 and v2 report ENOSPC. It passes on all three > kernels with a 4 GiB volume. > > Also ran 39 functional and 18 recovery cases per kernel on 32/64/128 > MiB > volumes. Content and read-only remount checks passed on unpatched, v1 > and v2 kernels. All three had the same 12 post-ENOSPC fsck failures; > recovery fsck checks were clean. > > Functional/recovery cases and generic/394 were repeated on unpatched > and v2 kernels at the hfs 7.4 merge (dd7b15aeac80), with the same > results. > > fs/hfs/inode.c | 10 ++++++++-- > 1 file changed, 8 insertions(+), 2 deletions(-) > > diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c > index 2aef3c36a150..9bdaa60fc392 100644 > --- a/fs/hfs/inode.c > +++ b/fs/hfs/inode.c > @@ -684,8 +684,14 @@ int hfs_inode_setattr(struct mnt_idmap *idmap, > struct dentry *dentry, > if (error) > return error; > > - truncate_setsize(inode, attr->ia_size); > - hfs_file_truncate(inode); > + if (attr->ia_size > inode->i_size) { > + error = generic_cont_expand_simple(inode, > attr->ia_size); > + if (error) > + return error; > + } else { > + truncate_setsize(inode, attr->ia_size); > + hfs_file_truncate(inode); > + } > inode_set_mtime_to_ts(inode, > inode_set_ctime_current(inode)); > } > Looks good. Reviewed-by: Viacheslav Dubeyko <slava@dubeyko.com> Thanks, Slava. [1] https://elixir.bootlin.com/linux/v7.3-rc5/source/fs/hfs/extent.c#L488 ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2] hfs: report errors when extending files with setattr 2026-10-21 21:22 ` Viacheslav Dubeyko @ 2026-10-22 13:51 ` Hengyu Liang 2026-10-22 18:23 ` Viacheslav Dubeyko 0 siblings, 1 reply; 4+ messages in thread From: Hengyu Liang @ 2026-10-22 13:51 UTC (permalink / raw) To: slava; +Cc: glaubitz, frank.li, linux-fsdevel, linux-kernel Hi Slava, On Wed, Oct 21, 2026 at 02:22:41PM -0700, Viacheslav Dubeyko wrote: > I think now this code [1] becomes dead. And we need to rework it: > > void hfs_file_truncate(struct inode *inode) > { > ... > hfs_dbg(...); > > if (WARN_ON_ONCE(inode->i_size > HFS_I(inode)->phys_size)) > return; > if (inode->i_size == HFS_I(inode)->phys_size) > return; [...] > It will be great if you could share another patch with the fix. Thanks for the review. I made this change and ran the whole xfstests set, but the grow path is still reached. The WARN_ON_ONCE() fires under fsstress in generic/013, 076, 476, 650, 750 and 795, every time from close() through hfs_file_release(). It comes from direct writes: 1. When an inode is read, phys_size is set to i_size, which does not have to be block aligned, e.g. 100. 2. A direct write does not go through cont_write_begin(). When it needs a new block, hfs_get_block() only adds s_blocksize to phys_size. A direct write of 512 bytes at offset 512 makes phys_size 612 and i_size 1024. 3. At the last close, hfs_file_truncate() sees i_size > phys_size and calls hfs_write_begin(). cont_write_begin() then zeroes [phys_size, i_size) in the page cache, which overwrites the data that the direct write has just written. So today this path loses data, and removing it would only replace that with the warning, while phys_size stays behind i_size. 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 On a804c5742bff, with and without the v2 patch, the last 412 bytes read back as zeros instead of 0x42, and fsck.hfs reports no error. With the cleanup applied, the data is intact but the warning fires. HFS+ has the same problem. hfsplus_file_truncate() has the same grow path, and commit 5f63ac80aef2 ("hfsplus: fix issue of direct writes beyond end-of-file") only covers writes that start past EOF. A direct write of 1024 bytes at offset 0 to the same 100-byte file loses 924 bytes on HFS+. I think the direct write path should be fixed first, so that i_size can no longer get past phys_size. Then the grow path really is dead and your cleanup is right. The simplest way I see is what fat_direct_IO() does: if a direct write would end past phys_size, return 0 so that it falls back to a buffered write, which zeroes and accounts through cont_write_begin(). Would that be fine with you? If so, I will send that fix for HFS with your cleanup on top, and a similar fix for HFS+. Thanks, Hengyu ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2] hfs: report errors when extending files with setattr 2026-10-22 13:51 ` Hengyu Liang @ 2026-10-22 18:23 ` Viacheslav Dubeyko 0 siblings, 0 replies; 4+ messages in thread From: Viacheslav Dubeyko @ 2026-10-22 18:23 UTC (permalink / raw) To: Hengyu Liang; +Cc: glaubitz, frank.li, linux-fsdevel, linux-kernel On Thu, 2026-10-22 at 09:51 -0400, Hengyu Liang wrote: > Hi Slava, > > On Wed, Oct 21, 2026 at 02:22:41PM -0700, Viacheslav Dubeyko wrote: > > I think now this code [1] becomes dead. And we need to rework it: > > > > void hfs_file_truncate(struct inode *inode) > > { > > ... > > hfs_dbg(...); > > > > if (WARN_ON_ONCE(inode->i_size > HFS_I(inode)->phys_size)) > > return; > > if (inode->i_size == HFS_I(inode)->phys_size) > > return; > [...] > > It will be great if you could share another patch with the fix. > > Thanks for the review. I made this change and ran the whole xfstests > set, but the grow path is still reached. The WARN_ON_ONCE() fires > under > fsstress in generic/013, 076, 476, 650, 750 and 795, every time from > close() through hfs_file_release(). > > It comes from direct writes: > > 1. When an inode is read, phys_size is set to i_size, which does not > have to be block aligned, e.g. 100. > 2. A direct write does not go through cont_write_begin(). When it > needs a new block, hfs_get_block() only adds s_blocksize to > phys_size. A direct write of 512 bytes at offset 512 makes > phys_size > 612 and i_size 1024. > 3. At the last close, hfs_file_truncate() sees i_size > phys_size and > calls hfs_write_begin(). cont_write_begin() then zeroes > [phys_size, i_size) in the page cache, which overwrites the data > that the direct write has just written. > > So today this path loses data, and removing it would only replace > that > with the warning, while phys_size stays behind i_size. > > 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 > > On a804c5742bff, with and without the v2 patch, the last 412 bytes > read back as zeros instead of 0x42, and fsck.hfs reports no error. > With the cleanup applied, the data is intact but the warning fires. > > HFS+ has the same problem. hfsplus_file_truncate() has the same grow > path, and commit 5f63ac80aef2 ("hfsplus: fix issue of direct writes > beyond end-of-file") only covers writes that start past EOF. A direct > write of 1024 bytes at offset 0 to the same 100-byte file loses 924 > bytes on HFS+. > > I think the direct write path should be fixed first, so that i_size > can no longer get past phys_size. Then the grow path really is dead > and your cleanup is right. The simplest way I see is what > fat_direct_IO() does: if a direct write would end past phys_size, > return 0 so that it falls back to a buffered write, which zeroes and > accounts through cont_write_begin(). > > Would that be fine with you? If so, I will send that fix for HFS with > your cleanup on top, and a similar fix for HFS+. > > Yes, we still have enough issues in HFS and let's fix it step by step. You are right, we need to fix the direct IO issue before removing this "dead" code (it's not completely dead yet). Related to HFS+... I am still working on rebuilding direct IO path for HFS+ and, potentially, your fix could conflict with my patchset. But, anyway, you can share your fix and, then, we can manage this conflict (if we have one finally). Thanks, Slava. ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-22 18:23 UTC | newest] Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-10-21 14:56 [PATCH v2] hfs: report errors when extending files with setattr Hengyu Liang 2026-10-21 21:22 ` Viacheslav Dubeyko 2026-10-22 13:51 ` Hengyu Liang 2026-10-22 18:23 ` Viacheslav Dubeyko
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®