Skip to content

feat(libkernel): support fcntl and ioctl on non-socket descriptors - #3840

Merged
oneandonlydean merged 3 commits into
boykopovar:mainfrom
ahir061:feat/libkernel-fcntl-files
Oct 12, 2026
Merged

oneandonlydean merged 3 commits into
boykopovar:mainfrom
ahir061:feat/libkernel-fcntl-files

Conversation

@ahir061

@ahir061 ahir061 commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

What

Carries screamm's screamm/AnyPS5:feat/libkernel-fcntl-files from #3757 unchanged, with their authorship (two commits on main a11c8f7f):

  • 70dd0c00 feat(libkernel): support fcntl on non-socket descriptors
  • 88c9ee8d feat(libkernel): support ioctl on non-socket descriptors

fcntl, sceKernelFcntl, ioctl and _ioctl worked only on libSceNet sockets, so every file, pipe and standard stream got EBADF. Yamagi Quake II stops on it (Sys_Error: change stdin to blocking Bad file descriptor), and XPSemu, VLC, RetroArch, Porpoise and Ruffle call fcntl on such descriptors (#3757).

The new File/src/DescriptorControl.cpp keeps a close-on-exec flag per descriptor and a status-flag set shared by duplicates, following FreeBSD 11.4 kern_fcntl/kern_dup. It covers F_GETFD/F_SETFD, F_GETFL/F_SETFL, F_DUPFD/F_DUPFD_CLOEXEC, F_RDAHEAD/F_READAHEAD and record locks on Linux. O_NONBLOCK on a pipe makes empty reads and full writes return EAGAIN. ioctl answers FIONBIO, FIONREAD (file size minus offset; queued bytes on a pipe's read end) and ENOTTY for the terminal requests. What it does not model throws with the flag and descriptor kind; #3757 lists the cases. Sockets keep their existing path. The fcntl, sceKernelFcntl and ioctl TechnicalDebt entries are updated.

It touches the same close/dup/pipe code in Stdio.cpp and Open.cpp as #3649, #3644 and #3681, so whichever merges second needs a rebase; I'll keep this one current.

Closes #3757.

Tested

  • Windows, by screamm (libkernel: fcntl and ioctl reject every non-socket descriptor (Yamagi Quake II, XPSemu, VLC, RetroArch); branch ready #3757): Windows 11 Pro 26220, WinLibs MinGW-w64 GCC 15.2.0 r7, Ninja, Release, BUILD_TESTING=ON, full build including libs: guest_kernel_fcntl, guest_dup, guest_udp, guest_socket_pair, guest_socket_read_write, guest_poll, guest_terminal, guest_file_vector, guest_aio 3/3.
  • Linux, for this carry: Ubuntu 24.04, GCC 13.3, Ninja, Release, BUILD_TESTING=ON, head 88c9ee8d:
    • The same nine tests pass. This also covers the Linux-only paths in guest_kernel_fcntl: record locks through the host's POSIX locks, and clearing O_APPEND on a file.
    • Built against main's libkernel, guest_kernel_fcntl aborts (sceKernelFcntl: file descriptors are not implemented, fd=5) and guest_dup aborts; both pass with these commits.
    • All 88 libkernel tests registered on Linux: 85 pass. guest_truncate and guest_kernel_stack_protect (linux CI: guest_kernel_stack_protect aborts on main since #2982 (libc unwinder, GCC 13 only) #3459) abort the same way on unchanged main in this environment. guest_raise_exception passes but takes 72 s with these commits and 71 s on main, over its 30 s timeout only because the test machine has a single core.
  • tools/check_conventions.py --base main passes.
  • Not run: titles.

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
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

#ifdef _WIN32
__builtin_sysv_va_list arguments;
__builtin_sysv_va_start(arguments, command);
const auto argument = __builtin_va_arg(arguments, long long);

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.

Could this only read the variadic argument for commands that require one, and read the command’s actual argument type? F_GETFL and F_GETFD have no third argument, but the non-socket path always calls va_arg here (and reads long at the POSIX branch below). The new tests call fcntl_nid_postfix(file, GetFlags) without a third argument at GuestKernelFcntl.cpp:102 and :175; fetching a missing vararg is undefined behavior. Commands that do take an argument also use different promoted types: flag/descriptor commands pass int, while lock commands pass a pointer. A command-specific extraction would avoid both mismatches on Windows and POSIX.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, thanks. Fixed in the latest commit: File::FcntlArgumentOf maps each command to no argument, an int or a pointer, and fcntl and sceKernelFcntl now read only that. F_GETFD, F_GETFL, F_GETOWN and unknown commands read nothing; the flag, descriptor and read-ahead commands read an int; the lock commands read a pointer.

It also fixed a real case: F_READAHEAD with -1 was read as 4294967295 and enabled read-ahead. guest_kernel_fcntl now checks -1 and 4096 through both entry points; it fails before this commit and passes after (Linux, GCC 13.3). The nine related tests pass, the changed files compile with MinGW-w64, and screamm’s two commits are unchanged.

@ahir061

ahir061 commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (11d9c00). Two conflicts: the ioctl, _ioctl TechnicalDebt entry next to the new sendfile entry from #3568 (both kept), and the Windows NativeWrite in Open.cpp, where main's shared-memory write path and this PR's pipe ENOSPC → EAGAIN mapping are now combined: shared-memory descriptors go through WriteSharedMemory_nid_no_patch first, everything else through _write with the pipe mapping. Re-tested on Linux (Ubuntu 24.04, GCC 13.3): the nine fcntl/ioctl tests and guest_ioctl_getsid pass, and 98 of 99 libkernel tests pass (guest_named_shm_concurrency fails the same way on unchanged main there, because the run is as root). The changed files compile with MinGW-w64, and tools/check_conventions.py passes.

screamm and others added 3 commits October 12, 2026 12:17
fcntl and sceKernelFcntl answered EBADF (sceKernelFcntl threw) for
every descriptor outside the libSceNet socket table, so files, pipes,
directories and descriptors 0 to 2 could not be queried or changed.

Observed: Yamagi Quake II (PPSA99007) stops with
"Sys_Error: change stdin to blocking Bad file descriptor", from
fcntl(fileno(stdin), F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY)
(yquake2 src/backends/unix/system.c:81-86).

Predicted from the titles' sources and decoded call sites: XPSemu
(PPSA97358, 97 fcntl sites: F_GETFD 12, F_SETFD 20, F_GETFL 8,
F_SETFL 9, F_DUPFD 1, F_DUPFD_CLOEXEC 7, F_GETLK 9) fails in
qemu_init_main_loop, where its pipe2 (pipe plus F_SETFD FD_CLOEXEC) and
glib's g_unix_set_fd_nonblocking (F_GETFL, F_SETFL) build the event
notifier (util/event_notifier-posix.c:44-67); VLC (PPSA85300, 43 sites,
F_RDAHEAD on every file it opens), RetroArch (PPSA99169, 17),
Porpoise (PPSA99764, 13) and Ruffle (9) call it on files and pipes.

The commands follow FreeBSD 11.4 kern_fcntl and kern_dup
(sys/kern/kern_descrip.c:479-799, 812, release/11.4.0 8c639945):
F_GETFD/F_SETFD keep the close-on-exec flag per descriptor, F_GETFL
reports the host access mode with the status flags, F_SETFL changes
only O_NONBLOCK, O_APPEND, O_ASYNC, O_FSYNC, O_DIRECT and FRDAHEAD,
F_DUPFD/F_DUPFD_CLOEXEC give the lowest free descriptor at or above the
start through dup (EINVAL for a negative start), F_DUP2FD goes through
dup2, F_RDAHEAD/F_READAHEAD set FRDAHEAD on files (EBADF on pipes,
ENOTTY elsewhere), and unknown commands fail with EINVAL. Status flags
live in a guest table shared by dup, dup2 and F_DUPFD like an open file
description. O_NONBLOCK switches a pipe (PIPE_NOWAIT on Windows, where an
empty read and a full write now report EAGAIN; the host flag on Linux).
Record locks use the host POSIX locks on Linux and throw on Windows.
Changes the host cannot model throw naming the flag and descriptor kind.
Sockets keep their path unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ioctl and _ioctl answered EBADF for every descriptor outside the
libSceNet socket table. Decoded call sites: PS5MC (PPSA99420, FIONBIO x3,
FIONREAD x2, TIOCGWINSZ x3), ProsperoTV (PPSA99003, FIONBIO on a pipe
once poll on pipes is in), XPSemu (PPSA97358, 21 sites).

Following FreeBSD 11.4 kern_ioctl (sys/kern/sys_generic.c:758),
vn_ioctl (sys/kern/vfs_vnops.c:1488), pipe_ioctl (sys/kern/sys_pipe.c:1346)
and randomdev_ioctl (sys/dev/random/randomdev.c:301), release/11.4.0
8c639945: FIONBIO sets or clears O_NONBLOCK through the same status
flags as fcntl F_SETFL (EFAULT for a null argument before EBADF);
FIONREAD reports the size past the offset of a file, the queued bytes of
a pipe's read end, 0 on its write end, ENOTTY on the random device, and
throws on a directory or another character device; termios requests
(group 't', TIOCGWINSZ included) fail with ENOTTY, as tcgetattr already
takes these descriptors as not a terminal; any other request throws
naming the request and descriptor kind. Sockets keep their path
unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
fcntl and sceKernelFcntl read a third argument as a 64-bit integer for
every command on a non-socket descriptor. F_GETFD and F_GETFL pass no
third argument, and the commands that take one pass an int or a pointer.
They now read nothing, an int or a pointer, by command. An int read as
a 64-bit value kept whatever the caller left in the upper half of the
register, so F_READAHEAD -1 enabled read-ahead instead of disabling it.
@ahir061
ahir061 force-pushed the feat/libkernel-fcntl-files branch from 50e40f4 to 99b1b32 Compare October 12, 2026 06:47
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 12, 2026
@ahir061

ahir061 commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Rebased again onto current main (d06c065). #3968 (openat) moved sceKernelOpen's open path into OpenNative; the descriptor registration from this PR now sits at the end of OpenNative, so descriptors opened through openat get the same fcntl and ioctl handling, and the random-device branch registers its descriptor as before. Re-tested on Linux: the nine fcntl/ioctl tests, guest_ioctl_getsid and guest_kernel_sys (openat) pass; the changed files compile with MinGW-w64; tools/check_conventions.py passes.

@github-actions

github-actions Bot commented Oct 12, 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
#3649 core/libs/prx/libkernel/CMakeLists.txt L5
core/libs/prx/libkernel/File/src/Stdio.cpp L14-15, L421, L526
#3787 core/libs/prx/libkernel/File/src/Open.cpp L206
core/libs/prx/libkernel/File/src/Stdio.cpp L476, L502, L526, L536
#4379 core/libs/prx/libkernel/File/src/Stdio.cpp L457
#4391 core/libs/prx/libkernel/File/src/Open.cpp L205-206, L303-308
#4411 docs/dev/TechnicalDebt.md L596-698

@xXPBSXx

xXPBSXx commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Title test for the fcntl/ioctl change (this PR; the commits come from screamm's branch in #3757), Windows 10, MinGW GCC, RTX 4080 SUPER: this PR merged locally onto current main d06c06565 (clean merge, no conflicts). Full build with -j4 succeeded; the ctest subset matching fcntl|ioctl|file|open|descriptor|stdio|pipe|dup|kernel ran 44 tests: 44 passed (video_out_flip_reopen skipped).

PPSA24423 (0 absent imports; stopped on main ceb342b94 at sceKernelFcntl: file descriptors are not implemented, fd=6, see #3757): with this change the fcntl abort is gone. The run (150 s limit) then ends after about 70 s with a different abort:

what():  AGC graphics: vertex buffer aliases the render target
FATAL: abort() (SIGABRT)

So the title gets past fcntl and reaches rendering. I did not look at the picture and one run only.

AI-assisted (Claude Code).

@oneandonlydean oneandonlydean self-assigned this Oct 12, 2026
@oneandonlydean
oneandonlydean merged commit 5f3b94c into boykopovar:main Oct 12, 2026
12 checks passed
milescoviello pushed a commit to milescoviello/AnyPS5 that referenced this pull request Oct 12, 2026
ProsperoRadio (PPSA99001) stops at 33.7 s on Windows, with boykopovar#3649
applied so that it gets past poll, on the throw from boykopovar#3840:

    what():  fcntl: record locks are not implemented on Windows, command=12, fd=3

F_GETLK, F_SETLK, F_SETLKW and the F_OGETLK, F_OSETLK and F_OSETLKW
forms now follow FreeBSD 11.4's kern_fcntl and lf_advlockasync
(8c639945): an SEEK_CUR start is resolved against the descriptor offset
(EOVERFLOW past OFF_MAX), F_RDLCK needs read access and F_WRLCK write
access (EBADF), another type is EINVAL, and the range is checked against
the file size for SEEK_END (EINVAL for a negative start or an unknown
whence, EOVERFLOW past OFF_MAX). The guest is one process, and
lf_blocks never lets locks of one owner conflict, so whichever
descriptor or thread asks, F_SETLK and F_SETLKW succeed without waiting
and F_GETLK reports F_UNLCK, as on the console. Nothing is held on the
host: Windows byte-range locks are mandatory and would fail the guest's
own reads and writes through other descriptors. A lock from the end of
a directory throws.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
renzipego-cpu pushed a commit to renzipego-cpu/AnyPS5 that referenced this pull request Oct 12, 2026
ProsperoRadio (PPSA99001) stops at 33.7 s on Windows, with boykopovar#3649
applied so that it gets past poll, on the throw from boykopovar#3840:

    what():  fcntl: record locks are not implemented on Windows, command=12, fd=3

F_GETLK, F_SETLK, F_SETLKW and the F_OGETLK, F_OSETLK and F_OSETLKW
forms now follow FreeBSD 11.4's kern_fcntl and lf_advlockasync
(8c639945): an SEEK_CUR start is resolved against the descriptor offset
(EOVERFLOW past OFF_MAX), F_RDLCK needs read access and F_WRLCK write
access (EBADF), another type is EINVAL, and the range is checked against
the file size for SEEK_END (EINVAL for a negative start or an unknown
whence, EOVERFLOW past OFF_MAX). The guest is one process, and
lf_blocks never lets locks of one owner conflict, so whichever
descriptor or thread asks, F_SETLK and F_SETLKW succeed without waiting
and F_GETLK reports F_UNLCK, as on the console. Nothing is held on the
host: Windows byte-range locks are mandatory and would fail the guest's
own reads and writes through other descriptors. A lock from the end of
a directory throws.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
RINNECODER added a commit to RINNECODER/AnyPS5 that referenced this pull request Oct 13, 2026
#467)

* fix(libc): return the default quiet NaN from the strtod family

C17 7.22.1.3 and 7.29.4.1.1 interpret NAN(n-char-sequence) as a quiet
NaN. With MinGW-w64, std::strtod, strtof and strtold are mingw-w64's
gdtoa functions, which copy the hex digits into the significand
without the quiet bit, and for long double without the x87 integer
bit: strtod("nan(0x123)") returned the signaling NaN
0x7ff0000000000123 and strtold a pseudo-NaN. A payload whose only set
bit is the implicit or x87 integer bit gave infinity:
strtof("nan(0x800000)"), strtod("nan(0x10000000000000)") and
strtold("nan(0x8000000000000000)") returned +inf. glibc returns quiet
NaNs that keep the payload.

C17 7.12.11.2p2 makes nan("n-char-sequence") equivalent to
strtod("NAN(n-char-sequence)", NULL), and nan and nanf already return
the console's fixed quiet NaNs, ignoring the tag. ParseFloat, which
strtod, strtof, strtold, atof and the wide variants share, now returns
the same values, 0x7ff8000000000000 and 0x7fc00000, negated after a
minus sign, on Windows and Linux. strtold returns the x87 default
quiet NaN, since no console nanl was observed.

(cherry picked from commit aa4a9b4)

* fix(libc): clear exhausted strtok continuation state

(cherry picked from commit 3cb4347)

* test(libc): cover exhausted tokenizer state

(cherry picked from commit 0950b19)

* fix(libc): terminate truncated wcsxfrm results

(cherry picked from commit 22d03d4)

* refactor(libc): place the NaN helpers and test includes apart from neighbouring changes

(cherry picked from commit c55f377)

* fix(libkernel): fail lseek on a pipe with ESPIPE on Windows

(cherry picked from commit 61fbec8)

* fix(libc): append through a descriptor opened by fdopen in an append mode on Windows

(cherry picked from commit dbbd6d5)

* fix(libkernel): throw for unsupported directory entry names

(cherry picked from commit d31e283)

* fix(libc): keep the host standard handles when a guest replaces descriptor 0, 1 or 2 on Windows

(cherry picked from commit c226541)

* feat(libc): implement floor

(cherry picked from commit f8e2c64)

* fix(libc): keep host diagnostics on stderr when a guest redirects it on Linux

On Linux a guest stderr redirect also moves the host's: guest
descriptors are host descriptors, and the guest stderr stream wraps the
host stderr FILE. After dup2(fd, 2), or freopen(path, mode, stderr) with
full buffering, the libc terminate report, the host fprintf(stderr)
diagnostics and libstdc++'s terminate message go into the title's pipe
or file, and abort() discards what is still buffered. RetroArch
(PPSA99169) reopens stderr onto app0/trace.txt with full buffering, so
its stop on main left no message anywhere.

Before the first guest redirect of descriptor 2 (libkernel dup2, libc
freopen of a stream on descriptor 2), keep a close-on-exec duplicate of
the host descriptor 2 and point the host stderr at an unbuffered stream
on it. The guest stream keeps its own FILE and follows the redirect.
The libc terminate report writes to the kept descriptor with write()
instead of the stderr stream, and libkernel's sceKernelDebugOutText uses
the same kept descriptor.

(cherry picked from commit 438d410)

* feat(libkernel): implement sceCoredumpGetStopInfoCpu

PPSA34063 (NHL 27) calls it with a 16-byte buffer outside a coredump
handler and expects 0x81180003 there. The registered handler is never
invoked, so every call is outside one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d04f5d6)
(cherry picked from commit 02ed289)

* fix(libc): replace an open or read-only destination in rename on Windows

(cherry picked from commit 69b9d18)

* fix(libc): fail fseek and ftell on a pipe with ESPIPE on Windows

(cherry picked from commit 7aabb90)

* fix(libc): check for a pipe on every stream seek on Windows

The pipe check cached its first answer in the FileStream, so a stream
first used on a regular file stayed classified as a file after its
descriptor was replaced with a pipe (dup2 onto fileno(stream)), and a
later fseek reached the C runtime again and dropped buffered bytes.
fseeko and ftello now ask GetFileType on each call, as lseek on a pipe
does since boykopovar#4670, and FileStream no longer keeps the state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0dda76b)

* fix(libkernel): getsockname on an unbound socket returns the zero address on Windows

FreeBSD's in_getsockaddr and in6_getsockaddr succeed for a socket that has no
local address and return the wildcard address with port 0. Winsock fails
getsockname with WSAEINVAL until the socket is bound, which the guest saw as
EINVAL. Return the zero address of the socket's family for that error.

(cherry picked from commit 0438621)

* feat(libc): implement _Mtx_init_with_name

(cherry picked from commit ab4ccad)

* feat(libc): implement wcsftime

PS5MC (PPSA99420) imports wcsftime (XbVXpf5WF28, aerolib) as the last
absent libSceLibcInternal import in its registry order (index 481), so
its predicted stop is:

    FAIL: unresolved ELF import XbVXpf5WF28 from libSceLibcInternal.prx

The stop is predicted from the import order, not observed in a run.

wcsftime follows FreeBSD 11.4 lib/libc/locale/wcsftime.c (8c639945):
the wide format is converted with wcsrtombs, strftime fills a buffer of
maxsize * MB_CUR_MAX bytes limited to maxsize, and mbsrtowcs converts
the result back. A format unit that the single-byte C locale of
lib/libc/locale/none.c cannot encode returns 0 with EILSEQ, a maxsize
whose product with MB_CUR_MAX overflows returns 0 with EINVAL, and an
empty or overflowing result or a null buffer returns 0 with errno
unchanged; the ERANGE or EINVAL set by the Windows CRT's strftime is
discarded. A null format or tm throws, as FreeBSD dereferences it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 78ffdf0)

* fix(libc): hand out the unwind cursor table through a _nid_no_patch export

_Unwind_Context took its cursor table from LibcUnwind::CursorSlots, a C++
data symbol of libc. On Windows LibcUnwind::Frame holds an _Unwind_Context,
so libSceAgcDriver, which builds frames for its worker sampler, imported
_ZN10LibcUnwind11CursorSlotsE from libc.prx. The NID patcher renames that
export in the patched libc and leaves the import alone, so the patched
libSceAgcDriver, libSceAgc and libSceVideoOut failed to load with error 127
and patched_libraries failed. The table is now file-local and the context
reads it through UnwindCursorSlots_nid_no_patch, which keeps its name on
both sides.

(cherry picked from commit 3a9cc63)

* fix(libc): print NaN without a sign in the guest printf formatter

The guest formatter handed NaN to the host's snprintf, which prints
-nan for a negative NaN and +nan or " nan" with the + and space flags.
FreeBSD 11's vfprintf clears the sign of every NaN and prints nan (NAN
for the upper-case conversions), keeping the width and the - flag and
ignoring 0. The formatter now does the same for a, e, f and g and
their long double forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
(cherry picked from commit 13444c8)

* feat(libc): implement atanhf

Dusklight (PPSA99305) imports atanhf (cPGyc5FGjy0) from
libSceLibcInternal.prx; the libc.prx it bundles only has a zero-return
stub for it.

The implementation follows the atanhf of the libc.prx bundled with
PPSA28319 (0x438a0), read from its disassembly: FreeBSD msun's
e_atanhf.c calling msun's s_log1pf.c, both as of 59bc2f09 (stable/9,
the revision the sinhf port took expm1f and expf from). The log1pf
paths for the positive arguments atanhf passes are inlined, as the
exported log1pf is still the host's. For |x| > 1, the infinities and
NaN, Sony's error helper raises FE_INVALID and sets errno to EDOM
before returning (x - x) / (x - x); for |x| = 1 it sets errno to
ERANGE before returning x / 0. Other arguments leave errno alone.

Running that code from the title's libc.prx on a Windows host gives
the same bits, errno and floating-point exceptions as the built
libc.prx for all 2^32 arguments, and msun's own e_atanhf.c and
s_log1pf.c built with the host compiler give the same bits. With the
host log1pf instead of the msun one, 4.7% of the arguments in [0, 1)
differ. guest_math pins bits for every branch reachable from atanhf,
both signs, and the errno and exception behaviour.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 3fe04d4)

* feat(libc): implement _Cnd_timedwait

(cherry picked from commit 572d330)

* fix(libc): match FreeBSD acosf errno

acosf returned 0xffc00000 (errno 33) for 2.0 (0x40000000), where FreeBSD
lib/msun/src/math.h returns 0xffc00000 (errno 0).

It now returns the FreeBSD result for that case. A guest test covers it
and fails on main.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
(cherry picked from commit 64e244e)

* fix(libc): keep the acosf result and leave errno unchanged

The previous commit replaced the out-of-range result with the default
quiet NaN 0x7fc00000, but main and FreeBSD e_acosf.c return 0xffc00000.

acosf now calls the host and restores errno, so only errno differs from
main. The guest test pins the result bits for 2.0f and -2.0f and keeps
the in-range case acosf(0.5f).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 988d304)

