Skip to content

fix(libkernel): read a watched file's path before its state on Windows - #9227

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
MaxLikesCode:fix/kqueue-snapshot-path-before-state
Oct 23, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
MaxLikesCode:fix/kqueue-snapshot-path-before-state

Conversation

@MaxLikesCode

Copy link
Copy Markdown
Contributor

What

guest_kqueue can abort on Windows with:

terminate called after throwing an instance of 'std::runtime_error'
  what():  EVFILT_VNODE: the parent watch of "\\?\C:\$Extend\$Deleted\" failed, FindFirstChangeNotification error=5

TakeSnapshot read the file information (FileBasicInfo, FileStandardInfo) and then the path (GetFinalPathNameByHandleW). A delete between the two left a snapshot with the old link count and delete flag but a path already under \$Extend\$Deleted\. IsDeleted was false for it, so the watch thread asked WatchParent for \$Extend\$Deleted\, which FindFirstChangeNotification refuses with ERROR_ACCESS_DENIED. The exception on the detached watch thread ends the process. FileWatch hits this when it removes its file while a watch thread it has just stopped is still in a poll; the watch threads are detached, and Stop only sets their event.

The snapshot now reads the path first. A path under \$Extend\$Deleted\ is only returned after the delete, so the information read after it reports the delete, IsDeleted holds and the parent watch is skipped, as #8623 does at registration. A delete between the two reads now leaves the old path with the deleted state, and the next poll replaces the path.

What Windows reports for a deleted file that is still open, measured on this machine with DeleteFileW on NTFS:

  • the only name: NumberOfLinks 0, DeletePending 1, path \\?\C:\$Extend\$Deleted\...;
  • one of two hard links, the one the handle was opened by: NumberOfLinks 1, DeletePending 1, path under \$Extend\$Deleted\.

So IsDeleted is true whenever the path is under \$Extend\$Deleted\ and the information is read after it. #7049 also changes IsDeleted, to treat a path under \$Extend\$Deleted\ as deleted for a rename into it; this change is independent of it and touches neither IsDeleted nor SnapshotHints.

Tested

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 under Silent stubs in their library's technical debt file
  • 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
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes. Written with Claude Code (Claude Opus 5.5).

🤖 Generated with Claude Code

TakeSnapshot read the file information and then the path. A delete
between the two left a snapshot whose path was already under
\$Extend\$Deleted\ but whose link count and delete flag were not, so
IsDeleted was false and the watch thread asked for a parent watch of
\$Extend\$Deleted\. FindFirstChangeNotification refuses it with
ERROR_ACCESS_DENIED, and the exception on the detached watch thread
ended the process. guest_kqueue hit this when FileWatch removed its
file while a watch thread was still polling.

The path is now read first. A path under \$Extend\$Deleted\ is only
returned after the delete, so the information read after it reports
the delete and the parent watch is skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 5021 core/libs/prx/libkernel/Equeue/src/FileEvent.cpp
PR 6879 core/libs/prx/libkernel/Equeue/src/FileEvent.cpp
PR 7049 core/libs/prx/libkernel/Equeue/src/FileEvent.cpp
PR 7088 core/libs/prx/libkernel/Equeue/src/FileEvent.cpp
PR 7121 core/libs/prx/libkernel/Equeue/src/FileEvent.cpp
PR 9823 core/libs/prx/libkernel/Equeue/src/FileEvent.cpp

@oneandonlydean oneandonlydean self-assigned this Oct 22, 2026
@oneandonlydean
oneandonlydean merged commit cd435c5 into boykopovar:main Oct 23, 2026
11 checks passed
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.

2 participants