On Windows the C runtime appends by seeking to the end and then writing,
so concurrent appends through different descriptors overwrite each other
and lose data. Write and writev on an O_APPEND descriptor now go through
WriteFile at the end-of-file offset, which Windows appends atomically,
and writev gathers its buffers into one append as FreeBSD writes the
whole vector at the end.
flock, fstatfs, ftruncate, fsync, fdatasync, getdirentries, fchmod, futimes and mmap on an archived descriptor acted on its null device and succeeded or failed with an unrelated errno. They now throw NotImplemented, each covered by the kernel archive test; mmap is noexcept, so its refusal is checked through the terminate handler.
sceKernelOpen and open give an archived file a read-only null-device descriptor. Reads, positioned, vectored and AIO reads, seeks, fstat and FIONREAD are served from the archive; dup, dup2 and F_DUPFD share the position. sceKernelStat and sceKernelCheckReachability see archived paths.
Writing opens fail with EROFS, O_CREAT|O_EXCL with EEXIST and O_DIRECTORY on a file with ENOTDIR. Opening an archived directory or one merged with the disk throws until getdents can list it.
A read-only open, stat or reachability check of a missing path listed its
directory twice: once for a case-insensitive match of the name and again for
a case-insensitive match of the relinked <name>.guest.prx (#5434). Both
names are now matched in the same listing. Precedence and the ambiguous-name
errors are unchanged.
lstat of a Windows symbolic link threw, although symlink creates links
with relative targets on Windows and readlink reads them. Report such a
link as S_IFLNK with its own identity and times and the length of its
target as st_size, through the reparse reader readlink already used,
now shared from libkernel. Links with absolute targets, junctions and
other reparse points still throw, as for readlink.
Guest descriptors are host C runtime descriptors. Closing descriptor 0, 1
or 2 closed the host process's standard stream, and the runtime then gave
that number to the next file opened anywhere in the process, including
files libkernel opens itself, such as the APR reader's. The title's next
close of the same number closed that file mid-read.
close and sceKernelClose now fail with EBADF for descriptors 0 to 2 that
were open when libkernel loaded, and leave them open. A standard
descriptor that was not open at load can still be opened and closed by
the guest.
PPSA21703 calls sceKernelClose(0) repeatedly while loading asset
bundles, which made APR reads return 0 or a part of the requested bytes
and raised UCRT invalid parameter reports from close.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fstat on a guest socket descriptor passed the guest number to the host,
which has no such descriptor, and threw ("FillFileStat: fstat failed for
fd 268435460"); sceKernelFstat returned EBADF. FreeBSD fills the stat
buffer for a socket through soo_stat (sys/kern/sys_socket.c):
bzero((caddr_t)ub, sizeof (*ub));
ub->st_mode = S_IFSOCK;
...
if ((sb->sb_state & SBS_CANTRCVMORE) == 0 || sbavail(sb))
ub->st_mode |= S_IRUSR | S_IRGRP | S_IROTH;
ub->st_size = sbavail(sb) - sb->sb_ctl;
...
if ((sb->sb_state & SBS_CANTSENDMORE) == 0)
ub->st_mode |= S_IWUSR | S_IWGRP | S_IWOTH;
and pru_sense_null (sys/kern/uipc_socket.c) adds
st_blksize = so->so_snd.sb_hiwat.
fstat and sceKernelFstat now answer a guest socket from the socket
table: S_IFSOCK, the read bits until the receive side is shut down and
drained, the write bits until the send side is shut down, st_size from
the bytes queued and st_blksize from the send buffer size, other fields
zero. shutdown records which side it closed. A closed socket descriptor
returns EBADF. The differences from the console (peer shutdown, the
host's FIONREAD and SO_SNDBUF, socketpair identity) are listed in
TechnicalDebt.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The per-descriptor lock exists only in the Windows transfer path, so ShareDescriptor calls SharePositionedTransfer under _WIN32 like GuestFileLockShare. Linux failed to link libkernel with an undefined reference to it.
Crimson Desert (PPSA25286) calls sceKernelStat with a null buffer at
start-up, and main throws "sceKernelStat: sb is null", which ends the
title in its first second.
FreeBSD 11.4 looks the path up first and only then copies the result
out, so a null buffer gets EFAULT for a path that exists and the lookup
error for one that does not (sys/kern/vfs_syscalls.c, sys_stat):
error = kern_statat(td, 0, AT_FDCWD, uap->path, UIO_USERSPACE,
&sb, NULL);
if (error == 0)
error = copyout(&sb, uap->ub, sizeof (sb));
sceKernelStat now does the same: the lookup and its errors are as
before, and a null buffer returns SCE_KERNEL_ERROR_EFAULT where the
status would be copied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS5MC (PPSA99420, Kodi 22) imports chflags (ixrw0h2tWuI) from
libkernel.prx, one reference. Predicted stop once the imports ahead of
it (pthread_getthreadid_np, sendfile, wcstok, pause, setrlimit and the
termios calls) are covered (not a run):
FAIL: unresolved ELF import ixrw0h2tWuI from libkernel.prx
The NID is computed with tools/nid_names.py and matches aerolib.
Follows FreeBSD 11.4 (8c639945a44588f6cfdddfb057bb5aad88735da0)
sys/kern/vfs_syscalls.c kern_chflagsat and setfflags, and
sys/ufs/ufs/ufs_vnops.c ufs_setattr, for a root caller: the lookup
through the guest resolver gives EFAULT, ENOENT and ENOTDIR (also for a
file reached through a trailing slash or used as a directory), a bit
outside the 17 UFS flags of sys/sys/stat.h (VNOVAL included) gives
EOPNOTSUPP, and SF_SNAPSHOT gives EPERM, since the snapshot flag cannot
be toggled. No host keeps BSD file flags and stat reports st_flags 0,
so flags 0 succeed without touching the file and any other flag throws
with the flags named. UF_READONLY and the other Windows-attribute flags
are not mapped to FILE_ATTRIBUTE_READONLY and its siblings: UFS stores
them without enforcing them, and on Windows the read-only attribute is
already the owner write bit of chmod.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 16e5b7ccd04e6467a5a282b6cfc9271304f42244)
The POSIX pread export threw on a null buffer, a negative offset and
any host read failure, so a refused read terminated the process.
Return -1 and set the guest errno the same way pwrite and
sceKernelPread already do.
ioctl() on a file, directory, pipe or character device threw "request
0x20006601 ... is not implemented" for FIOCLEX, and the same for
FIONCLEX, although the close-on-exec flag they change is already kept
for these descriptors and reported by fcntl(F_GETFD).
FreeBSD 11.4 handles both requests in kern_ioctl() for every descriptor,
before the request reaches the file's own ioctl, by changing the flag
F_GETFD reads. sys/kern/sys_generic.c, kern_ioctl():
case FIONCLEX:
fdp->fd_ofiles[fd].fde_flags &= ~UF_EXCLOSE;
goto out;
case FIOCLEX:
fdp->fd_ofiles[fd].fde_flags |= UF_EXCLOSE;
goto out;
sys/kern/kern_descrip.c, kern_fcntl():
case F_GETFD:
...
td->td_retval[0] =
(fde->fde_flags & UF_EXCLOSE) ? FD_CLOEXEC : 0;
Both requests now set or clear that flag and return 0. libuv before 1.44
sets close-on-exec this way on FreeBSD (uv__cloexec_ioctl).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libkernel numbered guest sockets from GuestSockets::FirstDescriptor
(0x10000000), so a title that puts a socket in a 1024-bit fd_set writes
fd / 8 bytes past it. The Epic Online Services SDK does this on its
RtcNetworkThread and three titles stop there (#1794):
Palworld PPSA23126 (queeeef, main a11c8f7f7):
FATAL: unhandled exception 0xc0000005 on thread 37164 'RtcNetworkThread'
rip 0x00007ffcdb1ea2be eossdk-ps5-shipping.prx.guest.prx+0xdea2be
rcx 0000000010000005 rsi 0000000000400000 r12 0000000010000005
The Mound PPSA26223 (xXPBSXx, main 0a8b1d32b):
FATAL: unhandled exception 0xc0000005 on thread 'RtcNetworkThread',
rip eossdk-ps5-shipping.prx.guest.prx+0xefc6f4, rcx = 0x10000005,
rsi = 0x400000
Dying Light 2 PPSA02262 (tv7):
FATAL: unhandled exception 0xc0000005 on thread 'RtcNetworkThread'
rcx 0000000010000005 ... rsi 0000000000400000 ... r12 0000000010000005
On FreeBSD 11.4 (8c639945) FD_SETSIZE is 1024 (sys/sys/select.h:58-59)
and sockets take the lowest free entry of the one process descriptor
table that files use (kern_descrip.c:1643-1660 fdalloc,
uipc_syscalls.c kern_socket through falloc).
Every socket number is now a real host descriptor: a zero-access NUL
handle registered with _open_osfhandle on Windows (as directory
descriptors already are) and an F_DUPFD_CLOEXEC duplicate of the host
socket on Linux. The socket table maps that number to the socket, so
"is this a socket" is a table lookup instead of a range check at the 31
former FirstDescriptor comparisons. close releases both the socket entry
and the number, dup and dup2 duplicate the number through the host table
and keep the socket shared, dup2 of a file onto a socket number drops the
socket entry, and sceKernelClose now closes sockets too. sceNetSocket and
sceNetAccept take their ids from the same table instead of a counter from
32, so libkernel and libSceNet numbers never collide.
select polls guest sockets alongside libSceNet sockets, the random device,
stdio and (on Linux) host descriptors; poll throws only for an open
descriptor that is not a guest socket and reports POLLNVAL otherwise.
File calls that used to fail with EBADF on the socket range return
FreeBSD's answer for a socket: lseek ESPIPE (kern_lseek), ftruncate and
getdirentries EINVAL (invfo_truncate, getvnode), flock EOPNOTSUPP;
fstat on a socket throws. pread on a closed descriptor returns EBADF.
title failing on host descriptor numbers; these three titles are that
case, and this change keeps host numbering for files and only moves
sockets into it.
Tests: guest_socket_descriptors (new: socket below 1024, select on a
socket and a file together, close frees the number for a file, dup of a
socket, sceKernelClose), guest_select (socket and pipe together, Linux),
guest_poll, guest_dup, guest_socket_pair, guest_fstatfs,
guest_posix_filesystem and guest_sce_net updated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 20378375b31eb5db922db6e55f45e44a4dae6445)
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.
Cloudberry Kingdom (PPSA23827, #3177) calls kqueue right after start-up
and stopped on the throwing declarations merged with #3337; Palworld's
Discord module (PPSA23126, #2648) imports kevent.
kqueue reserves a host null-device descriptor in the file descriptor
space and registers it as an event queue that reports each ready event
once per call and moves level-triggered ones behind the others, as
kqueue_scan does. close and sceKernelClose close the queue and drop the
descriptor's events from every queue, dup2 drops them for the target,
and dup/dup2 of a kqueue throw, so a reused descriptor number is never
read as a stale kqueue.
kevent follows FreeBSD 11 kern_event.c (release/11.0.1 34bd3475):
kqueue_kevent for the changelist (filter 0 skipped, EV_SYSFLAGS
cleared, EV_ERROR receipts with the errno in data, EV_RECEIPT, the
early return when nevents is exhausted), kqueue_register for EV_ADD,
EV_DELETE, EV_ENABLE, EBADF, ENOENT and EINVAL, kqueue_scan for the
timeout (EINVAL for a bad timespec, zero polls, above INT32_MAX waits
forever) and filt_timerattach, filt_usertouch for the timer and user
filters. Read and write events reuse the guest socket poll of
sceKernelAddReadEvent/WriteEvent.
Throws: other filters, EV_DISABLE, EV_DISPATCH, EV_FORCEONESHOT,
NOTE_LOWAT, EV_CLEAR on a socket, read/write events on non-socket
descriptors or another kqueue, changes to an existing timer (11.0 and
11.4 differ), a NOTE_NSECONDS fraction above one second and a negative
nevents. Recorded in TechnicalDebt.
Test guest_kqueue covers creation and close, timers, a user event
triggered from another thread, read and write events on a socket pair,
EV_ERROR receipts, the timeout and the error cases.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>