Skip to content

fix(libc): fail freopen of an empty name with ENOENT on Windows - #9768

Merged
oneandonlydean merged 2 commits into
boykopovar:mainfrom
DavidSamir:fix/libc-freopen-empty-name
Oct 23, 2026
Merged

oneandonlydean merged 2 commits into
boykopovar:mainfrom
DavidSamir:fix/libc-freopen-empty-name

Conversation

@DavidSamir

Copy link
Copy Markdown
Contributor

What

freopen with an empty file name failed with EINVAL on Windows where FreeBSD gives ENOENT.

freopen_nid_postfix (libc/src/FileAndHeap.cpp) resolved the path only if (*filename), so an empty name went to FileStream::Reopen and from there to the host: _wfreopen(L"", ...) in the MS CRT answers EINVAL. FreeBSD 11's freopen.c opens the name (f = _open(file, oflags, DEFFILEMODE), line 153), keeps the errno (sverrno = errno, line 162), restores it after closing the stream (errno = sverrno, line 196) and returns NULL, so an empty name gives ENOENT from open. fopen of an empty name already gives ENOENT on Windows.

An empty name now fails the way an unresolvable one does: ENOENT, the guest stream is closed (a host standard stream is detached and the host stream is left as it was), and NULL is returned.

freopen(NULL, ...) is unchanged: it was already refused with ENOTSUP before this code.

Tested

Windows 11, WinLibs GCC 15.2.0, Release, on main 8b3e39832a:

  • guest_freopen_host_standard (new) passes 3 of 3. It redirects the host standard output to a file, calls freopen("", "w", stdout), and checks that the result is NULL with ENOENT, that a host write to descriptor 1 still reaches the original destination, and that the guest stream is closed afterwards (fputs gives -1 with EBADF, as in GuestStandardStreams).
  • Negative control: with FileAndHeap.cpp from main and the same test, it aborts at the ENOENT check with errno 22.
  • The neighbouring stream tests pass: guest_standard_streams with its --closed-stdout and --exclusive-stdout forms, file_streams, guest_filesystem, the six guest_file_lock_* tests, guest_append_streams, guest_append_writes, guest_fopen_long_host_path and guest_host_standard_error_windows.
  • python tools/check_conventions.py --base origin/main passes.

Linux (Ubuntu 24.04 in WSL2, GCC 13.3.0, Release, main 3ca07d086): the new test passes 3/3, with guest_standard_streams, file_streams and guest_filesystem. I did not run the new test without the fix on Linux, where the host already answers ENOENT.

Overlap: #8873 (test(libc): check host stdout after freopen) tests the host handle after a successful freopen; this test covers the failing empty name and does not repeat it. tools/pr_overlap.py --branch (the version from #9539) reports nine pull requests as not compared because they conflict with main themselves; none of them changes freopen_nid_postfix. #9527 (freopen_s) calls freopen_nid_postfix and does not touch the guard.

Not run on macOS.

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

freopen skipped path resolution for an empty file name and handed it to the host, so the MS CRT answered EINVAL where FreeBSD's freopen, which opens the name and keeps that errno (lib/libc/stdio/freopen.c lines 153, 162 and 196), gives ENOENT, as fopen of an empty name already does. An empty name now fails like an unresolvable one: ENOENT, the guest stream is closed and a host standard stream is left as it was.
freopen of an empty name on stdout fails with ENOENT as fopen of an empty name does, the guest stream is closed afterwards and the host standard output is still writable and still reaches its destination. Before the previous commit the call failed with EINVAL.

@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 empty-name branch now follows the same cleanup path as a path-resolution failure, with ENOENT: ordinary guest streams close, while host-standard streams detach so the underlying host descriptor remains usable. The regression exercises the important host-standard case by redirecting descriptor 1, calling freopen("", ...), confirming the host write still reaches the original target, and then checking the guest stream reports EBADF. The negative control and neighboring stream tests strengthen the report.

Current-main merge-tree and diff-check are clean, and current-base conventions pass. The author reports focused Windows and Linux runs; the hosted product Linux/Windows jobs are skipped for this path. I did not run the full libc target locally because this checkout has no configured build and less than 1 GB free disk. No duplicate implementation or actionable code issue found.

@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 9763 core/libs/prx/libc/src/FileAndHeap.cpp L364-371

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 3965 core/libs/prx/libc/CMakeLists.txt
PR 5021 core/libs/prx/libc/CMakeLists.txt
PR 6044 core/libs/prx/libc/CMakeLists.txt
PR 7081 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/src/FileAndHeap.cpp
PR 7201 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/src/FileAndHeap.cpp
PR 7715 core/libs/prx/libc/CMakeLists.txt
PR 8150 core/libs/prx/libc/CMakeLists.txt
PR 8658 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/src/FileAndHeap.cpp
PR 8965 core/libs/prx/libc/CMakeLists.txt
PR 9526 core/libs/prx/libc/src/FileAndHeap.cpp
PR 9527 core/libs/prx/libc/src/FileAndHeap.cpp
PR 9929 core/libs/prx/libc/CMakeLists.txt

@JJenkx

JJenkx commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Independent Windows run, as data only. Machine: Windows 11 Enterprise 10.0.22621, Ryzen 9 5950X, WinLibs GCC 15.2.0 UCRT r7, Release, BUILD_TESTING=ON, Python 3.15. #9832, #9793 and #9768 were merged together onto main 11f976b, without conflicts, and each test was run 3 times. guest_freopen_host_standard passes 3 of 3.

AI-assisted (Claude Code).

@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 author has now supplied an independent Windows run of guest_freopen_host_standard 3/3 on a combined tree with #9832 and #9793 atop current main 11f976b27c, without conflicts. The negative control and neighboring stream test results are also recorded. Current-head conventions and git diff --check pass; the current PR check set still skips product Linux/Windows builds. No new code issue found.

@oneandonlydean oneandonlydean self-assigned this Oct 23, 2026
@oneandonlydean
oneandonlydean merged commit b8f8822 into boykopovar:main Oct 23, 2026
18 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.

4 participants