Skip to content

test(libc): run guest_fopen_errors on Windows - #9583

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
DavidSamir:test/win-guest-fopen-errors
Oct 26, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
DavidSamir:test/win-guest-fopen-errors

Conversation

@DavidSamir

@DavidSamir DavidSamir commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What

guest_fopen_errors was registered only under if(UNIX) (libc/CMakeLists.txt:137). Its only POSIX dependency was ::symlink, which it uses to build a self-referencing link. Everything it checks, fopen/freopen/fopen_s returning ELOOP, ENAMETOOLONG, EISDIR, EINVAL and ENOENT, also exists on Windows.

How:

  • GuestFopenErrors.cpp: on Windows the loop is made with CreateSymbolicLinkW(L"loop", L"loop", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE). If that fails, the test requires ERROR_PRIVILEGE_NOT_HELD, prints the same "needs Developer Mode or administrator rights" line as GuestDirectory.cpp and GuestAmpr.cpp, cleans up and exits 77. <unistd.h> is included only off Windows.
  • libc/CMakeLists.txt: the test leaves if(UNIX). It gets SKIP_RETURN_CODE 77, so a CI account without symlink rights reports a skip, not a failure, plus the usual WIN32 PATH prepend of the libc directory.

Tested

  • Windows 11 Pro 26200, WinLibs GCC 15.2.0, Release. CPU-side only, no GPU involved. ctest -R ^guest_fopen_errors$ passes.
  • Negative control: with the ELOOP → GuestEloop mapping in HostErrno.cpp:26 disabled, the test fails at line 69 (fopen("loop", "r") no longer reports ELOOP). Restored, it passes.
  • tools/check_conventions.py passes.