* fix(libc): honor openlog identity and facility

(cherry picked from commit 653c544)

* docs(libc): record syslog tag fallback gap

(cherry picked from commit 7313cf5)

* test(libc): match syslog PID formatting

(cherry picked from commit 81f727c)

* fix(libkernel): harden DirectMemory pool bounds and rollback

(cherry picked from commit 4f87ac0)

* feat(libc): implement wcscat

(cherry picked from commit cd0e9dc)

* feat(libc): implement generic error category messages

(cherry picked from commit e3cef33)

* fix(libc): reject invalid setlocale categories

(cherry picked from commit 099fa80)
(cherry picked from commit 5eb99f8)

* fix(libc): validate guest setlocale categories

(cherry picked from commit 2ec20db)

* fix(libc): drop unused clocale include

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 1a0de9e)

* feat(libc): implement __powidf2

The H+8UBOwfScI import was a throwing stub (libcCyberUnknown08). It is
the NID of "__powidf2", the double-precision integer-power helper that
complements __powisf2.

(cherry picked from commit b4e2ee0)

* fix(libc): export __cxa_thread_atexit

(cherry picked from commit 90c64af)

* fix(libc): ignore the guest DSO of thread atexit on Windows in the shared bridge

MinGW's __cxa_thread_atexit asserts dso is null or its own __dso_handle, so every guest export that forwards a guest module pointer needs the null rule, not only LibcInternalExtCxaThreadAtexit. The test now passes a non-host DSO on Windows too.

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 9c383e3)

* test(libc): allow the __cxa_thread_atexit export to call the MinGW registration

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 1ab578a)

* fix(libkernel): place guest thread stacks inside the application area

On Linux, glibc maps the stack of every guest thread near the top of the
host address space (around 0x7fff00000000). On the console, application
stacks lie inside the application area below 0xFC00000000, and guest code
relies on that: ChakraCore's garbage collector indexes a 4 MiB table with
address >> 18, so it only covers addresses below 1 TiB and reports
Js::OutOfMemoryException for a stack above it.

Map each guest thread's stack with DoMapAnon, which allocates from the
guest arena, behind a PROT_NONE guard page, and pass it to pthread_attr_setstack.
The stack is freed after a join; the stack of a detached thread is freed once its
host thread is gone from /proc/self/task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7637cac)

* fix(libkernel): protect host-only guest stack pages

(cherry picked from commit 9b6e7f6)

* test(libkernel): check the host-only stack guard on Linux only

Windows guest threads keep their normal stacks, so there is no host-only guard page below the guest stack to query.

(cherry picked from commit 7458574)

* feat(libc): record traced import calls on Windows

TraceImport_nid_no_patch wraps a function of a library named in
APS5_TRACE_CALLS in a thunk that records the import, the thread and
the caller in a ring and counts the call, then jumps to the function.
One target keeps one thunk, so function pointers compare equal. The
crash and abort reports print the last 64 calls and the 32 most called
imports. Nothing calls TraceImport_nid_no_patch yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 3953a8d)

