Skip to content

fix(libkernel): preserve Windows file timestamp precision - #1518

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
ArjenS-LA:fix/windows-file-timestamps
Oct 10, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
ArjenS-LA:fix/windows-file-timestamps

Conversation

@ArjenS-LA

Copy link
Copy Markdown
Contributor

What

On Windows, utimes and futimes discarded the microsecond fields when passing timestamps to _wutime. The stat bridge also reported zero fractional seconds, even when the file contained a precise native timestamp. The existing filesystem test requested a half-second timestamp but checked only whole seconds.

This change:

  • Sets access and modification times with SetFileTime, retaining guest microseconds. Null time arrays use the current Windows clock.
  • Reads native access, modification, creation and metadata-change times through FileBasicInfo, retaining 100-nanosecond precision. st_ctim uses metadata change time and st_birthtim uses creation time.
  • Uses ReOpenFile for regular-file descriptor updates, including read-only descriptors, so the operation targets the open file rather than reopening its pathname.
  • Rejects invalid microsecond fields in the path-based API as well as the descriptor-based API, and rejects Windows timestamp conversion overflow.
  • Adds the registered guest_file_timestamps regression test and documents the remaining host limits.

Native API behavior follows Microsoft's SetFileTime, FILE_BASIC_INFO, and ReOpenFile documentation.

Tested

Windows validation of PR head acea8db3a4cd4db9dfdfbe1f3dcaeceff939c9d6, rebased onto upstream main 4b2413fb5716bb56101e777536fe374e49ba90a2. This branch is independent of #1507 and contains no wcsrtombs changes.

Windows 11 Enterprise 10.0.26200; WinLibs MinGW-w64 GCC 15.2.0 r2 UCRT/posix/SEH, Ninja Release, BUILD_TESTING=ON, SPIRV-Tools disabled. The installed toolchain is r2, not CI's r7 package.

The five selected test targets and their libc/libkernel dependencies rebuilt successfully after the rebase:

CTest test Result
guest_file_timestamps Passed
guest_filesystem Passed
guest_directory_buffer Passed
guest_directory_close Passed
guest_file_vector Passed

5/5 passed, 0 failed, 0 skipped. The new test covers distinct access/modification fractions, read-only descriptors, all three guest stat entry points, native 100-nanosecond timestamps, creation versus metadata-change time, dates around the Unix epoch, invalid microseconds in either field, unrepresentable Windows dates, unchanged timestamps after rejected updates, null time arrays, directories, missing paths and pipe stat behavior.

Focused reproduction, using the original timestamp implementation from e28fa01ca60c1db29df747b16ce5595c68d7cc27 and then rerun against the published head:

Operation Before PR head
Native Windows stores a .500000000-second fraction; guest stat/fstat reads it .000000000 .500000000
Guest utimes requests .500000; native Windows reads it .000000000 .500000000
Guest futimes requests .500000; native Windows reads it .000000000 .500000000
Guest utimes receives tv_usec=1000000 Success -1, guest EINVAL=22

The new regression test was also compiled against the pre-fix runtime and failed at access time preserves microseconds. Native Windows reads independently check the stored timestamps, so matching guest read/write bugs cannot make the precision checks pass. These are focused reproductions, not a full-suite baseline comparison.

The committed-head conventions check, diff whitespace check and test-registration check passed. The registration check found no newly unregistered test executables.

These results cover the pinned PR head. Linux, the full CTest suite, the complete default/libs build, games and the eventual merged tree were not tested. No performance or game-compatibility conclusion is claimed.

Remaining limits are recorded in TechnicalDebt: timestamps follow the host filesystem's range and resolution; the exact Windows epoch is rejected because zero means leave unchanged to SetFileTime; guest directory descriptors still use their recorded paths.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source (none added)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes — OpenAI Codex assisted with implementation, regression tests, execution, analysis, and this report.

@klosixgaming-arch klosixgaming-arch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a well-implemented fix for Windows file timestamp precision. The approach of using GetFileInformationByHandleEx with FileBasicInfo and direct SetFileTime calls is the right way to preserve microsecond/nanosecond precision that _wstat64/_utime lose.

Strengths:

  • Clean encapsulation in a dedicated WindowsFileTime helper class
  • Thorough input validation (microsecond ranges, epoch boundaries, overflow checks)
  • Comprehensive test suite covering edge cases: pre-epoch times, invalid microseconds, boundary conditions, directory timestamps, pipe handling
  • Proper error mapping from Windows errors to POSIX errno values
  • Good documentation of remaining limitations in TechnicalDebt.md

Minor notes:

  • The ticks == 0 check in Encode() correctly handles the "leave unchanged" semantics of SetFileTime, which is a subtle but important detail
  • Test coverage for both file and directory descriptors is excellent

Ready to merge. This will improve compatibility with games that rely on precise file timestamps.

@Dextroid17

Copy link
Copy Markdown
Contributor

Verified the same way — merges clean onto current main, and the Windows job for this head built and ran the new test:

  merge onto current main (2b297842)                  no conflicts
  Build run for this head (37766406044), windows job
     Start 368: guest_file_timestamps
     Test #368: guest_file_timestamps ... Passed 0.02 sec

Start and Passed rather than a skip matters here for the same reason as above; and the test is Windows-gated (four preprocessor guards), so this job is the only place it can run.

The approach reads well: _wstat64 fills the struct as before, then GetFileInformationByHandleEx(FileBasicInfo) refines the three timestamps from the handle, with FILE_FLAG_BACKUP_SEMANTICS so directories work, and GetFileType != FILE_TYPE_DISK leaving the _wstat64 values in place instead of failing. Isolating the conversion in WindowsFileTime.hpp keeps the range/rounding rules in one place, and the debt entry you added for the range and the EINVAL rejection is the right place for them.

One question on the non-disk path: for a pipe or a device, ReadTimes returns success without setting hasTimes, so callers keep the whole-second _wstat64 values silently. If that is intended, it may be worth a clause in the same debt entry; if not, the caller has no way to tell the timestamp it got is the coarse one.

Written with AI assistance.

@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main (only conflict: adjacent TechnicalDebt.md line, both kept). Built and ran the libkernel/libs tests on Linux, all green including guest_file_timestamps and guest_filesystem; the Windows-specific paths are covered by the Windows job on this head (guest_file_timestamps passed, not skipped) and the author's Windows run. Merging once CI is green on the rebased head.

@adriadam10
adriadam10 force-pushed the fix/windows-file-timestamps branch from acea8db to 705e49d Compare October 9, 2026 23:59
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#2015 docs/dev/TechnicalDebt.md L255

@adriadam10
adriadam10 merged commit 67bb998 into boykopovar:main Oct 10, 2026
8 checks passed
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
…mestamps

fix(libkernel): preserve Windows file timestamp precision
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants