diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 2520ab4772f..528b252625d 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -359,6 +359,16 @@ if(BUILD_TESTING) set_tests_properties(guest_kernel_errors PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_getrlimit_tests ${LIBS_INCLUDE_DIR}/tests/GuestGetrlimit.cpp) + target_include_directories(guest_getrlimit_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_getrlimit_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_getrlimit_tests) + add_test(NAME guest_getrlimit COMMAND guest_getrlimit_tests) + set_tests_properties(guest_getrlimit PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_getrlimit PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_equeue_event_tests ${LIBS_INCLUDE_DIR}/tests/GuestEqueueEvents.cpp) target_include_directories(guest_equeue_event_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_equeue_event_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index 13e447cd128..878d4d87591 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -89,7 +89,42 @@ void validateSchedulingPolicy(int policy) { throw std::invalid_argument("Unsupported guest scheduling policy"); } +constexpr int guestFault = 14; +constexpr int guestInvalid = 22; +constexpr int guestRlimitData = 2; +constexpr int guestRlimitCount = 15; +constexpr std::int64_t guestRlimitInfinity = std::numeric_limits::max(); + +#ifdef _WIN32 +std::int64_t hostMemoryLimit() { + BOOL inJob = FALSE; + if (!IsProcessInJob(GetCurrentProcess(), nullptr, &inJob)) + throw std::system_error(GetLastError(), std::system_category(), "getrlimit: IsProcessInJob failed"); + if (!inJob) + return guestRlimitInfinity; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits{}; + if (!QueryInformationJobObject(nullptr, JobObjectExtendedLimitInformation, &limits, sizeof(limits), nullptr)) + throw std::system_error(GetLastError(), std::system_category(), "getrlimit: QueryInformationJobObject failed"); + if ((limits.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_PROCESS_MEMORY) == 0 || limits.ProcessMemoryLimit > static_cast(guestRlimitInfinity)) + return guestRlimitInfinity; + return static_cast(limits.ProcessMemoryLimit); } +#else +std::int64_t toGuestLimit(rlim_t value) { + if (value == RLIM_INFINITY || value > static_cast(guestRlimitInfinity)) + return guestRlimitInfinity; + return static_cast(value); +} +#endif + +} + +extern "C" int* APS5_VABI __error_nid_postfix(); + +struct GuestResourceLimit { + std::int64_t rlim_cur; + std::int64_t rlim_max; +}; struct GuestResourceUsage { KernelTimeval ru_utime; @@ -278,6 +313,31 @@ int APS5_VABI getrusage_nid_postfix(int who, GuestResourceUsage* usage) { return 0; } +int APS5_VABI getrlimit_nid_postfix(int resource, GuestResourceLimit* limit) { + if (resource < 0 || resource >= guestRlimitCount) { + *__error_nid_postfix() = guestInvalid; + return -1; + } + if (limit == nullptr) { + *__error_nid_postfix() = guestFault; + return -1; + } + if (resource != guestRlimitData) + throw std::runtime_error("getrlimit: unsupported resource " + std::to_string(resource)); +#ifdef _WIN32 + const std::int64_t memory = hostMemoryLimit(); + limit->rlim_cur = memory; + limit->rlim_max = memory; +#else + rlimit native{}; + if (::getrlimit(RLIMIT_DATA, &native) != 0) + throw std::system_error(errno, std::generic_category(), "getrlimit: getrlimit failed"); + limit->rlim_cur = toGuestLimit(native.rlim_cur); + limit->rlim_max = toGuestLimit(native.rlim_max); +#endif + return 0; +} + int APS5_VABI sceKernelIsTrinityMode(void) { return 0; } diff --git a/core/libs/tests/GuestGetrlimit.cpp b/core/libs/tests/GuestGetrlimit.cpp new file mode 100644 index 00000000000..598ae8ed941 --- /dev/null +++ b/core/libs/tests/GuestGetrlimit.cpp @@ -0,0 +1,90 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +struct ResourceLimit { + std::int64_t rlim_cur; + std::int64_t rlim_max; +}; + +extern "C" int APS5_VABI getrlimit_nid_postfix(int resource, ResourceLimit* limit); +extern "C" int* APS5_VABI __error_nid_postfix(); + +static constexpr int GUEST_EFAULT = 14; +static constexpr int GUEST_EINVAL = 22; +static constexpr int GUEST_RLIMIT_DATA = 2; +static constexpr int GUEST_RLIMIT_NOFILE = 8; +static constexpr int GUEST_RLIMIT_AS = 10; +static constexpr int GUEST_RLIM_NLIMITS = 15; +static constexpr std::int64_t GUEST_RLIM_INFINITY = std::numeric_limits::max(); + +static void Require(bool value) { if (!value) std::abort(); } + +static ResourceLimit GuestLimit(int resource) { + ResourceLimit limit{-1, -1}; + Require(getrlimit_nid_postfix(resource, &limit) == 0); + Require(limit.rlim_cur >= 0 && limit.rlim_cur <= limit.rlim_max); + return limit; +} + +static void RequireError(int resource, ResourceLimit* limit, int error) { + *__error_nid_postfix() = 0; + Require(getrlimit_nid_postfix(resource, limit) == -1); + Require(*__error_nid_postfix() == error); +} + +#ifndef _WIN32 +static std::int64_t Guest(rlim_t value) { + return value == RLIM_INFINITY ? GUEST_RLIM_INFINITY : static_cast(value); +} + +static void RequireHostLimit(int resource, int hostResource) { + rlimit host{}; + Require(getrlimit(hostResource, &host) == 0); + const ResourceLimit guest = GuestLimit(resource); + Require(guest.rlim_cur == Guest(host.rlim_cur)); + Require(guest.rlim_max == Guest(host.rlim_max)); +} + +static void LoweredHostLimitIsReported() { + rlimit host{}; + Require(getrlimit(RLIMIT_DATA, &host) == 0); + const rlim_t lowered = rlim_t{1} << 40; + if (host.rlim_max != RLIM_INFINITY && host.rlim_max < lowered) return; + host.rlim_cur = lowered; + Require(setrlimit(RLIMIT_DATA, &host) == 0); + Require(GuestLimit(GUEST_RLIMIT_DATA).rlim_cur == static_cast(lowered)); +} +#endif + +static void RequireUnsupported(int resource) { + ResourceLimit limit{}; + bool rejected = false; + try { + getrlimit_nid_postfix(resource, &limit); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected); +} + +int main() { +#ifdef _WIN32 + GuestLimit(GUEST_RLIMIT_DATA); +#else + RequireHostLimit(GUEST_RLIMIT_DATA, RLIMIT_DATA); + LoweredHostLimitIsReported(); +#endif + ResourceLimit limit{}; + RequireError(GUEST_RLIM_NLIMITS, &limit, GUEST_EINVAL); + RequireError(-1, &limit, GUEST_EINVAL); + RequireError(GUEST_RLIM_NLIMITS, nullptr, GUEST_EINVAL); + RequireError(GUEST_RLIMIT_DATA, nullptr, GUEST_EFAULT); + RequireUnsupported(GUEST_RLIMIT_NOFILE); + RequireUnsupported(GUEST_RLIMIT_AS); +}