* fix(libc): choose the acosf NaN bits instead of the host libm

FreeBSD msun e_acosf.c returns (x-x)/(x-x) for |x| > 1 and NaN inputs: the
x86 default NaN 0xffc00000, or the quieted input NaN. std::acos left it to the
host libm, and guest_math failed on the runner's glibc 2.39.

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 0265f33)

* test(libc): cover wide transform output bounds

(cherry picked from commit 79d88af)

* feat(libkernel): implement fcntl record locks on Windows

ProsperoRadio (PPSA99001) stops at 33.7 s on Windows, with boykopovar#3649
applied so that it gets past poll, on the throw from boykopovar#3840:

    what():  fcntl: record locks are not implemented on Windows, command=12, fd=3

F_GETLK, F_SETLK, F_SETLKW and the F_OGETLK, F_OSETLK and F_OSETLKW
forms now follow FreeBSD 11.4's kern_fcntl and lf_advlockasync
(8c639945): an SEEK_CUR start is resolved against the descriptor offset
(EOVERFLOW past OFF_MAX), F_RDLCK needs read access and F_WRLCK write
access (EBADF), another type is EINVAL, and the range is checked against
the file size for SEEK_END (EINVAL for a negative start or an unknown
whence, EOVERFLOW past OFF_MAX). The guest is one process, and
lf_blocks never lets locks of one owner conflict, so whichever
descriptor or thread asks, F_SETLK and F_SETLKW succeed without waiting
and F_GETLK reports F_UNLCK, as on the console. Nothing is held on the
host: Windows byte-range locks are mandatory and would fail the guest's
own reads and writes through other descriptors. A lock from the end of
a directory throws.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 43ffba8)

