sceKernelRmdir decided whether the last component was a symbolic link with
std::filesystem::is_symlink, which follows the link with WinLibs libstdc++ on
Windows. A dangling or self-referencing file link then looked missing on
Windows 10 (ENOENT instead of ENOTDIR), and a directory link to an empty
directory looked like that directory, so rmdir removed the link and
returned 0. The last component is now classified with FillLinkStat, the
same lstat path lstat_nid_postfix uses, matching FreeBSD 11 kern_rmdirat,
which returns ENOTDIR for any non-directory vnode found without following
the last link.
Fixes#12079
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This reverts commit 780b33aea3, restoring the 13824 MiB direct memory pool.
With the pool at 12288 MiB, Astro Bot (PPSA21564) can no longer load its
level: DirectMemoryAllocator asserts "Can't allocate direct memory" for a
64 MB VideoPlayerTexture with 12228 of 12288 MB in use, then the process
faults about 26 s in (2 of 2 runs, RTX 4090, Linux, main cccdaa2734).
The same tree with only this merge reverted runs the full 150 s with no
direct memory assert (2 of 2 runs).
scePthreadMutexattrSetkind called NotImplemented_nid_no_patch for a kind
outside 1 to 4. Firmware 13.42's libkernel, run in an emulator, makes it
call pthread_mutexattr_setkind_np, which stores any kind as a signed
16-bit value and returns 0, and turns a null attribute's -1 with errno 22
into 0x8001FFFF. It now forwards to pthread_mutexattr_setkind_np and
converts the result with sceKernelError, so a null attribute or object
still returns 0x8001FFFF and now also sets errno 22.
An out-of-range kind is refused by scePthreadMutexInit with 0x80020016,
leaving the mutex untouched, as measured for the same module.
scePthreadMutexattrGetkind is unchanged; kind 0 now reaches it and
returns 0.
sceKernelTruncate and truncate checked for a symbolic link loop only
after the native truncate failed, but on Windows std::filesystem::status
already reports a self-referencing link as missing, so the call returned
ENOENT before reaching that check. The loop check now runs before the
not-found result too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SuspendThread fails with STATUS_THREAD_IS_TERMINATING for a thread that
has begun to exit while its handle is not signaled yet. TryDeliver took
only the signaled handle as an exit, so the raise deliverer, which keeps
sweeping a target in host code until delivery or exit, threw "cannot
suspend the target thread" and aborted the process when that target
exited in between. The suspension now reads the NTSTATUS from
NtSuspendThread and treats a terminating target as exited; other
failures still throw.
guest_raise_exiting_thread raises at 32 threads per round that block in
a host wait and exit after it, for 100 rounds.
The test throws std::bad_alloc from a replaced operator new, so on MinGW it needs the same libc link, unwind section rename and DLL path as the other throwing guest tests (the Windows link failed on __gxx_personality_v0).
JsonObject held std::pair<std::string, JsonValue> while JsonValue was
still incomplete. Clang with libstdc++ instantiates the pair and the
vector destructor at the end of JsonValue and stops with "incomplete
type used in type trait expression". A named JsonMember struct defined
after JsonValue keeps the vector element a plain class.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Unbacked pages of a PRT range are anonymous zero pages, so a failed allocation puts those back with _zeroPrt instead of leaving the range reserved and unreadable.
_ammAllocatePrt took pool pages one at a time and mapped and recorded them run by run, so a failure on run k lost the pool pages of runs k to n. It now maps the unbacked part through AmmPool::MapScattered and returns the range to the reservation if that throws, and the unused _takePoolPages is gone.
Fail each allocation of a map from a fragmented pool, a map whose host call fails after each run, and a page insertion, and check that the pool and the page records are unchanged after every failure and that a map of one page more than the pool holds is still refused.
Move the pool bookkeeping into AmmPool.hpp. A map from a fragmented pool now plans its pages without changing the pool, stages the page and reference records in their own maps, maps the runs, and only then splits the pool and moves the records in with no allocation, so a failed map leaves the pool and the records as they were. Adding a page inserts its reference record first and undoes it if the page record throws.
_ammMap took one contiguous run of the direct memory given to the mapper and
threw (an uncaught runtime_error -> abort) when the pool was fragmented:
PPSA03671 aborted ~4 min into a slot-4 load on an 11 MB map ("AMM: no free run
of 11010048 bytes"). The PS5 maps the mapper's pages through page tables, so a
fragmented pool still serves the map: when no run fits, the map now takes
pages from several runs (first fit) and maps each contiguous group. Run hww8:
the 11 MB map and several 10-33 MB maps went through, no abort.