From bd842bc7d2340641a94754f39da455266cc3a398 Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:57:02 +0200 Subject: [PATCH] fix(libs): share Windows flock state between duplicated descriptors --- core/libs/prx/libc/CMakeLists.txt | 1 + core/libs/prx/libc/include/FileLock.hpp | 12 + core/libs/prx/libc/include/FileStream.hpp | 5 + core/libs/prx/libc/src/FileLock.cpp | 83 +++++ core/libs/prx/libkernel/CMakeLists.txt | 9 + .../libkernel/File/src/DescriptorControl.cpp | 7 + core/libs/prx/libkernel/File/src/FileLock.cpp | 63 +--- core/libs/tests/GuestFileLock.cpp | 306 ++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 9 files changed, 427 insertions(+), 60 deletions(-) create mode 100644 core/libs/prx/libc/include/FileLock.hpp create mode 100644 core/libs/prx/libc/src/FileLock.cpp create mode 100644 core/libs/tests/GuestFileLock.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index f631dc835..a957817d0 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -17,6 +17,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/HostEntropy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FileAndHeap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/FileLock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/StdioExtras.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/StandardStreams.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestAllocations.cpp diff --git a/core/libs/prx/libc/include/FileLock.hpp b/core/libs/prx/libc/include/FileLock.hpp new file mode 100644 index 000000000..63fc6be67 --- /dev/null +++ b/core/libs/prx/libc/include/FileLock.hpp @@ -0,0 +1,12 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_FILELOCK_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_FILELOCK_HPP + +#ifdef _WIN32 +extern "C" { +int GuestFileLock_nid_no_patch(int fd, int operation); +void GuestFileLockShare_nid_no_patch(int from, int to); +void GuestFileLockForget_nid_no_patch(int fd); +} +#endif + +#endif diff --git a/core/libs/prx/libc/include/FileStream.hpp b/core/libs/prx/libc/include/FileStream.hpp index f93727c73..88cee1647 100644 --- a/core/libs/prx/libc/include/FileStream.hpp +++ b/core/libs/prx/libc/include/FileStream.hpp @@ -12,6 +12,7 @@ #include #ifdef _WIN32 #include +#include "prx/libc/include/FileLock.hpp" #endif struct GuestFilePrefix { @@ -76,6 +77,7 @@ public: #ifdef _WIN32 std::wstring wideMode; for (const char character : std::string_view(mode)) wideMode.push_back(static_cast(character)); + GuestFileLockForget_nid_no_patch(::_fileno(previous)); _handle = ::_wfreopen(filename.c_str(), wideMode.c_str(), previous); #else _handle = std::freopen(filename.c_str(), mode, previous); @@ -124,6 +126,9 @@ public: int Close() { GetHandle(); +#ifdef _WIN32 + GuestFileLockForget_nid_no_patch(::_fileno(_handle)); +#endif const int result = std::fclose(std::exchange(_handle, nullptr)); _guest.flags = 0; _guest.descriptor = -1; diff --git a/core/libs/prx/libc/src/FileLock.cpp b/core/libs/prx/libc/src/FileLock.cpp new file mode 100644 index 000000000..1ed227981 --- /dev/null +++ b/core/libs/prx/libc/src/FileLock.cpp @@ -0,0 +1,83 @@ +#include "prx/libc/include/FileLock.hpp" + +#ifdef _WIN32 + +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int LockShared = 1; +constexpr int LockExclusive = 2; +constexpr int LockNonblocking = 4; +constexpr int LockUnlock = 8; + +struct LockState { + std::mutex mutex; + int mode = 0; +}; + +std::mutex g_mutex; +std::map> g_locks; + +std::shared_ptr Materialize(int fd) { + const auto found = g_locks.find(fd); + if (found != g_locks.end()) return found->second; + return g_locks.emplace(fd, std::make_shared()).first->second; +} + +} + +extern "C" { + +int GuestFileLock_nid_no_patch(int fd, int operation) { + const int mode = operation & (LockShared | LockExclusive | LockUnlock); + if (mode != LockShared && mode != LockExclusive && mode != LockUnlock) { + errno = EINVAL; + ::SetLastError(ERROR_INVALID_PARAMETER); + return -1; + } + const auto handle = reinterpret_cast(::_get_osfhandle(fd)); + if (handle == INVALID_HANDLE_VALUE) { + errno = EBADF; + ::SetLastError(ERROR_INVALID_HANDLE); + return -1; + } + const auto state = [&] { + std::lock_guard lock(g_mutex); + return Materialize(fd); + }(); + std::lock_guard lock(state->mutex); + if (state->mode == mode) return 0; + if (state->mode != 0) { + OVERLAPPED overlapped{}; + if (!::UnlockFileEx(handle, 0, MAXDWORD, MAXDWORD, &overlapped)) return -1; + state->mode = 0; + } + if (mode == LockUnlock) return 0; + DWORD flags = mode == LockExclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0; + if (operation & LockNonblocking) flags |= LOCKFILE_FAIL_IMMEDIATELY; + OVERLAPPED overlapped{}; + if (!::LockFileEx(handle, flags, 0, MAXDWORD, MAXDWORD, &overlapped)) return -1; + state->mode = mode; + return 0; +} + +void GuestFileLockShare_nid_no_patch(int from, int to) { + std::lock_guard lock(g_mutex); + auto state = Materialize(from); + g_locks[to] = std::move(state); +} + +void GuestFileLockForget_nid_no_patch(int fd) { + std::lock_guard lock(g_mutex); + g_locks.erase(fd); +} + +} + +#endif diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 5ab912cf3..b17b3587f 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -1119,6 +1119,15 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(guest_kernel_fcntl PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + add_test_executable(guest_file_lock_tests ${LIBS_INCLUDE_DIR}/tests/GuestFileLock.cpp) + target_include_directories(guest_file_lock_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_file_lock_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_file_lock_tests) + foreach(fileLockCase IN ITEMS duplicate lifetime conversion threads streams process) + add_test(NAME guest_file_lock_${fileLockCase} COMMAND guest_file_lock_tests ${fileLockCase}) + set_tests_properties(guest_file_lock_${fileLockCase} PROPERTIES TIMEOUT 20 ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endforeach() endif() add_test_executable(guest_sandbox_random_word_tests ${LIBS_INCLUDE_DIR}/tests/GuestSandboxRandomWord.cpp) target_include_directories(guest_sandbox_random_word_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libkernel/File/src/DescriptorControl.cpp b/core/libs/prx/libkernel/File/src/DescriptorControl.cpp index 7bdd8c8bc..d2a62b739 100644 --- a/core/libs/prx/libkernel/File/src/DescriptorControl.cpp +++ b/core/libs/prx/libkernel/File/src/DescriptorControl.cpp @@ -3,6 +3,7 @@ #include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/File/include/RandomDevice.hpp" #include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/FileLock.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -494,12 +495,18 @@ int Ioctl(int fd, std::uint64_t request, void* argument) { void RememberDescriptor(int fd, int openFlags) { std::lock_guard lock(g_mutex); g_entries[fd] = Entry{(openFlags & OpenCloseOnExec) != 0, std::make_shared(openFlags & OpenStatusFlags)}; +#ifdef _WIN32 + GuestFileLockForget_nid_no_patch(fd); +#endif } void ShareDescriptor(int from, int to) { std::lock_guard lock(g_mutex); auto status = Materialize(from).status; g_entries[to] = Entry{false, std::move(status)}; +#ifdef _WIN32 + GuestFileLockShare_nid_no_patch(from, to); +#endif } void ForgetDescriptor(int fd) { diff --git a/core/libs/prx/libkernel/File/src/FileLock.cpp b/core/libs/prx/libkernel/File/src/FileLock.cpp index 3b2f67122..1ebc7e409 100644 --- a/core/libs/prx/libkernel/File/src/FileLock.cpp +++ b/core/libs/prx/libkernel/File/src/FileLock.cpp @@ -1,73 +1,16 @@ #include "prx/libkernel/File/include/FileLock.hpp" +#include "prx/libc/include/FileLock.hpp" #ifdef _WIN32 -#include -#include -#include -#include -#include - -namespace { - -constexpr int LockShared = 1; -constexpr int LockExclusive = 2; -constexpr int LockNonblocking = 4; -constexpr int LockUnlock = 8; - -std::mutex g_mutex; -std::map g_locks; - -int HeldLock(int fd) { - std::lock_guard lock(g_mutex); - const auto found = g_locks.find(fd); - return found == g_locks.end() ? 0 : found->second; -} - -void SetHeldLock(int fd, int mode) { - std::lock_guard lock(g_mutex); - if (mode == 0) { - g_locks.erase(fd); - } else { - g_locks[fd] = mode; - } -} - -} - namespace File { int Flock(int fd, int operation) { - const int mode = operation & (LockShared | LockExclusive | LockUnlock); - if (mode != LockShared && mode != LockExclusive && mode != LockUnlock) { - errno = EINVAL; - ::SetLastError(ERROR_INVALID_PARAMETER); - return -1; - } - const auto handle = reinterpret_cast(::_get_osfhandle(fd)); - if (handle == INVALID_HANDLE_VALUE) { - errno = EBADF; - ::SetLastError(ERROR_INVALID_HANDLE); - return -1; - } - const int held = HeldLock(fd); - if (held == mode) return 0; - if (held != 0) { - OVERLAPPED overlapped{}; - if (!::UnlockFileEx(handle, 0, MAXDWORD, MAXDWORD, &overlapped)) return -1; - SetHeldLock(fd, 0); - } - if (mode == LockUnlock) return 0; - DWORD flags = mode == LockExclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0; - if (operation & LockNonblocking) flags |= LOCKFILE_FAIL_IMMEDIATELY; - OVERLAPPED overlapped{}; - if (!::LockFileEx(handle, flags, 0, MAXDWORD, MAXDWORD, &overlapped)) return -1; - SetHeldLock(fd, mode); - return 0; + return GuestFileLock_nid_no_patch(fd, operation); } void ForgetFileLock(int fd) { - SetHeldLock(fd, 0); + GuestFileLockForget_nid_no_patch(fd); } } diff --git a/core/libs/tests/GuestFileLock.cpp b/core/libs/tests/GuestFileLock.cpp new file mode 100644 index 000000000..eea442119 --- /dev/null +++ b/core/libs/tests/GuestFileLock.cpp @@ -0,0 +1,306 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class FileStream; + +extern "C" { +int APS5_VABI open_nid_postfix(const char*, int, int); +int APS5_VABI close_nid_postfix(int); +int APS5_VABI sceKernelClose(int); +int APS5_VABI dup_nid_postfix(int); +int APS5_VABI dup2_nid_postfix(int, int); +int APS5_VABI fcntl_nid_postfix(int, int, ...); +int APS5_VABI flock_nid_postfix(int, int); +int* APS5_VABI __error_nid_postfix(); +FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); +FileStream* APS5_VABI fdopen_nid_postfix(int, const char*); +FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*); +int APS5_VABI fclose_nid_postfix(FileStream*); +int APS5_VABI fileno_nid_postfix(FileStream*); +} + +static void Require(bool condition) { + if (!condition) std::abort(); +} + +static int Duplicate(int source, int kind) { + if (kind == 0) return dup_nid_postfix(source); + if (kind == 1) return dup2_nid_postfix(source, 20); + return fcntl_nid_postfix(source, 0, 20); +} + +static bool Acquires(int descriptor, int mode) { + const int result = flock_nid_postfix(descriptor, mode | 4); + if (result == 0) { + Require(flock_nid_postfix(descriptor, 8) == 0); + return true; + } + Require(result == -1 && *__error_nid_postfix() == 35); + return false; +} + +struct Fixture { + static inline unsigned serial = 0; + std::string path = (std::filesystem::temp_directory_path() / ("anyps5-flock-" + std::to_string(::GetCurrentProcessId()) + "-" + std::to_string(serial++) + ".bin")).string(); + std::set descriptors; + int source = Open(true); + int other = Open(false); + + int Open(bool create) { + const int descriptor = open_nid_postfix(path.c_str(), create ? 0x602 : 2, 0644); + Require(descriptor >= 0); + descriptors.insert(descriptor); + return descriptor; + } + int Copy(int kind, int from = -1) { + const int descriptor = Duplicate(from < 0 ? source : from, kind); + Require(descriptor >= 0); + descriptors.insert(descriptor); + return descriptor; + } + void Close(int descriptor, bool kernel = false) { + Require((kernel ? sceKernelClose(descriptor) : close_nid_postfix(descriptor)) == 0); + descriptors.erase(descriptor); + } + ~Fixture() { + for (const int descriptor : descriptors) Require(close_nid_postfix(descriptor) == 0); + Require(std::remove(path.c_str()) == 0); + } +}; + +static void Duplicates() { + for (int kind = 0; kind != 3; ++kind) { + for (bool before : {false, true}) { + for (int mode : {1, 2}) { + Fixture fixture; + int copy = before ? fixture.Copy(kind) : -1; + Require(flock_nid_postfix(fixture.source, mode | 4) == 0); + if (!before) copy = fixture.Copy(kind); + Require(flock_nid_postfix(copy, mode | 4) == 0); + Require(flock_nid_postfix(fixture.source, mode | 4) == 0); + Require(!Acquires(fixture.other, 2)); + Require(flock_nid_postfix(copy, 8) == 0); + Require(Acquires(fixture.other, 2)); + Require(flock_nid_postfix(fixture.source, 8) == 0); + } + } + } +} + +static void Lifetime() { + for (int kind = 0; kind != 3; ++kind) { + for (bool kernel : {false, true}) { + Fixture fixture; + const int copy = fixture.Copy(kind); + Require(flock_nid_postfix(fixture.source, 2 | 4) == 0); + const int chain = fixture.Copy(0, copy); + fixture.Close(fixture.source, kernel); + fixture.Close(copy, !kernel); + Require(!Acquires(fixture.other, 2)); + Require(flock_nid_postfix(chain, 8) == 0); + Require(Acquires(fixture.other, 2)); + Require(flock_nid_postfix(chain, 2 | 4) == 0); + fixture.Close(chain, kernel); + Require(Acquires(fixture.other, 2)); + } + } + { + Fixture fixture; + const int copy = fixture.Copy(0); + Require(flock_nid_postfix(fixture.source, 2 | 4) == 0); + Require(dup2_nid_postfix(copy, fixture.source) == fixture.source); + Require(dup2_nid_postfix(fixture.source, fixture.source) == fixture.source); + Require(!Acquires(fixture.other, 2)); + Require(flock_nid_postfix(fixture.source, 8) == 0); + Require(Acquires(fixture.other, 2)); + } + { + Fixture first; + Fixture second; + const int copy = second.Copy(0); + Require(flock_nid_postfix(second.source, 2 | 4) == 0); + Require(dup2_nid_postfix(first.source, second.source) == second.source); + Require(!Acquires(second.other, 2)); + Require(flock_nid_postfix(copy, 8) == 0); + Require(Acquires(second.other, 2)); + Require(flock_nid_postfix(second.source, 2 | 4) == 0); + Require(!Acquires(first.other, 2)); + Require(flock_nid_postfix(first.source, 8) == 0); + Require(Acquires(first.other, 2)); + } + { + Fixture fixture; + const int previous = fixture.source; + Require(flock_nid_postfix(previous, 2 | 4) == 0); + fixture.Close(previous); + const int recycled = fixture.Open(false); + Require(recycled == previous && Acquires(recycled, 2)); + Require(Acquires(fixture.other, 2)); + } +} + +static void Conversions() { + for (int kind = 0; kind != 3; ++kind) { + Fixture fixture; + const int copy = fixture.Copy(kind); + for (int repeat = 0; repeat != 20; ++repeat) { + Require(flock_nid_postfix(fixture.source, 2 | 4) == 0); + Require(flock_nid_postfix(copy, 1 | 4) == 0); + Require(Acquires(fixture.other, 1) && !Acquires(fixture.other, 2)); + Require(flock_nid_postfix(fixture.source, 2 | 4) == 0); + Require(!Acquires(fixture.other, 1)); + Require(flock_nid_postfix(copy, 8) == 0 && Acquires(fixture.other, 2)); + } + Require(flock_nid_postfix(fixture.source, 1 | 4) == 0); + Require(flock_nid_postfix(fixture.other, 1 | 4) == 0); + Require(flock_nid_postfix(copy, 2 | 4) == -1 && *__error_nid_postfix() == 35); + Require(flock_nid_postfix(fixture.other, 8) == 0); + Require(Acquires(fixture.other, 2)); + } +} + +static void Threads() { + for (int kind = 0; kind != 3; ++kind) { + Fixture fixture; + Require(flock_nid_postfix(fixture.source, 2 | 4) == 0); + const int copy = fixture.Copy(kind); + auto repeated = std::async(std::launch::async, [&] { return flock_nid_postfix(copy, 2); }); + Require(repeated.wait_for(std::chrono::seconds(2)) == std::future_status::ready); + Require(repeated.get() == 0 && flock_nid_postfix(copy, 8) == 0); + } + for (bool duplicate : {false, true}) { + Fixture fixture; + const int copy = duplicate ? fixture.Copy(0) : fixture.source; + std::barrier gate(3); + int results[2] = {-2, -2}; + std::thread first([&] { gate.arrive_and_wait(); results[0] = flock_nid_postfix(fixture.source, 1 | 4); }); + std::thread second([&] { gate.arrive_and_wait(); results[1] = flock_nid_postfix(copy, 1 | 4); }); + gate.arrive_and_wait(); + first.join(); + second.join(); + Require(results[0] == 0 && results[1] == 0); + Require(flock_nid_postfix(copy, 8) == 0 && Acquires(fixture.other, 2)); + } + Fixture first; + Fixture unrelated; + Require(flock_nid_postfix(first.source, 2 | 4) == 0); + std::promise entered; + auto entry = entered.get_future(); + auto waiting = std::async(std::launch::async, [&] { + entered.set_value(); + return flock_nid_postfix(first.other, 2); + }); + entry.wait(); + Require(waiting.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout); + Require(Acquires(unrelated.source, 2)); + Require(flock_nid_postfix(first.source, 8) == 0); + Require(waiting.wait_for(std::chrono::seconds(2)) == std::future_status::ready && waiting.get() == 0); + Require(flock_nid_postfix(first.other, 8) == 0); +} + +static void Streams() { + Fixture first; + Fixture second; + auto* stream = fopen_nid_postfix(first.path.c_str(), "r+b"); + Require(stream != nullptr); + const int descriptor = fileno_nid_postfix(stream); + Require(flock_nid_postfix(descriptor, 2 | 4) == 0); + Require(fclose_nid_postfix(stream) == 0 && Acquires(first.other, 2)); + for (const auto* fixture : {&second, &first}) { + stream = fopen_nid_postfix(fixture->path.c_str(), "r+b"); + Require(stream != nullptr && fileno_nid_postfix(stream) == descriptor); + Require(flock_nid_postfix(descriptor, 2 | 4) == 0 && !Acquires(fixture->other, 2)); + Require(fclose_nid_postfix(stream) == 0); + } + const int recycled = first.Open(false); + Require(recycled == descriptor && flock_nid_postfix(recycled, 2 | 4) == 0); + Require(!Acquires(first.other, 2)); + const int copy = first.Copy(0, recycled); + stream = fdopen_nid_postfix(recycled, "r+"); + Require(stream != nullptr && !Acquires(first.other, 2)); + Require(fclose_nid_postfix(stream) == 0); + first.descriptors.erase(recycled); + Require(!Acquires(first.other, 2)); + Require(flock_nid_postfix(copy, 8) == 0 && Acquires(first.other, 2)); + stream = fopen_nid_postfix(first.path.c_str(), "r+b"); + Require(stream != nullptr); + const int reopened = fileno_nid_postfix(stream); + Require(flock_nid_postfix(reopened, 2 | 4) == 0); + const int oldCopy = first.Copy(0, reopened); + Require(freopen_nid_postfix(second.path.c_str(), "r+b", stream) == stream); + Require(!Acquires(first.other, 2)); + Require(flock_nid_postfix(oldCopy, 8) == 0 && Acquires(first.other, 2)); + Require(flock_nid_postfix(fileno_nid_postfix(stream), 2 | 4) == 0 && !Acquires(second.other, 2)); + const auto missing = first.path + ".missing"; + Require(freopen_nid_postfix(missing.c_str(), "r+b", stream) == nullptr); + Require(Acquires(second.other, 2)); + stream = fopen_nid_postfix(second.path.c_str(), "r+b"); + Require(stream != nullptr); + Require(flock_nid_postfix(fileno_nid_postfix(stream), 2 | 4) == 0 && !Acquires(second.other, 2)); + Require(fclose_nid_postfix(stream) == 0); +} + +static int ChildTry(const std::string& path, int mode) { + wchar_t executable[32768]; + Require(::GetModuleFileNameW(nullptr, executable, 32768) != 0); + std::wstring command = L"\"" + std::wstring(executable) + L"\" child \"" + std::filesystem::path(path).wstring() + L"\" " + std::to_wstring(mode | 4); + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + PROCESS_INFORMATION process{}; + Require(::CreateProcessW(nullptr, command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &startup, &process) != 0); + ::CloseHandle(process.hThread); + if (::WaitForSingleObject(process.hProcess, 3000) != WAIT_OBJECT_0) { + ::TerminateProcess(process.hProcess, 99); + std::abort(); + } + DWORD result = 99; + Require(::GetExitCodeProcess(process.hProcess, &result) != 0); + ::CloseHandle(process.hProcess); + return static_cast(result); +} + +static void Processes() { + for (int kind = 0; kind != 3; ++kind) { + for (int mode : {1, 2}) { + Fixture fixture; + Require(flock_nid_postfix(fixture.source, mode | 4) == 0); + const int copy = fixture.Copy(kind); + Require(ChildTry(fixture.path, 2) == 35); + Require(ChildTry(fixture.path, 1) == (mode == 1 ? 0 : 35)); + fixture.Close(fixture.source); + Require(ChildTry(fixture.path, 2) == 35); + Require(flock_nid_postfix(copy, 8) == 0); + Require(ChildTry(fixture.path, 2) == 0); + } + } +} + +int main(int argc, char** argv) { + if (argc == 4 && std::string(argv[1]) == "child") { + const int descriptor = open_nid_postfix(argv[2], 2, 0); + Require(descriptor >= 0); + const int result = flock_nid_postfix(descriptor, std::atoi(argv[3])); + const int error = result == 0 ? 0 : *__error_nid_postfix(); + if (result == 0) Require(flock_nid_postfix(descriptor, 8) == 0); + Require(close_nid_postfix(descriptor) == 0); + return error; + } + Require(argc == 2); + const std::string mode = argv[1]; + if (mode == "duplicate") Duplicates(); + else if (mode == "lifetime") Lifetime(); + else if (mode == "conversion") Conversions(); + else if (mode == "threads") Threads(); + else if (mode == "streams") Streams(); + else if (mode == "process") Processes(); + else std::abort(); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index e05b39391..403d70855 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -716,3 +716,4 @@ Throughout the project, every function at every stage either **does exactly what - [pthread_spin_init, pthread_spin_destroy, pthread_spin_lock, pthread_spin_trylock, pthread_spin_unlock](../../core/libs/prx/libkernel/Pthread/Posix/Spinlock.cpp) (libkernel) follow FreeBSD 11.4 libthr [`thr_pspinlock.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_pspinlock.c) and the owner checks of its [`thr_umtx.h`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_umtx.h#L80-L175), unverified on the console: `EINVAL` for a null or destroyed lock and a `pshared` other than 0 or 1, `EBUSY` from `trylock` while any thread holds it, `EPERM` from `unlock` by a thread that does not hold it. `PTHREAD_PROCESS_SHARED` allocates a private lock, as guest code runs in one process. libthr spins forever when the holder locks again; here that call throws - [pthread_cond_reltimedwait_np](../../core/libs/prx/libkernel/Pthread/Posix/Cond.cpp) (libkernel) takes `(cond, mutex, usec)`, a relative timeout in microseconds, and is `scePthreadCondTimedwait` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadCondTimedwait` ([`condvar.cpp`](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/libraries/kernel/threads/condvar.cpp#L229-L234)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `sem_reltimedwait_np`. Unverified on the console - [sysctl kern.arandom](../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) serves FreeBSD 11's MIB {1, 37} (CTL_KERN, KERN_ARANDOM) and the "kern.arandom" sysctlbyname lookup through std::random_device, returning min(*oldLength, 256) random bytes. Following FreeBSD's sysctl_kern_arnd, a null buffer reports min(*oldLength, 256) without writing, a null length returns 0 without writing, and requests with a new value return EPERM (1) as the node is read-only. Cryptographic quality depends on the host's random device implementation. +- [flock](../../core/libs/prx/libc/src/FileLock.cpp) (libkernel) on Windows shares one lock state between duplicated descriptors, following FreeBSD 11.4's [flock(2)](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/sys/flock.2). Independent opens have separate states; closing a descriptor or reopening a stream drops only its reference. Lock transitions on one open file description are serialized. The host's [LockFileEx](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex) locks also restrict ordinary reads and writes through other handles, unlike FreeBSD's advisory locks, and do not restrict mapped views. Console behavior, signal interruption and concurrent closure of a descriptor while its lock call is pending are unverified.