* feat(libkernel): accept MADV_PROTECT advisory hint in madvise

(cherry picked from commit 59bd0c1)

* feat(libkernel): implement lutimes

(cherry picked from commit 8d08caa)

* fix(libkernel): validate CPU stop-info buffers and cover handler state

(cherry picked from commit c18daed)

* feat(libkernel): implement pthread_atfork

pthread_atfork (U9t5kJAWPnA, aerolib) appends the prepare, parent and
child handlers to a process-wide list under a lock and returns 0, or
ENOMEM when the entry cannot be allocated, as FreeBSD 11.4's
_pthread_atfork does (lib/libthr/thread/thr_fork.c:78-100, freebsd-src
8c639945a44588f6cfdddfb057bb5aad88735da0). Null handlers are accepted;
FreeBSD's fork skips them. The handlers never run: fork
(libkernel_sys) throws and vfork is absent, so no child process is
created. The list is kept, in registration order, for a later fork
(PthreadAtforkHandlers_nid_no_patch); recorded in TechnicalDebt.

XPSemu (PPSA97358) calls it from a constructor, before main
(ZiZc3/XPSemu 8eec07b4, util/rcu.c:488-495):

    static void __attribute__((__constructor__)) rcu_init(void)
    {
        smp_mb_global_init();
    #ifdef CONFIG_POSIX
        pthread_atfork(rcu_init_lock, rcu_init_unlock, rcu_init_child);
    #endif
        rcu_init_complete();
    }

