Skip to content

feat(libkernel): implement dup2 - #1870

Closed
TheAggie wants to merge 2 commits into
boykopovar:mainfrom
TheAggie:feat/libkernel-dup2
Closed

TheAggie wants to merge 2 commits into
boykopovar:mainfrom
TheAggie:feat/libkernel-dup2

Conversation

@TheAggie

@TheAggie TheAggie commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

What

dup2 was not exported. ccleste (PPSA59322) calls it to move its standard error onto a pipe that it forwards to the kernel log.

Two commits:

  1. dup2. Guest file descriptors are host CRT descriptors, so dup2 duplicates through the host (_dup2 on Windows, dup2 elsewhere).
    • A negative or closed source, or a negative target, fails with EBADF. Duplicating a descriptor onto itself returns it.
    • On Windows, a directory descriptor's path entry for the target is forgotten, because the target now names something else.
    • Socket descriptors live in their own table, so a socket on either side throws. On Windows a directory source throws too, for the same reason.
  2. sceKernelDebugOutText keeps the host's standard error. Because guest descriptors are host descriptors, a guest that moves descriptor 2 also moves the stream sceKernelDebugOutText writes to. ccleste forwards its standard error into the kernel log, so it read its own debug output back and logged it again without end. Before the guest's first dup2 onto descriptor 2, the host's standard error is duplicated and kept, and sceKernelDebugOutText writes there from then on.

Tests (guest_filesystem):

  • A pipe's write end duplicated to descriptor 60 still writes into the pipe after the original is closed. Same-descriptor and the three EBADF cases are covered.
  • With the host's standard error captured to a file and a guest pipe moved onto descriptor 2, sceKernelDebugOutText output reaches the captured host stream, and a guest write(2, ...) reaches the pipe.

Not covered: on Windows, the CRT's _dup2 onto descriptor 2 also changes the process's STD_ERROR_HANDLE, so AnyPS5's own crash reports written through that handle end up in the guest's pipe too. Restoring the Win32 handle would be a separate change.

Tested

Windows 11 Pro (26200), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release.

  • The full suite (485 tests) passes on current main with this PR and four other small libc/libkernel PRs of mine merged together (atol, getprogname, dup2, ctime/rewind, pthread_set_name_np). It was run from the generated CTestTestfile.cmake commands because antivirus blocks ctest.exe on this machine.
  • tools/check_conventions.py --base origin/main passes on this branch alone.

Not tested on Linux or 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 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
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

🤖 Generated with Claude Code

TheAggie and others added 2 commits October 8, 2026 17:13
Guest file descriptors are host CRT descriptors, so dup2 duplicates through
the host. A negative or closed source, or a negative target, fails with
EBADF; socket and directory descriptors, which live in their own tables,
throw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Guest descriptors are host descriptors, so a guest that moves standard
error with dup2 also moves the stream sceKernelDebugOutText writes to. A
title that forwards its standard error to the kernel log through a pipe
then reads its own debug output back and logs it again without end. Before
the guest's first dup2 onto descriptor 2, the host's standard error is kept,
and sceKernelDebugOutText writes there.

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

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

Progress report (ce64d8e → bf51014)

➖ System libraries: 84.76% (+0.0%, +1 implemented functions)

✅ 1 implemented
Library Name
libkernel dup2_nid_postfix

@github-actions

github-actions Bot commented Oct 9, 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
#1755 core/libs/tests/GuestFilesystem.cpp L29
#1764 core/libs/tests/GuestFilesystem.cpp L29

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Closing: ccleste (PPSA59322) stops at shm_open on main before it calls dup2. Reopen when dup2 is its next stop on main, with the log line.

@Recapi

Recapi commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

@TheAggie dup2 is now a title's next stop on main: Prospero Win (PPSA99995, catalog record 0.1.1, sha256 checked) stops at it on main d70b899, with nothing else merged:

./app.elf: symbol lookup error: ./app.elf: undefined symbol: wdUufa9g-D8

Linux (Ubuntu, GCC 15.2.0), eboot.bin unwrapped from its SELF container, relinked with relinker --registry, run in a bwrap sandbox. tools/import_audit.py lists 9 absent imports for it; besides dup2 they are dup, seteuid, sigemptyset, getdents, sigaction, geteuid, sceKernelGetModuleInfo and sceSysmoduleGetModuleHandleInternal, so it should stop at the others afterwards.

That meets the reopen condition in the comment above, if you want to pick it back up. No open PR implements dup2 (checked against the 555 open PRs).

@rmz-24

rmz-24 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Windows confirmation of the above, in case it helps reopening: Windows 11 Pro 25H2 (10.0.26200.9457), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release, Prospero Win PPSA99995 (catalog record 0.1.1, sha256 checked), eboot.bin unwrapped from its SELF and relinked with --windows.

  • main d70b899, nothing else merged: FAIL: unresolved ELF import wdUufa9g-D8 from libkernel.prx (dup2), the first stop.
  • main + this branch (bf51014, both commits cherry-pick cleanly): the title gets past dup2 and stops at the next absent import, +F7C-hdk7+E (sigemptyset, feat(libkernel): implement the sigset functions #1641).
  • guest_filesystem passes, and the full ctest is 510/510.

AI-assisted: yes

@TheAggie

Copy link
Copy Markdown
Contributor Author

Thanks @Recapi and @rmz-24 for the Prospero Win runs. Since this was closed, #2478 by @itsbo5 implements both dup and dup2 (with guest sockets) and has a Windows check from @babyhuey, so it covers more than this PR. I'll leave this one closed in favour of #2478.

AI-assisted (Claude Code).

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