Repository navigation
Conversation
ce4f8ca to
5605bb4
Compare
5605bb4 to
8a80a9a
Compare
8a80a9a to
12a1071
Compare
…es on Windows ftruncate, truncate and sceKernelFtruncate extended a file through _chsize_s, which writes the whole new length as zero bytes: 256 MiB took about 700 ms and ftruncate(fd, INT64_MAX) kept writing until the disk was full. FreeBSD extends the file without writing data and fails a length the file system cannot hold with EFBIG. Set the end of file with SetFileInformationByHandle instead and map ERROR_INVALID_PARAMETER on a disk file to EFBIG, ERROR_DISK_FULL to ENOSPC and a read-only descriptor to EACCES (EINVAL for the caller, as before). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
12a1071 to
2a74997
Compare
CnRJay
left a comment
There was a problem hiding this comment.
SetFileInformationByHandle(FileEndOfFileInfo) is the right call: it extends without writing, and reads of the extension still return zeros because of NTFS's valid-data length.
One mapping to check: ERROR_ACCESS_DENIED → EACCES. FreeBSD's ftruncate(2) fails with EINVAL when the descriptor was opened without write permission (kern_ftruncate: if ((fp->f_flag & FWRITE) == 0) return (EINVAL)), and EACCES is only for truncate on a path. If a read-only descriptor can reach NativeFtruncate (I couldn't tell whether the result != -2 path or #5112 rejects it before this point), it would now report EACCES. If it can't reach it, a Require or a comment-free assertion in the test, covering ftruncate on an O_RDONLY descriptor (expect EINVAL), would pin it.
# Conflicts: # core/libs/prx/libkernel/File/src/Stdio.cpp
|
Thanks. A read-only descriptor does reach AI-assisted (Claude Code). |
|
Closing: no title is known to reach this. With the queue at 700+ open PRs, please hold off on new PRs that don't name a title needing the change until it's down. If a title stops on this on current main, reopen with the title ID and the log line. |
What
On Windows,
ftruncate,truncateandsceKernelFtruncateextended a file through_chsize_s. That call writes the whole new length as zero bytes. FreeBSD extends the file without writing data (the new range is a hole on a file system that has holes) and fails a length the file system cannot hold withEFBIG.The difference shows as write load and as an unbounded loop:
ftruncate(fd, 256 MiB)wrote 256 MiB of zeros (measured once by hand);ftruncate(fd, INT64_MAX)kept writing at the disk's speed until the volume was full (found when a failure-path probe of mine did this and filled a test drive; stopped by hand).ftruncatewith a large length.NativeFtruncatenow sets the end of file withSetFileInformationByHandle(FileEndOfFileInfo), which does not write the extension and does not move the file offset. The errors follow FreeBSD where Windows distinguishes them:ERROR_INVALID_PARAMETERon a disk file (a length beyond what the volume can hold; seen withINT64_MAX, the size from which NTFS refuses it was not measured) isEFBIG(27) (on another kind of handle it isEINVAL),ERROR_FILE_TOO_LARGEisEFBIGas well,ERROR_DISK_FULLandERROR_HANDLE_DISK_FULLareENOSPC(28),ERROR_ACCESS_DENIED(a read-only descriptor) staysEACCES, whichftruncatealready turns intoEINVAL. Shrinking works as before.truncate(by path) andsceKernelFtruncateuse the same function. Linux uses::ftruncateas before.Limits, listed in the TechnicalDebt entry for libkernel: the file is not sparse, so NTFS reserves the clusters and a length above the free space fails with
ENOSPCwhere FreeBSD's hole succeeds; and a write behind the end of the file still makes NTFS write zero bytes over the gap (a one-bytepwritefar behind the end takes as long as writing the gap,lseek+writethe same). Making the file sparse would change that and is not part of this PR.Overlap: #4923 ("reject ftruncate on non-file Windows handles") is merged and changed the same lines of
NativeFtruncate; the conflict is resolved in the merge commit described under Tested (itsGetFileTypecheck, non-disk handle:EINVAL, stays in front of theSetFileInformationByHandlecall of this PR).Tested
Windows 10 (19045), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release, on
mainc2565f014; moved since ontomain0214069e3(rebased cleanly) with its test registration and debt entry at their own places, the test length reduced from 256 MiB to 64 MiB and the check changed from a timing comparison to a byte count:guest_truncate_extend(new, length capped at 64 MiB, removes its files, also after a failure) passes: on Windows it reads the process'sWriteTransferCount(GetProcessIoCounters) before and afterftruncateto 64 MiB and requires fewer than 4 MiB written (a zero fill writes the whole length;SetFileInformationByHandlewrites nothing), so no time and no disk speed enter the check; it printsextending by 64 MiB wrote 0 byteswith the change. It also checks the size, the unchanged file offset, the old bytes kept, the extension reading as zeros, shrinking,EINVALfor a negative length and for a read-only descriptor, andtruncateby path. On other hosts only these functional checks run.Stdio.cpp(extending a file must not write its new length as zero bytes: the process wrote about as many bytes as the new length; checked withStdio.cpptaken frommain). The test does not callftruncatewithINT64_MAX, asmainwould fill the disk.ftruncate(fd, INT64_MAX)with the change returnsEFBIGat once (run once by hand with a 20 s kill timer; not part of the test). TheEFBIGandENOSPCbranches are not covered by the test; theEINVALfor a negative length comes from the check that was already in front ofNativeFtruncate.guest_filesystemand the otherguest_file*tests pass.python tools/check_conventions.py --base upstream/mainpasses.-j4), aftermain016db923awas merged into the head (merge commit5e5e41708; one conflict inNativeFtruncateinStdio.cppwith fix(libkernel): reject ftruncate on non-file Windows handles #4923, which rejects a descriptor that is not a disk file withEINVAL: both changes are kept, the check for a non-disk handle now runs beforeSetFileInformationByHandle, so theEINVALbranch of the error mapping for a non-disk handle is gone andERROR_INVALID_PARAMETERis alwaysEFBIG):guest_truncate_extendpasses (0.04 s, ctest) together withguest_filesystem(which holds fix(libkernel): reject ftruncate on non-file Windows handles #4923's tests),guest_kernel_sysandguest_file_vector(4 of 4, 0 failed); withStdio.cpptaken back tomainand the new test kept it fails (extending a file must not write its new length as zero bytes: the process wrote about as many bytes as the new length, exit code0xc0000409).python tools/check_conventions.py --base upstream/mainpasses. This replaces the earlier figures. Not run: the socket tests, Linux, a console, a title.Checklist
main; no other open PR implements the same functions (overlap with fix(libkernel): reject ftruncate on non-file Windows handles #4923 described above)NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt (docs/dev/debt/libkernel.md).mdfiles or images added to the repositoryAI-assisted (Claude Code).
🤖 Generated with Claude Code