so a throwing export would stop the title before main. Predicted stop
removed (registry index 255, not run):
FAIL: unresolved ELF import U9t5kJAWPnA from libkernel.prx

guest_pthread_atfork checks registration order, null handlers,
concurrent registration from four threads and that no handler runs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit f37bccf)
(cherry picked from commit e8fd542)

* feat(libkernel): add mlockall

mlockall (x7g7Ebeo8-U, aerolib) follows FreeBSD 11.4's sys_mlockall
(sys/vm/vm_mmap.c:1067-1131, freebsd-src
8c639945a44588f6cfdddfb057bb5aad88735da0) for the flag check: 0 or
bits outside MCL_CURRENT (1) and MCL_FUTURE (2) fail with EINVAL. The
title process is taken to pass priv_check(PRIV_VM_MLOCK), as mlock
already does.

Valid flags throw. MCL_FUTURE would need every later guest and host
mapping to be locked; nothing in the mapping paths does that, and
passing it to the host would make later host allocations fail above
the host RLIMIT_MEMLOCK. MCL_CURRENT would have to lock the whole
process, which on AnyPS5 also holds host stacks, heaps, modules and
driver mappings (sceKernelVirtualQuery reports them as guest memory),
with ENOMEM above a guest RLIMIT_MEMLOCK that is not known (getrlimit
throws for it). mlock pins host pages (VirtualLock, mlock) range by
range and keeps no record, so there is no state to extend. Recorded
in TechnicalDebt.

XPSemu (PPSA97358) calls it only with -overcommit mem-lock=on
(ZiZc3/XPSemu 8eec07b4, os-posix.c:330-355, reached from
system/vl.c:831-835):

    int flags = MCL_CURRENT | MCL_FUTURE;
    ...
    ret = mlockall(flags);
    if (ret < 0) {
        error_report("mlockall: %s", strerror(errno));
    }

and exits on failure, so that option ends in the throw; without it
the title is not affected. Predicted stop removed (registry index 201,
not run):
FAIL: unresolved ELF import x7g7Ebeo8-U from libkernel.prx

guest_mlock checks EINVAL for 0, 4, 5 and -1 and the throw for 1, 2
and 3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 87f4aa3)
(cherry picked from commit a7ea2bd)

* fix(libc): format strftime and asctime as FreeBSD 11 does

strftime handed most conversions to the host's strftime, which differs
from FreeBSD 11's for E and O modifiers on conversions glibc does not
pair with them (%Ea was printed literally), padding flags on %+ and %v,
years outside 1000 to 9999 (%Y printed 899 and -1 where FreeBSD prints
0899 and -001), and out-of-range fields (%I of hour 100, %u of weekday
8). On Windows the padding flags printed nothing. strftime now follows
FreeBSD 11 strftime.c with the C locale on every host, and asctime
follows FreeBSD 11 asctime.c, padding years to four digits and setting
longer years apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
(cherry picked from commit d0df62b)

* feat(libkernel): return ESRCH and EINVAL for unsupported cpuset affinity targets

Allow calling threads to query and set affinity using their own thread ID in addition to -1. Return ESRCH for non-matching thread or process IDs and EINVAL for invalid level/which arguments instead of throwing NotImplemented, matching FreeBSD cpuset semantics.

(cherry picked from commit 9efabff)

* fix(libkernel): check and keep the scheduling policy of a thread

On a PS5 (firmware 13.60) pthread_setschedparam and
scePthreadSetschedparam accept only SCHED_FIFO and SCHED_RR with a
priority of 256 to 767 or 896 to 959, return EINVAL otherwise, and
keep the policy for pthread_getschedparam. A created thread takes the
policy of explicit scheduling attributes, or its creator's when it
inherits. Here any policy and priority were accepted and SCHED_FIFO
was always reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5c6a9d2)

* feat(libkernel): implement execv and execve as throwing exports

XPSemu PPSA97358 imports both from libkernel.prx; with the imports ahead
of them covered, its predicted next stops (relink registry order, not an
observed run) are:

  FAIL: unresolved ELF import KaK52lYTBIs from libkernel.prx  (execv, 7 call sites)
  FAIL: unresolved ELF import kdguLiAheLI from libkernel.prx  (execve, 4 call sites)

Today the title stops earlier at the observed
  FAIL: unresolved ELF import aRo9AhFUXcM from libkernel.prx  (sigsetjmp, merged in boykopovar#3486)

As for execvp (boykopovar#2827), the process image cannot be replaced here, so both
throw with the execvp message pattern once FreeBSD 11's execve would start
loading the image (freebsd-src 8c639945, sys/kern/kern_exec.c
exec_copyin_args, do_execve, exec_check_permissions; lib/libc/gen/exec.c
execv passes environ). Before that they fail as FreeBSD does: EFAULT for a
null argument vector or path, ENOENT and ENOTDIR from the lookup as stat
reports it, EACCES for a path that is not a regular file. Paths of
PATH_MAX bytes or more and strings over ARG_MAX throw instead of guessing
the console's limits. The execv calls in XPSemu's sources run in a fork
child (net/tap.c), and its fork fails with ENOSYS (ZiZc3/XPSemu 8eec07b4,
ps5/compat/libc_missing.c, ps5/build-title.sh --defsym).

NIDs computed with tools/nid_names.py and confirmed in aerolib.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 57f0fa3)
(cherry picked from commit 4f1ae1c)

* fix(libkernel): limit execve to unsupported behavior

(cherry picked from commit 77016ca)

* fix(libkernel): keep the execv export and its debt line

The rename to execve dropped execv, which PPSA97358 imports.

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit cdf4b17)

* feat(libkernel): add clock_settime with FreeBSD argument checks

PS5MC (PPSA99420, Kodi 22) imports clock_settime (d7nUj1LOdDU) from
libkernel.prx, one reference, after sigwaitinfo in its import order.
Predicted stop once the imports ahead of it are covered (not a run):

  FAIL: unresolved ELF import d7nUj1LOdDU from libkernel.prx

The NID is computed with tools/nid_names.py and matches aerolib.

Follows FreeBSD 11.4 sys/kern/kern_time.c sys_clock_settime and
kern_clock_settime (8c639945a44588f6cfdddfb057bb5aad88735da0) for a
root caller: EFAULT for a null time (copyin), EINVAL for any clock but
CLOCK_REALTIME and for a negative tv_sec or a tv_nsec outside 0 to
999999999. A valid CLOCK_REALTIME request throws with the requested
time named, since the host clock must not be set and no guest clock
offset is kept.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Co-authored-by: David <david@example.com>
(cherry picked from commit ae9978a)

* feat(libkernel): report the host SoC and CPU temperature

PPSA99058 and PPSA99420 import sceKernelGetCpuTemperature and sceKernelGetSocSensorTemperature and stop at start-up on main. Read the host CPU/package, board and NVMe sensors (hwmon/thermal on Linux, the ACPI thermal zones and the storage temperature property on Windows) and report an unsupported sensor with EINVAL.

(cherry picked from commit 0f73f5f)

* feat(libkernel): answer the coredump user-file attach calls

sceCoredumpAttachUserFile (5nc2gdLNsok) and
sceCoredumpAttachMemoryRegionAsUserFile (MEJ7tc7ThwM) get the
signatures of Orbital's db_types.json (cc408367) and SharpProspero's
KernelSystem.cs (5dfdb4b0): (uint32_t userValue, const char* path) and
(uint32_t userValue, const void* mem, size_t size, const char* name).

