From d98bbcd5bb4ba75964332330055c448f5b6a1723 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Sun, 4 Oct 2026 18:19:53 +0200 Subject: [PATCH] fix(libkernel): report invalid nanosleep requests through errno and the sce error codes nanosleep and _nanosleep forwarded the result of sceKernelNanosleep, which returned -1 for a rejected request: errno was never set, and the sce call returned a value that is not an sce error. A request with negative seconds was rejected as well, where FreeBSD's kern_nanosleep returns 0 without sleeping. nanosleep and _nanosleep now return -1 with errno set to EINVAL when tv_nsec is outside [0, 1000000000) and to EFAULT for a null request; sceKernelNanosleep returns the matching SCE_KERNEL_ERROR code. A request with valid nanoseconds and negative seconds returns 0 at once. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/CMakeLists.txt | 10 +++ core/libs/prx/libkernel/Time/Time.cpp | 31 +++++--- core/libs/tests/GuestNanosleep.cpp | 100 +++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 core/libs/tests/GuestNanosleep.cpp diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 4c758836a..fb93278a4 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -176,6 +176,16 @@ if(BUILD_TESTING) set_tests_properties(guest_cpu_time_clocks PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_nanosleep_tests ${LIBS_INCLUDE_DIR}/tests/GuestNanosleep.cpp) + target_include_directories(guest_nanosleep_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_nanosleep_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_nanosleep_tests) + add_test(NAME guest_nanosleep COMMAND guest_nanosleep_tests) + set_tests_properties(guest_nanosleep PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_nanosleep PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_pthread_sem_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadSem.cpp) target_include_directories(guest_pthread_sem_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_pthread_sem_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/Time/Time.cpp b/core/libs/prx/libkernel/Time/Time.cpp index 63e759c19..8cde1fc97 100644 --- a/core/libs/prx/libkernel/Time/Time.cpp +++ b/core/libs/prx/libkernel/Time/Time.cpp @@ -2,6 +2,7 @@ #include "prx/libkernel/Time/include/TimedWait.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include #include #include @@ -24,6 +25,8 @@ #include #endif +extern "C" int* APS5_VABI __error_nid_postfix(); + static std::uint64_t RawMonotonicNanos() { #ifdef _WIN32 return TimedWait::NowNanos(); @@ -222,15 +225,15 @@ int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds) { return 0; } -int APS5_VABI sceKernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rmtp) { - if (rqtp == nullptr) { - return -1; +static int SleepForRequest(const KernelTimespec* rqtp, KernelTimespec* rmtp) { + constexpr int guestFault = 14; + constexpr int guestInvalid = 22; + if (rqtp == nullptr) return guestFault; + if (rqtp->tv_nsec < 0 || rqtp->tv_nsec >= 1000000000LL) return guestInvalid; + if (rqtp->tv_sec >= 0) { + TimedWait::SleepNanos(static_cast(rqtp->tv_sec) * 1000000000ULL + + static_cast(rqtp->tv_nsec)); } - if (rqtp->tv_sec < 0 || rqtp->tv_nsec < 0 || rqtp->tv_nsec >= 1000000000LL) { - return -1; - } - TimedWait::SleepNanos(static_cast(rqtp->tv_sec) * 1000000000ULL + - static_cast(rqtp->tv_nsec)); if (rmtp != nullptr) { rmtp->tv_sec = 0; rmtp->tv_nsec = 0; @@ -238,12 +241,20 @@ int APS5_VABI sceKernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rmt return 0; } +int APS5_VABI sceKernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rmtp) { + const int error = SleepForRequest(rqtp, rmtp); + return error == 0 ? 0 : SceKernelError(error); +} + int APS5_VABI nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp) { - return sceKernelNanosleep(rqtp, rmtp); + const int error = SleepForRequest(rqtp, rmtp); + if (error == 0) return 0; + *__error_nid_postfix() = error; + return -1; } int APS5_VABI _nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp) { - return sceKernelNanosleep(rqtp, rmtp); + return nanosleep_nid_postfix(rqtp, rmtp); } int APS5_VABI usleep_nid_postfix(KernelUseconds microseconds) { diff --git a/core/libs/tests/GuestNanosleep.cpp b/core/libs/tests/GuestNanosleep.cpp new file mode 100644 index 000000000..db2e4ee8b --- /dev/null +++ b/core/libs/tests/GuestNanosleep.cpp @@ -0,0 +1,100 @@ +#include "SceTypes.hpp" +#include +#include + +extern "C" { +int APS5_VABI nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp); +int APS5_VABI _nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp); +int APS5_VABI sceKernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rmtp); +int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); +int* APS5_VABI __error_nid_postfix(); +} + +using Nanosleep = int (APS5_VABI *)(const KernelTimespec*, KernelTimespec*); + +static constexpr int SCE_OK = 0; +static constexpr int SCE_KERNEL_ERROR_EFAULT = static_cast(0x8002000E); +static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); + +static constexpr int GUEST_EACCES = 13; +static constexpr int GUEST_EFAULT = 14; +static constexpr int GUEST_EINVAL = 22; +static constexpr int GUEST_CLOCK_MONOTONIC = 4; + +static constexpr std::int64_t NANOS_PER_SECOND = 1000000000LL; +static constexpr std::int64_t NANOS_PER_MILLISECOND = 1000000LL; +static constexpr std::int64_t SLEEP_NANOS = 50 * NANOS_PER_MILLISECOND; +static constexpr std::int64_t EARLY_WAKE_MARGIN_NANOS = 5 * NANOS_PER_MILLISECOND; +static constexpr std::int64_t NO_SLEEP_LIMIT_NANOS = 2 * NANOS_PER_SECOND; + +static void Require(bool value) { if (!value) std::abort(); } + +static std::int64_t MonotonicNanos() { + KernelTimespec time{-1, -1}; + Require(clock_gettime_nid_postfix(GUEST_CLOCK_MONOTONIC, &time) == SCE_OK); + return time.tv_sec * NANOS_PER_SECOND + time.tv_nsec; +} + +static void PosixRejects(Nanosleep sleep, const KernelTimespec* request, int error) { + *__error_nid_postfix() = GUEST_EACCES; + Require(sleep(request, nullptr) == -1); + Require(*__error_nid_postfix() == error); +} + +static void PosixRejectsInvalidRequests(Nanosleep sleep) { + const KernelTimespec negativeNanos{0, -1}; + const KernelTimespec wholeSecondOfNanos{0, NANOS_PER_SECOND}; + const KernelTimespec negativeBoth{-1, -1}; + PosixRejects(sleep, &negativeNanos, GUEST_EINVAL); + PosixRejects(sleep, &wholeSecondOfNanos, GUEST_EINVAL); + PosixRejects(sleep, &negativeBoth, GUEST_EINVAL); + PosixRejects(sleep, nullptr, GUEST_EFAULT); +} + +static void SceRejectsInvalidRequests() { + const KernelTimespec negativeNanos{0, -1}; + const KernelTimespec wholeSecondOfNanos{0, NANOS_PER_SECOND}; + const KernelTimespec negativeBoth{-1, -1}; + Require(sceKernelNanosleep(&negativeNanos, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelNanosleep(&wholeSecondOfNanos, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelNanosleep(&negativeBoth, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelNanosleep(nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT); +} + +static void ReturnsAtOnce(Nanosleep sleep, KernelTimespec request) { + const std::int64_t start = MonotonicNanos(); + *__error_nid_postfix() = GUEST_EACCES; + Require(sleep(&request, nullptr) == SCE_OK); + Require(*__error_nid_postfix() == GUEST_EACCES); + Require(MonotonicNanos() - start < NO_SLEEP_LIMIT_NANOS); +} + +static void NegativeSecondsDoNotSleep(Nanosleep sleep) { + ReturnsAtOnce(sleep, {-1, 0}); + ReturnsAtOnce(sleep, {-1, NANOS_PER_SECOND - 1}); + ReturnsAtOnce(sleep, {INT64_MIN, 0}); + ReturnsAtOnce(sleep, {0, 0}); +} + +static void SleepsForTheRequest(Nanosleep sleep) { + const KernelTimespec request{0, SLEEP_NANOS}; + const std::int64_t start = MonotonicNanos(); + *__error_nid_postfix() = GUEST_EACCES; + Require(sleep(&request, nullptr) == SCE_OK); + Require(*__error_nid_postfix() == GUEST_EACCES); + Require(MonotonicNanos() - start >= SLEEP_NANOS - EARLY_WAKE_MARGIN_NANOS); +} + +int main() { + PosixRejectsInvalidRequests(nanosleep_nid_postfix); + PosixRejectsInvalidRequests(_nanosleep_nid_postfix); + SceRejectsInvalidRequests(); + + NegativeSecondsDoNotSleep(nanosleep_nid_postfix); + NegativeSecondsDoNotSleep(_nanosleep_nid_postfix); + NegativeSecondsDoNotSleep(sceKernelNanosleep); + + SleepsForTheRequest(nanosleep_nid_postfix); + SleepsForTheRequest(_nanosleep_nid_postfix); + SleepsForTheRequest(sceKernelNanosleep); +}