Merge pull request #4516 from Ondra97/codex/file-lock-lifetime
fix(libs): share Windows flock state between duplicated descriptors
This commit is contained in:
commit
3b5c96377d
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue