Merge pull request #4516 from Ondra97/codex/file-lock-lifetime

fix(libs): share Windows flock state between duplicated descriptors
This commit is contained in:
Dean 2026-10-13 07:30:48 +13:00 committed by GitHub
commit 3b5c96377d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 427 additions and 60 deletions

View File

@ -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

View File

@ -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

View File

@ -12,6 +12,7 @@
#include <type_traits>
#ifdef _WIN32
#include <io.h>
#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<unsigned char>(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;

View File

@ -0,0 +1,83 @@
#include "prx/libc/include/FileLock.hpp"
#ifdef _WIN32
#include <cerrno>
#include <io.h>
#include <map>
#include <memory>
#include <mutex>
#include <windows.h>
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<int, std::shared_ptr<LockState>> g_locks;
std::shared_ptr<LockState> 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<LockState>()).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<HANDLE>(::_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

View File

@ -1137,6 +1137,15 @@ if(BUILD_TESTING)
if(WIN32)
set_tests_properties(guest_kernel_fcntl PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
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:$<TARGET_FILE_DIR:libkernel>")
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})

View File

@ -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 <cerrno>
@ -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<int>(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) {

View File

@ -1,73 +1,16 @@
#include "prx/libkernel/File/include/FileLock.hpp"
#include "prx/libc/include/FileLock.hpp"
#ifdef _WIN32
#include <cerrno>
#include <io.h>
#include <map>
#include <mutex>
#include <windows.h>
namespace {
constexpr int LockShared = 1;
constexpr int LockExclusive = 2;
constexpr int LockNonblocking = 4;
constexpr int LockUnlock = 8;
std::mutex g_mutex;
std::map<int, int> 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<HANDLE>(::_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);
}
}

View File

@ -0,0 +1,306 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <barrier>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <future>
#include <set>
#include <string>
#include <thread>
#include <windows.h>
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<int> 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<void> 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<int>(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();
}

View File

@ -724,3 +724,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.