The handler is never invoked here, so both return
SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER (0x81180003, shadPS4
coredump_error.h e0a46675) and attach nothing; a null path or name and
a null, empty or wrapping region throw because the order of the
console's checks is unknown.

Carried from @screamm with author attribution preserved.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

(cherry picked from commit 204355c)

* feat(libkernel_sys): implement mkdirat, unlinkat, renameat, fchmodat and chroot

Hand-merged from upstream: ec0d61e 55ddffe 2a8e745 9ceb6d1 dc0d316
41b0ca5 e3c1d01 603d94a 7e5bec0 f8a269e. The fork's libkernel_sys
Export.cpp and its test were an older shape of the file, so the upstream
versions were taken whole, together with the RenameGuestPath export in libc
Rename.cpp/General.hpp.

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

* fix(libc): call __dynamic_cast and __cxa_init_primary_exception with the guest ABI

Every other export of the C++ runtime is declared APS5_VABI, but these two
were not. On Windows a title calls them with the System V convention while
they read the Microsoft one, so __dynamic_cast took its fourth argument, the
src2dst hint, as the source pointer: an ordinary downcast (hint 0) returned
null, and the negative hints made it read through an invalid pointer.

guest_dynamic_cast calls both through APS5_VABI declarations, as a title
does, and checks a single-inheritance downcast, a failed cross cast, a cast
of a base object and the header __cxa_init_primary_exception fills in.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f4be036)

* test(libc): register guest_dynamic_cast beside guest_array_callbacks

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 7154138)

* fix(libc): keep the caller errno when localtime cannot represent a time

Adapted from upstream b1cf014 (POSIX and Windows localtime failure paths;
the Windows year-range handling and its test are not ported).

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

* feat(libc): implement the float rounding and classification exports

PPSA99611 imports ceilf, floorf, truncf, isnan and isinf from libc.prx and stops at start-up on main. Implement them next to the existing double forms: the rounding results delegate to the standard library like the other float exports, and isnan/isinf reuse the __isnan/__isinf helpers isnanf already uses.

(cherry picked from commit 00f0589)

* fix(libc): build the guest category test and the dynamic_cast registration on macOS

Cast away const on the category vtable entries (Apple clang rejects the
reinterpret_cast), register guest_dynamic_cast once, use duration_cast for the
_Cnd_timedwait deadline (libc++ system_clock is microsecond based), include
unistd.h for the libkernel_sys at-calls, and stub GuestThreadStackHostRangeOverlaps
in the direct memory test harness.

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

* fix(libs): find the executable directory on macOS

libSceFont and libSceNpEntitlementAccess located the executable
through /proc/self/exe, which macOS does not have, so finding the
system fonts and the entitlements file threw there. Use
_NSGetExecutablePath and resolve the path with
std::filesystem::canonical on macOS. Linux and Windows are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f30f3e8)

* fix(libSceFont): include vector for the macOS executable lookup

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

---------

Co-authored-by: Yunus Shaikh <75296019+yunusshaikh7@users.noreply.github.com>
Co-authored-by: QRMOE <mohandmahdi44@gmail.com>
Co-authored-by: David <David.Samir.WD@gmail.com>
Co-authored-by: ArjenS-LA <arjensingh@outlook.com>
Co-authored-by: BIGFERRY999 <bigferry24@gmail.com>
Co-authored-by: marshmll <renandasilvaoliveiraandrade@gmail.com>
Co-authored-by: poonasor <rickypoon89@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: milescoviello <kitslayer29@gmail.com>
Co-authored-by: Hussein Kadhim <husseinkad.dev@gmail.com>
Co-authored-by: Feuris <sebastian6@live.com.ar>
Co-authored-by: David <david@example.com>
Co-authored-by: Dean Galvin <dean.galvin@live.com>
Co-authored-by: Rick <99503318+0xRickx@users.noreply.github.com>
Co-authored-by: Alex <270190108+CouchRiv@users.noreply.github.com>
Co-authored-by: MarcZX100 <marczxrr@gmail.com>
Co-authored-by: Yaash Singhh <YASHVSINGH27+git@GMAIL.COM>
Co-authored-by: Evgeniy Kazarin <evgkaz0@gmail.com>
Co-authored-by: Adrià Franch <adriadam10@gmail.com>
Co-authored-by: Akul Tyagi <69496404+AkulTyagi11@users.noreply.github.com>
Co-authored-by: Cesar Rodriguez <11452991+Tamalero@users.noreply.github.com>
Co-authored-by: codingreapermaps <233954859+codingreapermaps@users.noreply.github.com>
Co-authored-by: screamm <davidrydgren@gmail.com>
Co-authored-by: jagrat Singh <jagratsingh9899@gmail.com>
Co-authored-by: lorkydey <lorky.contact@gmail.com>
Co-authored-by: hugodearaujo <hugoaraujocamara@gmail.com>
Co-authored-by: nabilfp <nabillgithub@gmail.com>
Co-authored-by: Nixitov <Nixitov@users.noreply.github.com>
Co-authored-by: Ahir Barman Maji <191682168+ahir061@users.noreply.github.com>
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.

libkernel: fcntl and ioctl reject every non-socket descriptor (Yamagi Quake II, XPSemu, VLC, RetroArch); branch ready

5 participants