Overlap (tools/pr_overlap.py --branch plus git merge-tree):

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 (order with feat(libc): implement std::_Atomic_assert #7851 agreed with its author: neither waits; whichever merges second keeps guest_atomic_assert inside if(UNIX) and guest_fopen_errors outside)
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 22, 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
PR 7851 core/libs/prx/libc/CMakeLists.txt L175-180

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/libc/CMakeLists.txt
PR 6713 core/libs/prx/libc/CMakeLists.txt
PR 7081 core/libs/prx/libc/CMakeLists.txt
PR 7201 core/libs/prx/libc/CMakeLists.txt
PR 7447 core/libs/prx/libc/CMakeLists.txt
PR 7715 core/libs/prx/libc/CMakeLists.txt
PR 8150 core/libs/prx/libc/CMakeLists.txt

@MarcZX10 MarcZX10 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.

The Windows symlink handling is scoped to the test setup, requests the unprivileged-creation flag, and converts the specific permission failure into CTest's configured skip code with cleanup. The CMake registration and Windows PATH setup match neighboring tests. The current conventions check passes; the initial failure is superseded. Hosted relinker and related matrix checks pass; product builds are skipped. The Windows test and negative-control results are author-reported; I did not run them locally.

The CMake hunk overlaps #7851: both edit the same if(UNIX) registration area, and #7851 is still open. Your description already gives a safe resolution (keep #7851's guest_atomic_assert registration inside its UNIX guard while moving this test out). Please agree that order with its author and update Depends on if either PR must land first; currently this checklist says none. If both can be merged independently after that explicit agreement, record the agreed order and keep the two registrations under their correct platform conditions.

@fogydes

fogydes commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

The Conventions check fails on the head of this PR (job 119617768332): Fill in the pull request template: section 'What' is missing or empty, and the same for Tested and Checklist.

Everything else in the matrix is green (relinker on all three OSes, relinker-asan, register-context, write-watch), and python3 tools/check_conventions.py --base origin/main prints the same three template findings locally. Filling in every line of .github/pull_request_template.md (What, Tested, Depends on, Unblocks, GPUs tested, AI-assisted, and tick the boxes) is all that appears to block CI.

@DavidSamir

Copy link
Copy Markdown
Contributor Author

@fogydes thanks. That run was on the first head; the body has since been rewritten to the template and the Conventions job on the current head passes. @MarcZX10, the order with #7851 is agreed: @hypermoist confirmed on #7851 that neither PR depends on the other, and whichever merges second keeps guest_atomic_assert inside if(UNIX) and guest_fopen_errors outside it. It's now recorded in the checklist.

AI-assisted: yes

@MarcZX10 MarcZX10 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.

The #7851 overlap is now resolved by agreement: its author accepted the order-independent resolution, keeping guest_atomic_assert inside if(UNIX) and this test outside it. The checklist records that agreement, so Depends on: none is accurate. The Windows symlink setup and permission-only skip path are also clear, and I found no test-logic issue in the diff. This head is 107 commits behind current upstream/main. Please update it and rerun its checks before merge. Checks on exact head da2d201a1de2b495dcd4cc12c8594387eb34821a: - git diff --check upstream/main...HEAD: passed. - python tools/check_conventions.py --base upstream/main --head HEAD: passed. - Hosted eligibility, labels, report, conventions and relinker/matrix checks pass. Hosted Linux/Windows product jobs are skipped. - The changed Windows behavior was not run locally in this Linux workspace. The Windows 11 test and negative control in the description are author-reported.

I’ve noticed that holding this PR because of #5239 isn’t necessary. It’s fine for PRs to keep coming in; they’ll be handled in due course. Please disregard my earlier request to hold or defer it.

@JJenkx

JJenkx commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Independent Windows run of this PR, as data only. Machine: Windows 11 Enterprise 10.0.22621 (CreateSymbolicLinkW succeeds here, so the test does not take its skip path), Ryzen 9 5950X, WinLibs GCC 15.2.0 UCRT r7, Release. Head da2d201 was merged with #9584 onto main 8b3a6ba.

The main thread is stuck inside libstdc++'s directory walk. The gdb stack, attached after 4 s:

#0 ntdll!ZwCreateFile
#1 KERNELBASE!CreateFileW
#3 ucrtbase!_wfindnext64i32
#4-7 libstdc++-6.dll
#8 main

This is the final std::filesystem::remove_all(root, error). A standalone program with the same toolchain shows the cause. It creates loop with CreateSymbolicLinkW(L"loop", L"loop", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) in a fresh directory:

  • symlink_status("loop") reports not_found and is_symlink is false, because this libstdc++ does not see the reparse point.
  • remove_all on the directory then never returns.
  • Calling DeleteFileW(L"loop") first lets remove_all finish: 2 entries removed, no error.

Each killed run also left an anyps5-fopen-errors-<n> directory in %TEMP%, with the loop inside. rmdir /s /q removes it.

One way to fix it: on _WIN32, delete the link with DeleteFileW(L"loop") while the root is still the current directory, before the cleanup remove_all, and also before the early remove_all in the exit-77 path if that path ever creates it. I have not tested that change in the test itself. Your Windows 11 26200 run passing may come from a different libstdc++ or Windows filesystem behaviour; I can't tell which from here.

AI-assisted (Claude Code).

@JJenkx

JJenkx commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up: I tried the suggested change in the test itself, on the same local merge (da2d201 + #9584 on main 8b3a6ba, same machine):

     std::error_code error;
+#ifdef _WIN32
+    DeleteFileW(L"loop");
+#endif
     std::filesystem::current_path(std::filesystem::temp_directory_path(), error);
     std::filesystem::remove_all(root, error);

With it, ctest -R ^guest_fopen_errors$ passes in 10 of 10 runs, and no anyps5-fopen-errors-* directory is left in %TEMP%. Without it, the same build timed out in 10 of 10 runs. The ELOOP checks themselves are unchanged. The link is still the current directory's loop at that point, because current_path has not moved yet.

AI-assisted (Claude Code).

@JJenkx JJenkx mentioned this pull request Oct 22, 2026
12 tasks done

@MarcZX10 MarcZX10 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.

The Windows test has a reproducible cleanup hang on this exact head. main() creates the self-referential loop symlink in EnterFreshRoot(), runs the checks, then changes to the temp parent and calls std::filesystem::remove_all(root) at GuestFopenErrors.cpp:120-121. An independent Windows/MinGW run reports this head timing out 10/10 because remove_all does not recognize the reparse point and loops in the directory walk. The same reviewer added DeleteFileW(L"loop") before changing the current directory and reports 10/10 passes with no leftover temp directories. Please remove the link explicitly on _WIN32 while the test is still in the fresh root, check that removal succeeds, then leave the existing remove_all cleanup in place. The permission-denied skip path is reached only when link creation fails, so it should not contain a created link.

I ran g++ -std=c++20 -Wall -Wextra -Werror -fsyntax-only -Icore/libs core/libs/tests/GuestFopenErrors.cpp and the corresponding x86_64-w64-mingw32-g++ command on exact head da2d201a1de2b495dcd4cc12c8594387eb34821a; both pass. These are syntax checks only; I cannot execute the Windows binary here. Hosted product jobs are skipped. The independent Windows timeout/fix runs are the runtime evidence for this blocker.

Separately, I withdraw my earlier generic intake objection: your contribution-count eligibility means the queue-based hold does not apply. The merge-order agreement with #7851 is already recorded. The branch still needs an update to current upstream main (8b3e39832af85f404c6195418d11b3e1ebd1924d, 150 commits ahead of this head), followed by the focused Windows run. Current-main diff-check and conventions pass.

@MarcZX10

Copy link
Copy Markdown
Contributor

I’ve noticed that my earlier request to hold this PR based on #5239 was unnecessary. It’s fine to keep submitting PRs; maintainers will handle them in due course. Please disregard that request.

@THEMANnt THEMANnt left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed da2d201a1de2b495dcd4cc12c8594387eb34821a against 6ef5a1c4c92272598b18adeb9b5414112f8d15ed. No actionable findings in this focused review.

Source review: Inspected the complete platform/registration change and fixture cleanup. Windows uses CreateSymbolicLinkW for the self-referential file link; privilege failure is explicitly reported as skip 77 after leaving and deleting the fresh fixture directory. CTest now recognizes that skip and adds the Windows runtime path. Existing fopen/reopen/error assertions and non-Windows creation remain unchanged.

Validation: Windows / GCC 15.2: exact head syntax compilation passed; baseline fails because ::symlink is unavailable. The submitted test linked against existing local development libc/libkernel and returned 77 with the expected privilege message. That compatibility execution verifies the skip path, not the fopen assertions or a pinned runtime build. Conventions, whitespace and registration passed. Effective hosted relinker/ASan and metadata checks succeeded; product jobs were skipped.

Scope: Bounded Windows fixture/CTest portability change. Successful symlink-capable fopen execution remains unverified locally; no privileges/settings were changed and no game ran.

@adriadam10 adriadam10 self-assigned this Oct 25, 2026
DavidSamir and others added 2 commits October 25, 2026 18:23
The test was registered only under if(UNIX) because it built its symbolic
link loop with ::symlink. On Windows it now makes the loop with
CreateSymbolicLinkW (unprivileged create) and exits 77, which CTest reports
as skipped, when the account cannot create symbolic links.
…xture

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@adriadam10
adriadam10 force-pushed the test/win-guest-fopen-errors branch from da2d201 to 2736e7f Compare October 25, 2026 17:30
@adriadam10
adriadam10 dismissed MarcZX10’s stale review October 26, 2026 09:24

Dismissing a stale review: the requested changes were addressed after it (new head) and the merge queue blocks on it. Please request changes again if anything is still open.

@adriadam10
adriadam10 merged commit e701168 into boykopovar:main Oct 26, 2026
17 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

The hang MarcZX10 reported is fixed on the current head: our commit deletes the loop link with DeleteFileW before leaving the fixture directory, as in @JJenkx's trial (10/10 passes there, 10/10 timeouts without it). The CMake registration, the PATH setup, the 77 skip path and the order with #7851 were checked; merged.

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.

6 participants