diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 209da779b..686be48ce 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -104,6 +104,15 @@ endforeach() add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS}) if(BUILD_TESTING) + add_executable(guest_error_tests tests/GuestErrors.cpp) + target_include_directories(guest_error_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_error_tests PRIVATE libc) + configure_windows_unwind(guest_error_tests) + add_test(NAME guest_errors COMMAND guest_error_tests) + if(WIN32) + set_tests_properties(guest_errors PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_executable(guest_resolver_tests tests/GuestResolver.cpp) target_include_directories(guest_resolver_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_resolver_tests PRIVATE libScePosixForWebKit) diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index b3428c80d..574558de6 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -18,6 +18,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RuntimeSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/LocaleSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Process.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Errors.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/CxxAbiSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/exception/Exports.cpp ) diff --git a/core/libs/prx/libc/src/Errors.cpp b/core/libs/prx/libc/src/Errors.cpp new file mode 100644 index 000000000..963191cf6 --- /dev/null +++ b/core/libs/prx/libc/src/Errors.cpp @@ -0,0 +1,75 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" int* APS5_VABI __error_nid_postfix(); + +namespace { +// Guest FreeBSD errno numbering, not the host CRT's numbering. +constexpr const char* messages[] = { + "No error", "Operation not permitted", "No such file or directory", "No such process", + "Interrupted system call", "Input/output error", "Device not configured", "Argument list too long", + "Exec format error", "Bad file descriptor", "No child processes", "Resource deadlock avoided", + "Cannot allocate memory", "Permission denied", "Bad address", "Block device required", + "Device busy", "File exists", "Cross-device link", "Operation not supported by device", + "Not a directory", "Is a directory", "Invalid argument", "Too many open files in system", + "Too many open files", "Inappropriate ioctl for device", "Text file busy", "File too large", + "No space left on device", "Illegal seek", "Read-only filesystem", "Too many links", "Broken pipe", + "Numerical argument out of domain", "Result too large", "Resource temporarily unavailable", + "Operation now in progress", "Operation already in progress", "Socket operation on non-socket", + "Destination address required", "Message too long", "Protocol wrong type for socket", + "Protocol not available", "Protocol not supported", "Socket type not supported", + "Operation not supported", "Protocol family not supported", "Address family not supported", + "Address already in use", "Cannot assign requested address", "Network is down", "Network is unreachable", + "Network connection reset", "Connection aborted", "Connection reset by peer", "No buffer space available", + "Socket is already connected", "Socket is not connected", "Socket is shut down", "Too many references", + "Operation timed out", "Connection refused", "Too many symbolic links", "File name too long", + "Host is down", "No route to host", "Directory not empty", "Too many processes", "Too many users", + "Disk quota exceeded", "Stale file handle", "Remote path has too many levels", "Invalid RPC structure", + "RPC version mismatch", "RPC program unavailable", "Program version mismatch", "Invalid RPC procedure", + "No locks available", "Function not implemented", "Inappropriate file type", "Authentication error", + "Authentication required", "Identifier removed", "No message of requested type", "Value too large", + "Operation canceled", "Invalid byte sequence", "Attribute not found", "Programming error", + "Bad message", "Multihop attempted", "Link severed", "Protocol error", "Insufficient capabilities", + "Operation not permitted in capability mode", "State not recoverable", "Previous owner died" +}; +static_assert(sizeof(messages) / sizeof(messages[0]) == 97); +bool Known(int error) { return error >= 0 && error < 97; } +} + +extern "C" { +char* APS5_VABI strerror_nid_postfix(int error) { + thread_local char buffer[128]; + const int saved = *__error_nid_postfix(); + if (Known(error)) std::snprintf(buffer, sizeof(buffer), "%s", messages[error]); + else std::snprintf(buffer, sizeof(buffer), "Unknown error: %d", error); + *__error_nid_postfix() = Known(error) ? saved : 22; + 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 (!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'; + } + *__error_nid_postfix() = saved; + return result; +} + +void APS5_VABI perror_nid_postfix(const char* prefix) { + const int saved = *__error_nid_postfix(); + char message[128]; + strerror_r_nid_postfix(saved, message, sizeof(message)); + if (prefix && *prefix) std::fprintf(stderr, "%s: %s\n", prefix, message); + else std::fprintf(stderr, "%s\n", message); + *__error_nid_postfix() = saved; +} +} diff --git a/core/libs/tests/GuestErrors.cpp b/core/libs/tests/GuestErrors.cpp new file mode 100644 index 000000000..453dae221 --- /dev/null +++ b/core/libs/tests/GuestErrors.cpp @@ -0,0 +1,37 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" { +char* APS5_VABI strerror_nid_postfix(int); +int APS5_VABI strerror_r_nid_postfix(int, char*, std::size_t); +int* APS5_VABI __error_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + *__error_nid_postfix() = 13; + Require(std::strcmp(strerror_nid_postfix(35), "Resource temporarily unavailable") == 0); + Require(*__error_nid_postfix() == 13); + Require(std::strcmp(strerror_nid_postfix(78), "Function not implemented") == 0); + char* parent = strerror_nid_postfix(22); + std::thread worker([] { + Require(std::strcmp(strerror_nid_postfix(45), "Operation not supported") == 0); + }); + worker.join(); + 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(buffer[0] && std::strstr(buffer, "Unknown") == nullptr); + } + Require(strerror_r_nid_postfix(-1, buffer, sizeof(buffer)) == 22); + Require(std::strstr(buffer, "-1") != nullptr); + 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'); + Require(strerror_r_nid_postfix(22, nullptr, 0) == 34); +}