fix(libc): give strerror_r and perror the strerror texts as measured on a PS5

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
orezraeyandClaude Opus 5.5 committed 2026-10-25 17:59:49 -03:00
1 parent cceb6c3c41
commit b4530f1cce
6 files changed
+63 -46

No files matched your search

@@ -0,0 +1,8 @@
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_ERRORMESSAGES_HPP
#define CORE_LIBS_PRX_LIBC_INCLUDE_ERRORMESSAGES_HPP
#include <cstddef>
extern "C" void FreeBsdErrorMessage_nid_no_patch(int error, char* buffer, std::size_t length);
#endif
+21 -15
View File
@@ -1,5 +1,6 @@
#include "prx/libc/include/WindowsFormatting.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/ErrorMessages.hpp"
#include "prx/libc/include/FileStream.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdio>
@@ -45,32 +46,37 @@ constexpr const char* messages[] = {
static_assert(sizeof(messages) / sizeof(messages[0]) == 97);
bool Known(int error) { return error >= 0 && error < 97; }
bool NamedByStrerror(int error) { return error > 0 && error < 95; }
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 std::snprintf(text, sizeof(text), "error_code=0x%08x", static_cast<unsigned>(error));
}
}
extern "C" {
void FreeBsdErrorMessage_nid_no_patch(int error, char* buffer, std::size_t length) {
const int saved = *__error_nid_postfix();
if (Known(error)) std::snprintf(buffer, length, "%s", messages[error]);
else std::snprintf(buffer, length, "Unknown error: %d", error);
*__error_nid_postfix() = saved;
}
char* APS5_VABI strerror_nid_postfix(int error) {
thread_local char buffer[128];
if (error == 0) std::snprintf(buffer, sizeof(buffer), "No error");
else if (NamedByStrerror(error)) std::snprintf(buffer, sizeof(buffer), "%s", messages[error]);
else std::snprintf(buffer, sizeof(buffer), "error_code=0x%08x", static_cast<unsigned>(error));
Describe(error, buffer);
return buffer;
}
int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t length) {
const int saved = *__error_nid_postfix();
char temporary[128];
if (Known(error)) std::snprintf(temporary, sizeof(temporary), "%s", messages[error]);
else std::snprintf(temporary, sizeof(temporary), "Unknown error: %d", error);
const auto required = std::strlen(temporary) + 1;
int result = Known(error) ? 0 : 22;
if (Known(error) && (!buffer || length < required)) result = 34;
if (buffer && length) {
const auto count = required <= length ? required - 1 : length - 1;
std::memcpy(buffer, temporary, count);
buffer[count] = '\0';
}
char text[128];
Describe(error, text);
*__error_nid_postfix() = saved;
return result;
const auto required = std::strlen(text) + 1;
if (!buffer || length < required) return 34;
std::memcpy(buffer, text, required);
return 0;
}
void APS5_VABI perror_nid_postfix(const char* prefix) {
+2 -3
View File
@@ -1,5 +1,6 @@
#include "prx/libc/include/WindowsFormatting.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/ErrorMessages.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/SonyCrt.hpp"
#include <cerrno>
@@ -10,7 +11,6 @@
#include <cstring>
#include <limits>
#include <mutex>
#include <stdexcept>
extern "C" void APS5_VABI ignore_handler_s_nid_postfix(const char* message, void* pointer, int error) {
SonyIgnoreHandlerS(message, pointer, error);
@@ -41,8 +41,7 @@ extern "C" int APS5_VABI wcscpy_s_nid_postfix(std::uint16_t* dest, std::size_t s
namespace {
std::size_t ReadErrorMessage(int error, char (&message)[128]) {
const int result = strerror_r_nid_postfix(error, message, sizeof(message));
if (result != 0 && result != 22) throw std::runtime_error("strerror_s: error message exceeds internal buffer");
FreeBsdErrorMessage_nid_no_patch(error, message, sizeof(message));
return std::strlen(message);
}
+29 -26
View File
@@ -58,40 +58,43 @@ int main() {
Require(std::strcmp(parent, "Invalid argument") == 0);
char buffer[128];
for (int error = 0; error <= 96; ++error) {
Require(strerror_r_nid_postfix(error, buffer, sizeof(buffer)) == 0);
Require(std::strcmp(buffer, freebsd[error]) == 0);
char coded[32];
std::snprintf(coded, sizeof(coded), "error_code=0x%08x", static_cast<unsigned>(error));
const char* expected = error == 0 ? "No error" : error < 95 ? freebsd[error] : coded;
Require(strerror_r_nid_postfix(error, buffer, sizeof(buffer)) == 0);
Require(std::strcmp(buffer, expected) == 0);
Require(std::strcmp(strerror_nid_postfix(error), expected) == 0);
Require(*__error_nid_postfix() == 13);
}
Require(strerror_r_nid_postfix(-1, buffer, sizeof(buffer)) == 22);
Require(std::strcmp(buffer, "Unknown error: -1") == 0);
Require(*__error_nid_postfix() == 13);
struct UnknownError {
int error;
const char* text;
const char* coded;
};
for (const UnknownError& unknown : {UnknownError{97, "Unknown error: 97", "error_code=0x00000061"},
UnknownError{9999, "Unknown error: 9999", "error_code=0x0000270f"},
UnknownError{INT_MAX, "Unknown error: 2147483647", "error_code=0x7fffffff"},
UnknownError{-1, "Unknown error: -1", "error_code=0xffffffff"},
UnknownError{INT_MIN, "Unknown error: -2147483648", "error_code=0x80000000"}}) {
Require(strerror_r_nid_postfix(unknown.error, buffer, sizeof(buffer)) == 22);
Require(std::strcmp(buffer, unknown.text) == 0);
for (const int error : {97, 100, 1000, 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);
Require(std::strcmp(buffer, coded) == 0);
Require(*__error_nid_postfix() == 13);
Require(std::strcmp(strerror_nid_postfix(unknown.error), unknown.coded) == 0);
Require(std::strcmp(strerror_nid_postfix(error), coded) == 0);
Require(*__error_nid_postfix() == 13);
}
char sentinel[] = "xyz";
Require(strerror_r_nid_postfix(22, sentinel, 1) == 34);
Require(sentinel[0] == 0 && sentinel[1] == 'y');
sentinel[0] = 'x';
Require(strerror_r_nid_postfix(22, sentinel, 0) == 34 && sentinel[0] == 'x');
struct ShortBuffer {
int error;
const char* text;
};
for (const ShortBuffer& entry : {ShortBuffer{0, "No error"}, ShortBuffer{22, "Invalid argument"},
ShortBuffer{95, "error_code=0x0000005f"}, ShortBuffer{-1, "error_code=0xffffffff"}}) {
const std::size_t required = std::strlen(entry.text) + 1;
for (const std::size_t length : {std::size_t{0}, std::size_t{1}, std::size_t{5}, required - 1}) {
char canary[64];
std::memset(canary, 0xcc, sizeof(canary));
Require(strerror_r_nid_postfix(entry.error, canary, length) == 34);
for (const char byte : canary) Require(static_cast<unsigned char>(byte) == 0xcc);
Require(*__error_nid_postfix() == 13);
}
char exact[64];
std::memset(exact, 0xcc, sizeof(exact));
Require(strerror_r_nid_postfix(entry.error, exact, required) == 0);
Require(std::strcmp(exact, entry.text) == 0);
Require(static_cast<unsigned char>(exact[required]) == 0xcc);
}
Require(strerror_r_nid_postfix(22, nullptr, 0) == 34);
char truncated[4];
Require(strerror_r_nid_postfix(97, truncated, sizeof(truncated)) == 22 && std::strcmp(truncated, "Unk") == 0);
Require(strerror_r_nid_postfix(-1, truncated, sizeof(truncated)) == 22 && std::strcmp(truncated, "Unk") == 0);
Require(strerror_r_nid_postfix(22, nullptr, sizeof(buffer)) == 34);
}
+2 -1
View File
@@ -60,12 +60,13 @@ void CheckMessages() {
CheckMessage(2, "No such file or directory");
CheckMessage(11, "Resource deadlock avoided");
CheckMessage(35, "Resource temporarily unavailable");
CheckMessage(95, "State not recoverable");
CheckMessage(96, "Previous owner died");
CheckMessage(-1, "Unknown error: -1");
CheckMessage(97, "Unknown error: 97");
CheckMessage(std::numeric_limits<int>::min(), "Unknown error: -2147483648");
CheckMessage(std::numeric_limits<int>::max(), "Unknown error: 2147483647");
for (int error = 0; error < 97; ++error) {
for (int error = 1; error < 95; ++error) {
char expected[128];
Require(strerror_r_nid_postfix(error, expected, sizeof(expected)) == 0, "existing guest error table");
CheckMessage(error, expected);
+1 -1
View File
@@ -47,7 +47,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](../../../core/libs/prx/libc/src/Errors.cpp) (libc) - follows 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, any other number "error_code=0x%08x", and errno is left alone. The console's system libc, which homebrew without a bundled `libc.prx` uses, answers differently ("No error information" for 0 and unknown numbers, 95 and 96 named). `strerror_r` and `perror` keep "No error: 0" and "Unknown error: %d"; they are not measured.
- [strerror, strerror_r and perror](../../../core/libs/prx/libc/src/Errors.cpp) (libc) - a null `strerror_r` buffer returns `ERANGE` (34) without writing; what the console does with one is not measured. Otherwise they 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"; `strerror_r` returns 0 for every number, and 34 without writing anything when the buffer is shorter than the text and its terminator; `perror` prints the same texts; errno is left alone.
- [strerror_s and strerrorlen_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc) follow [C11 K.3.7.4.2 and K.3.7.4.3](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf), using the existing FreeBSD error messages and unknown-error formatting. Truncation writes an ellipsis when capacity exceeds 3; constraint failures leave the destination untouched and invoke the registered guest-ABI handler. The console's `RSIZE_MAX` (`SIZE_MAX >> 1` here), nonzero returns (`EINVAL` 22 for constraints, `ERANGE` 34 for truncation), handler arguments and default abort behavior are unverified. Messages remain in the C locale, and guest errno is preserved.
- [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