[GH-ISSUE #55] msdosfs: mmap'd files lose their size on truncate and rewrite; no SIGBUS past EOF #46

Closed
opened 2026-11-03 22:03:11 -07:00 by gitea-mirror · 1 comment
Owner

Originally created by @raynorpat on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/55

Symptom

On the same FAT16 volume (kernel and setup as in #54), stress-mmap fails
every time. The same tests pass on UFS, HFS+ and ext2 (ext2 has its own
trunc panic):

STRESS mmap private FAIL reason=short-read off=0x2000 worker=0 iter=1 seed=1   (-n 1 -i 2, also seeds 2, 3 and -n 3 -i 20)
STRESS mmap trunc   FAIL reason=no-sigbus worker=0 iter=0 seed=1               (-n 1 -i 1, seeds 1-3)
  • private, iteration 2: open(O_TRUNC) on a file that was mapped
    MAP_PRIVATE in iteration 1, write() of 64 KiB, read() from offset 0. The
    read hits EOF at 8 KiB, and the file stays 8192 bytes on disk (ls -l), so
    56 KiB of written data is lost.
  • trunc: on a 64 KiB MAP_SHARED file, ftruncate to 20 KiB, then touching
    the mapping at 28 KiB does not fault. The test stops there, and the files
    it leaves are 40960 bytes instead of 20480, so the truncated size did not
    stick either.

fsck_msdos -n stays clean after these runs. The damage is to file sizes and
data, not the FAT.

Reproduce

stress-mmap -n 1 -i 2 -s 1 -d /mnt/m private
stress-mmap -n 1 -i 1 -s 1 -d /mnt/m trunc

Log: vm/work/fsrepro/res-m6.log, res-m5-final.log, res-m4-final.log.

Where to look

bsd/msdosfs/msdosfs_denode.c detrunc() and msdosfs_vnops.c. Unlike
ffs_truncate (mapfs_trunc) and ext2fs_truncate (ext2fs_vm_flush(...,1,length)),
msdosfs never runs the MACH_NBC mapped-file truncate. It calls
vnode_pager_setsize directly, even on mapped vnodes, so mapped pages past
the new EOF stay valid. Also msdosfs_pageout is a plain
VOP_WRITE pass-through. A late pageout of stale pages can therefore write
through the normal write path after a truncate and change de_FileSize
(compare ufs_pageout). Port the ufs/ext2 MACH_NBC truncate and pageout
handling to msdosfs.

Found by the stress suite (src/Commands/stress, branch stress-phase3) during the phase 3 filesystem run. Related: #45 (rename races, fixed on fs-races).

Originally created by @raynorpat on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/55 ## Symptom On the same FAT16 volume (kernel and setup as in #54), `stress-mmap` fails every time. The same tests pass on UFS, HFS+ and ext2 (ext2 has its own `trunc` panic): ``` STRESS mmap private FAIL reason=short-read off=0x2000 worker=0 iter=1 seed=1 (-n 1 -i 2, also seeds 2, 3 and -n 3 -i 20) STRESS mmap trunc FAIL reason=no-sigbus worker=0 iter=0 seed=1 (-n 1 -i 1, seeds 1-3) ``` - `private`, iteration 2: `open(O_TRUNC)` on a file that was mapped `MAP_PRIVATE` in iteration 1, `write()` of 64 KiB, `read()` from offset 0. The read hits EOF at 8 KiB, and the file stays 8192 bytes on disk (`ls -l`), so 56 KiB of written data is lost. - `trunc`: on a 64 KiB `MAP_SHARED` file, `ftruncate` to 20 KiB, then touching the mapping at 28 KiB does not fault. The test stops there, and the files it leaves are 40960 bytes instead of 20480, so the truncated size did not stick either. `fsck_msdos -n` stays clean after these runs. The damage is to file sizes and data, not the FAT. ## Reproduce ``` stress-mmap -n 1 -i 2 -s 1 -d /mnt/m private stress-mmap -n 1 -i 1 -s 1 -d /mnt/m trunc ``` Log: `vm/work/fsrepro/res-m6.log`, `res-m5-final.log`, `res-m4-final.log`. ## Where to look `bsd/msdosfs/msdosfs_denode.c` `detrunc()` and `msdosfs_vnops.c`. Unlike `ffs_truncate` (`mapfs_trunc`) and `ext2fs_truncate` (`ext2fs_vm_flush(...,1,length)`), msdosfs never runs the MACH_NBC mapped-file truncate. It calls `vnode_pager_setsize` directly, even on mapped vnodes, so mapped pages past the new EOF stay valid. Also `msdosfs_pageout` is a plain `VOP_WRITE` pass-through. A late pageout of stale pages can therefore write through the normal write path after a truncate and change `de_FileSize` (compare `ufs_pageout`). Port the ufs/ext2 MACH_NBC truncate and pageout handling to msdosfs. Found by the stress suite (`src/Commands/stress`, branch `stress-phase3`) during the phase 3 filesystem run. Related: #45 (rename races, fixed on fs-races).
Author
Owner

@raynorpat commented on GitHub (Oct 16, 2026):

Fixed in 260033acbb (kernel: Merge filesystem repairs for issues 52-55), on ffs-perf; the pageout and size handling is in msdosfs_vnops.c and msdosfs_denode.c.

Verified on the repair kernel:

  • The original reproducers stress-mmap -n 1 -i 2 -s 1 private and -n 1 -i 1 -s 1 trunc now pass on FAT16, and so does the extended shared mixed private trunc -n 3 -i 20 -s 2.
  • The host fixture vm/msdosfs_pageout_test.py covers stale pageouts past EOF, partial final pages, errors, and cached extension before the on-disk size catches up. It failed before the fix and passes with it.

Not covered by this fix, and seen on the unmodified kernel too: mapping a file directly, shrinking it, then growing it again returns old data past the truncated EOF on FAT16, and loses retained dirty data on UFS. Those direct-mmap coherency cases are separate from this issue. The same test using write instead of mmap passes.

Test notes and focused regressions: vm/filesystem-issues-52-55.md in that commit. Repair kernel cksum 3500498149 (i386, QEMU snapshot); PowerPC not tested.

<!-- gh-comment-id:6206909991 --> @raynorpat commented on GitHub (Oct 16, 2026): Fixed in 260033acbb4c29cf90781f9b16cc028a79785e69 (kernel: Merge filesystem repairs for issues 52-55), on ffs-perf; the pageout and size handling is in `msdosfs_vnops.c` and `msdosfs_denode.c`. Verified on the repair kernel: - The original reproducers `stress-mmap -n 1 -i 2 -s 1 private` and `-n 1 -i 1 -s 1 trunc` now pass on FAT16, and so does the extended `shared mixed private trunc -n 3 -i 20 -s 2`. - The host fixture `vm/msdosfs_pageout_test.py` covers stale pageouts past EOF, partial final pages, errors, and cached extension before the on-disk size catches up. It failed before the fix and passes with it. Not covered by this fix, and seen on the unmodified kernel too: mapping a file directly, shrinking it, then growing it again returns old data past the truncated EOF on FAT16, and loses retained dirty data on UFS. Those direct-mmap coherency cases are separate from this issue. The same test using `write` instead of mmap passes. Test notes and focused regressions: `vm/filesystem-issues-52-55.md` in that commit. Repair kernel cksum 3500498149 (i386, QEMU snapshot); PowerPC not tested.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
github-starred/RhapsodiOS#46
No description provided.