fix(libkernel): use per-descriptor locks for positioned transfers and seeks
This commit is contained in:
parent
3915f6dc09
commit
3cab3c05ec
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
|
@ -27,6 +28,8 @@ bool DescriptorIsOpen_nid_no_patch(int descriptor);
|
|||
|
||||
namespace File {
|
||||
int HostStandardError();
|
||||
[[nodiscard]] std::unique_lock<std::mutex> LockPositionedTransfer(int descriptor);
|
||||
void SharePositionedTransfer(int from, int to);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -564,6 +564,7 @@ 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)};
|
||||
File::SharePositionedTransfer(from, to);
|
||||
#ifdef _WIN32
|
||||
GuestFileLockShare_nid_no_patch(from, to);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ static int NativeOpen(const std::filesystem::path& p, int nativeFlags, std::uint
|
|||
return descriptor;
|
||||
}
|
||||
static std::int64_t NativeLseek(int fd, std::int64_t offset, int whence) {
|
||||
const auto positioned = File::LockPositionedTransfer(fd);
|
||||
const auto result = File::SeekSharedMemory_nid_no_patch(fd, offset, whence);
|
||||
if (result != -2) return result;
|
||||
const auto position = ::_lseeki64(fd, offset, whence);
|
||||
|
|
@ -55,6 +56,7 @@ static int NativeRead(int fd, void* buf, std::size_t n) {
|
|||
if (n > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
|
||||
throw std::runtime_error("sceKernelRead: nbytes exceeds platform limit");
|
||||
}
|
||||
const auto positioned = File::LockPositionedTransfer(fd);
|
||||
const int result = ::_read(fd, buf, static_cast<unsigned int>(n));
|
||||
if (result < 0 && _doserrno == ERROR_NO_DATA) errno = EAGAIN;
|
||||
return result;
|
||||
|
|
@ -63,6 +65,7 @@ static int NativeWrite(int fd, const void* buf, std::size_t n) {
|
|||
if (n > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
|
||||
throw std::runtime_error("sceKernelWrite: nbytes exceeds platform limit");
|
||||
}
|
||||
const auto positioned = File::LockPositionedTransfer(fd);
|
||||
const auto shared = File::WriteSharedMemory_nid_no_patch(fd, buf, n);
|
||||
if (shared != -2) return static_cast<int>(shared);
|
||||
const int result = ::_write(fd, buf, static_cast<unsigned int>(n));
|
||||
|
|
|
|||
|
|
@ -26,10 +26,12 @@
|
|||
#include <cstdarg>
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -171,7 +173,27 @@ static int NativeSync(int descriptor) {
|
|||
static int NativeFlock(int descriptor, int operation) {
|
||||
return File::Flock(descriptor, operation);
|
||||
}
|
||||
static std::mutex g_positionedMutex;
|
||||
struct PositionedLock {
|
||||
std::mutex mutex;
|
||||
};
|
||||
std::mutex g_positionedTableLock;
|
||||
std::unordered_map<int, std::shared_ptr<PositionedLock>> g_positionedLocks;
|
||||
std::unique_lock<std::mutex> File::LockPositionedTransfer(int descriptor) {
|
||||
std::shared_ptr<PositionedLock> entry;
|
||||
{
|
||||
std::lock_guard table(g_positionedTableLock);
|
||||
auto& slot = g_positionedLocks[descriptor];
|
||||
if (!slot) slot = std::make_shared<PositionedLock>();
|
||||
entry = slot;
|
||||
}
|
||||
return std::unique_lock<std::mutex>(entry->mutex);
|
||||
}
|
||||
void File::SharePositionedTransfer(int from, int to) {
|
||||
std::lock_guard table(g_positionedTableLock);
|
||||
auto& source = g_positionedLocks[from];
|
||||
if (!source) source = std::make_shared<PositionedLock>();
|
||||
g_positionedLocks[to] = source;
|
||||
}
|
||||
std::int64_t NativePositioned_nid_no_patch(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset, bool write) {
|
||||
if (nbytes > static_cast<std::size_t>(std::numeric_limits<DWORD>::max())) {
|
||||
throw std::runtime_error("NativePositioned: nbytes exceeds platform limit");
|
||||
|
|
@ -185,7 +207,7 @@ std::int64_t NativePositioned_nid_no_patch(int descriptor, void* buf, std::size_
|
|||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
std::lock_guard lock(g_positionedMutex);
|
||||
const auto positioned = File::LockPositionedTransfer(descriptor);
|
||||
LARGE_INTEGER position{};
|
||||
if (::GetFileType(handle) != FILE_TYPE_DISK || !::SetFilePointerEx(handle, LARGE_INTEGER{}, &position, FILE_CURRENT)) {
|
||||
errno = ESPIPE;
|
||||
|
|
@ -212,7 +234,7 @@ static std::int64_t NativeTransfer(int descriptor, void* buf, std::size_t nbytes
|
|||
if (nbytes > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
|
||||
throw std::runtime_error("NativeTransfer: nbytes exceeds platform limit");
|
||||
}
|
||||
std::lock_guard lock(g_positionedMutex);
|
||||
const auto positioned = File::LockPositionedTransfer(descriptor);
|
||||
if (write) {
|
||||
const auto result = File::WriteSharedMemory_nid_no_patch(descriptor, buf, nbytes);
|
||||
if (result != -2) return result;
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@
|
|||
- [sceKernelAddReadEvent and sceKernelAddWriteEvent](../../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel): a write event reports the socket's send buffer size (`SO_SNDBUF`) in `data`, not the free space left in it, as Windows has no query for that space. Only guest sockets are supported; other descriptors and a low watermark above 1 throw
|
||||
- [close](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) and [setsockopt](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) reach a [libSceNet](../../../core/libs/prx/libSceNet/Export.cpp) socket through hooks libSceNet registers, as `select` polls them; the other libkernel socket calls do not. libSceNet numbers its sockets from 32 in its own table, so a CRT file descriptor and a socket can share a number; `close` then closes the socket. On the console both are one kernel descriptor space
|
||||
- [sceKernelPread, sceKernelPwrite, sceKernelPreadv, sceKernelPwritev](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on Windows transfer at the given offset with an overlapped `ReadFile`/`WriteFile` and restore the descriptor's file pointer afterwards, so a concurrent `read`/`write` on the same descriptor can see the moved pointer in between, unlike on the console. Handles that are not disk files return `ESPIPE`. `readv` on a pipe or console handle stops after the first iovec read, a short read the console is assumed to allow; each iovec is transferred separately, so a failure after the first transfer returns the bytes already done
|
||||
- [sceKernelAioSubmitReadCommands, sceKernelAioSubmitWriteCommands and their Multiple variants](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) on Windows transfer through the same positioned path as `sceKernelPread`/`sceKernelPwrite` and restore the descriptor's file pointer afterwards, so a concurrent `read`/`write` on the same descriptor can see the moved pointer in between, unlike on the console
|
||||
- [sceKernelAioSubmitReadCommands, sceKernelAioSubmitWriteCommands and their Multiple variants](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) on Windows transfer through the same positioned path as `sceKernelPread`/`sceKernelPwrite` and restore the descriptor's file pointer afterwards; positioned transfers, plain transfers and seeks of one descriptor are serialized per descriptor (shared across duplicated descriptors), so a concurrent transfer observes a stable file pointer
|
||||
- [sceKernelAioSetParam](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) writes the five values into the 20-byte scheduling struct the first argument points to, following the declarations in [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json), [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Kernel/KernelAio.cs) and [ampr_emu](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/ps5/include/sce/_fs.h). Whether the console checks the values is unknown, so a null struct, a scheduling window or delayed count limit outside 0..128, a split flag other than 0 or 1 and a split size or chunk size above 0x1000000 (SharpProspero's maximums) throw. The scheduling settings are not used: `sceKernelAioInitializeImpl` ignores the struct and requests run synchronously on submit
|
||||
- [sceKernelGetFsSandboxRandomWord](../../../core/libs/prx/libkernel/System/src/Sandbox.cpp) (libkernel) returns a fixed directory name, `gkbHtXc3Wq`. On the console the name is the process's randomized path (syscall 602, `randomized_path`), and the sandbox exposes system files under it, such as `common/lib` and `common/cert`. Nothing is mounted under the name here, so `/gkbHtXc3Wq/common/...` resolves under the guest root like any other path and does not exist
|
||||
- [getcontext and setcontext](../../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel) and [makecontext](../../../core/libs/prx/libSceLibcInternal/UserContext.cpp) (libSceLibcInternal) use FreeBSD 11's amd64 `ucontext_t` (`mcontext` at 0x10, `mc_len` 800, `uc_link` at 0x330, `uc_stack` at 0x338, 0x370 bytes), the layout homebrew built against FreeBSD's headers passes: PPSA99039's OpenSSL `ASYNC` (`crypto/async/arch/async_posix.c`) captures into a 0x370-byte stack buffer. `getcontext` writes the 0x330 bytes before `uc_link`, as FreeBSD's `UC_COPY_SIZE`. The console's own layout for these functions is not verified; the exception handler's context is a different one (see `sceKernelRaiseException`)
|
||||
|
|
|
|||
Loading…
Reference in New Issue