fix(libc): name the extended errno values in strerror as titles' libc.prx does

The libc.prx that PS5 titles ship (PPSA05916, PPSA07923, PPSA13934) gives
strerror, strerror_r and strerror_s texts for eight numbers above the
FreeBSD range: 107 "State not recoverable", 108 "Owner dead", 152 "File
positioning error", 1040 "No message available", 1050 "No stream
resources", 1051 "Not a stream", 1062 "Other" and 1074 "Stream timeout".
AnyPS5 answered "error_code=0x%08x" for them. Every other number from
-2000 to 2000, INT_MAX and INT_MIN already matched.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
JJenkxandClaude Opus 5.5 authored and Adrià committed 2026-10-28 19:15:43 +01:00
1 parent f21ef7c99c
commit 6caa14c3dc
3 files changed
+32 -2

No files matched your search

+16
View File
@@ -46,9 +46,25 @@ constexpr const char* messages[] = {
static_assert(sizeof(messages) / sizeof(messages[0]) == 97);
bool NamedByStrerror(int error) { return error > 0 && error < 95; }
constexpr struct {
int error;
const char* text;
} extendedMessages[] = {
{107, "State not recoverable"}, {108, "Owner dead"}, {152, "File positioning error"},
{1040, "No message available"}, {1050, "No stream resources"}, {1051, "Not a stream"},
{1062, "Other"}, {1074, "Stream timeout"},
};
const char* ExtendedMessage(int error) {
for (const auto& entry : extendedMessages)
if (entry.error == error) return entry.text;
return nullptr;
}
void Describe(int error, char (&text)[128]) {
if (error == 0) std::snprintf(text, sizeof(text), "No error");
else if (NamedByStrerror(error)) std::snprintf(text, sizeof(text), "%s", messages[error]);
else if (const char* extended = ExtendedMessage(error)) std::snprintf(text, sizeof(text), "%s", extended);
else std::snprintf(text, sizeof(text), "error_code=0x%08x", static_cast<unsigned>(error));
}
}
+15 -1
View File
@@ -68,7 +68,21 @@ int main() {
Require(std::strcmp(strerror_nid_postfix(error), expected) == 0);
Require(*__error_nid_postfix() == 13);
}
for (const int error : {97, 100, 1000, 9999, INT_MAX, -1, INT_MIN}) {
const struct {
int error;
const char* text;
} extended[] = {
{107, "State not recoverable"}, {108, "Owner dead"}, {152, "File positioning error"},
{1040, "No message available"}, {1050, "No stream resources"}, {1051, "Not a stream"},
{1062, "Other"}, {1074, "Stream timeout"},
};
for (const auto& entry : extended) {
Require(strerror_r_nid_postfix(entry.error, buffer, sizeof(buffer)) == 0);
Require(std::strcmp(buffer, entry.text) == 0);
Require(std::strcmp(strerror_nid_postfix(entry.error), entry.text) == 0);
Require(*__error_nid_postfix() == 13);
}
for (const int error : {97, 100, 106, 109, 151, 153, 1000, 1039, 1041, 1075, 9999, INT_MAX, -1, INT_MIN}) {
char coded[32];
std::snprintf(coded, sizeof(coded), "error_code=0x%08x", static_cast<unsigned>(error));
Require(strerror_r_nid_postfix(error, buffer, sizeof(buffer)) == 0);
+1 -1
View File
@@ -69,7 +69,7 @@
- [wcsncat_s and wcscat_s](../../../core/libs/prx/libc/src/Strings.cpp) (libc) refuse a copy as overlapping only when the written range `[dest + len1, dest + len1 + len2)` meets the read range `[src, src + len2)`. The rule comes from a static reading of the console's `wcsncat_s` in `libc.prx` (not run on a console); `wcscat_s` gets it through `wcsncat_s`, and its own console code was not read. The narrow `strncat_s` keeps a wider rule, `[dest, dest + used + length + 1)` against `[src, src + length + 1)`, whose console code was not read either, so it refuses some copies whose wide equivalent proceeds.
- [snwprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: the handler installed with `set_constraint_handler_s` is not invoked, and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf`
- [swprintf_s and vswprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follow C11 K.3.9.1.4 with `RSIZE_MAX` assumed to be `(SIZE_MAX >> 1) / sizeof(char16_t)`: the complete output, including the terminating null, must fit in the buffer, otherwise it is a runtime-constraint violation that sets `buffer[0]` to zero and returns a negative value, and the buffer is not partially written. Argument and encoding checks match `snwprintf_s`
- [strerror, strerror_r and perror](../../../core/libs/prx/libc/src/Errors.cpp) (libc) - match the system `libSceLibcInternal` of PS5 firmware 13.60 and the `libc.prx` that CUSA04865 and CUSA12435 ship, loaded on a PS4 Pro (firmware 10.71): 0 gives "No error", 1 to 94 the FreeBSD texts and any other number "error_code=0x%08x"; `perror` prints the same texts; errno is left alone. `strerror_r` follows the `libc.prx` that PPSA31872 ships, read statically: a buffer under 23 bytes returns `ERANGE` (34) without writing, otherwise it `strncpy`s length - 1 bytes, terminates the last byte and returns 0, so a long text is cut. Firmware 13.60 agreed at 0, 1, 5, 16 and 256 bytes; 17 to 255 bytes and the cut were not run on a console. A null buffer of 23 bytes or more throws, as the console writes through it.
- [strerror, strerror_r and perror](../../../core/libs/prx/libc/src/Errors.cpp) (libc) - follow the `libc.prx` that PPSA05916, PPSA07923 and PPSA13934 ship, run natively over -2000 to 2000, `INT_MAX` and `INT_MIN`: 0 gives "No error", 1 to 94 the FreeBSD texts, 107, 108, 152, 1040, 1050, 1051, 1062 and 1074 their own texts, and any other number "error_code=0x%08x"; `perror` prints the same texts and errno is left alone. Firmware 13.60's `libSceLibcInternal` and the PS4 `libc.prx` of CUSA04865 and CUSA12435 agreed at the numbers measured there (0 to 96, 97, 100, 1000, -1, `INT_MAX`, `INT_MIN`); the eight extra names were not run on a console. `strerror_r` follows PPSA31872's `libc.prx`, read statically: under 23 bytes it returns `ERANGE` (34) without writing, otherwise it `strncpy`s length - 1 bytes, terminates the last byte and returns 0. Firmware 13.60 agreed at 0, 1, 5, 16 and 256 bytes. A null buffer of 23 bytes or more throws, as the console writes through it.
- [strerror_s and strerrorlen_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc) - a null destination, a capacity of 0 or one above `RSIZE_MAX` (`SIZE_MAX >> 1` here) calls the registered guest-ABI constraint handler with `EINVAL` and leaves the destination untouched, following [C11 K.3.7.4.2](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf); what the console does then, its `RSIZE_MAX` and its default handler are not measured. Otherwise they match the system `libSceLibcInternal` of PS5 firmware 13.60: the `strerror` texts, a short buffer truncated with "..." when it holds more than 3 bytes and a return of 0 either way, and `errno` left alone
- [getw and putw](../../../core/libs/prx/libc/src/BinaryWordIo.cpp) (libc) follow FreeBSD 11.4 [getw.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/getw.c) and [putw.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/putw.c): one 32-bit little-endian word, `getw` returns -1 for EOF or an error, and `putw` returns 0 on success or -1 on error while holding the stream lock. They use the existing host-backed streams, so buffering and error timing follow the host CRT. Null streams throw and closed streams return -1 with `EBADF`, as existing byte I/O does. Console behavior is unverified. A payload process on a PS5 (firmware 13.60) gave the same results: two words written little-endian, -1 with end of file after them and for a 2-byte file, and -1 with the error flag for `putw` on a read-only stream
- [vsnwprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - `va_list` counterpart of `snwprintf_s` (C11 K.3.9.1.3): truncates to the buffer and returns the full length, with the same constraints and argument checks