diff --git a/core/libs/prx/libkernel/Aio/src/Aio.cpp b/core/libs/prx/libkernel/Aio/src/Aio.cpp index 6e2b35c0a..d8e8087d0 100644 --- a/core/libs/prx/libkernel/Aio/src/Aio.cpp +++ b/core/libs/prx/libkernel/Aio/src/Aio.cpp @@ -10,13 +10,10 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestArena.hpp" +#include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/KernelErrors.hpp" -#ifdef _WIN32 -#include -#include -#include -#else +#ifndef _WIN32 #include #include #endif @@ -54,35 +51,6 @@ void SetState(std::int32_t id, std::int32_t state) { g_states[id] = state; } -#ifdef _WIN32 -std::int64_t NativePositioned(std::int32_t fd, void* buf, std::size_t nbyte, std::int64_t offset, bool write, const char* name) { - if (nbyte > static_cast(std::numeric_limits::max())) { - throw std::runtime_error(std::string(name) + ": nbytes exceeds platform limit"); - } - if (offset < 0) { - errno = EINVAL; - return -1; - } - const auto handle = reinterpret_cast(::_get_osfhandle(fd)); - if (handle == INVALID_HANDLE_VALUE) { - errno = EBADF; - return -1; - } - OVERLAPPED overlapped{}; - overlapped.Offset = static_cast(offset); - overlapped.OffsetHigh = static_cast(static_cast(offset) >> 32u); - DWORD done = 0; - const BOOL ok = write ? ::WriteFile(handle, buf, static_cast(nbyte), &done, &overlapped) - : ::ReadFile(handle, buf, static_cast(nbyte), &done, &overlapped); - if (!ok) { - if (!write && ::GetLastError() == ERROR_HANDLE_EOF) return 0; - errno = EIO; - return -1; - } - return done; -} -#endif - std::int64_t NativePread(std::int32_t fd, void* buf, std::size_t nbyte, std::int64_t offset) { const GuestArena::HostWrite destination(buf, nbyte); if (!destination.Open()) { @@ -90,7 +58,11 @@ std::int64_t NativePread(std::int32_t fd, void* buf, std::size_t nbyte, std::int return -1; } #ifdef _WIN32 - return NativePositioned(fd, buf, nbyte, offset, false, "sceKernelAioSubmitReadCommands"); + if (offset < 0) { + errno = EINVAL; + return -1; + } + return NativePositioned_nid_no_patch(fd, buf, nbyte, offset, false); #else return static_cast(::pread(fd, buf, nbyte, static_cast(offset))); #endif @@ -98,7 +70,11 @@ std::int64_t NativePread(std::int32_t fd, void* buf, std::size_t nbyte, std::int std::int64_t NativePwrite(std::int32_t fd, const void* buf, std::size_t nbyte, std::int64_t offset) { #ifdef _WIN32 - return NativePositioned(fd, const_cast(buf), nbyte, offset, true, "sceKernelAioSubmitWriteCommands"); + if (offset < 0) { + errno = EINVAL; + return -1; + } + return NativePositioned_nid_no_patch(fd, const_cast(buf), nbyte, offset, true); #else return static_cast(::pwrite(fd, buf, nbyte, static_cast(offset))); #endif diff --git a/core/libs/prx/libkernel/File/include/File.hpp b/core/libs/prx/libkernel/File/include/File.hpp index cb3f099ff..5b067ab77 100644 --- a/core/libs/prx/libkernel/File/include/File.hpp +++ b/core/libs/prx/libkernel/File/include/File.hpp @@ -16,6 +16,9 @@ std::int64_t APS5_VABI sceKernelWrite(int d, const void* buf, std::size_t nbytes std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence); int APS5_VABI sceKernelStat(const char* path, FileStat* sb); int APS5_VABI sceKernelUnlink(const char* path); +#ifdef _WIN32 +std::int64_t NativePositioned_nid_no_patch(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset, bool write); +#endif } diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 162c77511..a42cc5fa3 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -97,7 +97,7 @@ static int NativeFlock(int descriptor, int operation) { if (operation & 4) flags |= LOCKFILE_FAIL_IMMEDIATELY; return ::LockFileEx(handle, flags, 0, MAXDWORD, MAXDWORD, &overlapped) ? 0 : -1; } -static std::int64_t NativePositioned(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset, bool write) { +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::numeric_limits::max())) { throw std::runtime_error("NativePositioned: nbytes exceeds platform limit"); } @@ -139,10 +139,10 @@ static bool NativeIsDisk(int descriptor) { return handle != INVALID_HANDLE_VALUE && ::GetFileType(handle) == FILE_TYPE_DISK; } static std::int64_t NativePread(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset) { - return NativePositioned(descriptor, buf, nbytes, offset, false); + return NativePositioned_nid_no_patch(descriptor, buf, nbytes, offset, false); } static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) { - return NativePositioned(descriptor, const_cast(buf), nbytes, offset, true); + return NativePositioned_nid_no_patch(descriptor, const_cast(buf), nbytes, offset, true); } #else #include @@ -560,7 +560,7 @@ static std::int64_t TransferIovecs(int d, const KernelIovec* iov, int iovcnt, co if (!write && !OpenIovecs(iov, iovcnt, destinations)) return SceErrorFromErrno(GUEST_EFAULT); if (total == 0) { char none = 0; - const auto result = offset != nullptr ? NativePositioned(d, &none, 0, *offset, write) : NativeTransfer(d, &none, 0, write); + const auto result = offset != nullptr ? NativePositioned_nid_no_patch(d, &none, 0, *offset, write) : NativeTransfer(d, &none, 0, write); return result < 0 ? SceErrorFromErrno(errno) : 0; } const bool whole = write || offset != nullptr || NativeIsDisk(d); @@ -569,7 +569,7 @@ static std::int64_t TransferIovecs(int d, const KernelIovec* iov, int iovcnt, co auto* base = static_cast(iov[i].base); for (std::size_t position = 0; position < iov[i].length;) { const auto chunk = std::min(iov[i].length - position, std::numeric_limits::max()); - const auto result = offset != nullptr ? NativePositioned(d, base + position, chunk, *offset + done, write) : NativeTransfer(d, base + position, chunk, write); + const auto result = offset != nullptr ? NativePositioned_nid_no_patch(d, base + position, chunk, *offset + done, write) : NativeTransfer(d, base + position, chunk, write); if (result < 0) return done != 0 ? done : SceErrorFromErrno(errno); done += result; position += static_cast(result); diff --git a/core/libs/tests/GuestAio.cpp b/core/libs/tests/GuestAio.cpp index 2826901b5..b1e1eda10 100644 --- a/core/libs/tests/GuestAio.cpp +++ b/core/libs/tests/GuestAio.cpp @@ -17,6 +17,7 @@ extern "C" { int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode); int APS5_VABI sceKernelClose(int d); +std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence); int APS5_VABI sceKernelAioDeleteRequest(std::int32_t id, std::int32_t* ret); int APS5_VABI sceKernelAioInitializeImpl(void* param, std::int32_t size); void APS5_VABI sceKernelAioInitializeParam(void* param); @@ -99,6 +100,7 @@ int main() { Check(sceKernelAioDeleteRequest(id, nullptr) == SCE_KERNEL_ERROR_EFAULT); const int batchFd = sceKernelOpen(file.string().c_str(), SCE_KERNEL_O_RDWR, 0); Check(batchFd >= 0); + Check(sceKernelLseek(batchFd, 2, 0) == 2); std::array patch = {'W', 'X', 'Y', 'Z'}; KernelAioResult patchResult{-1, 0}; KernelAioResult badResult{-1, 0}; @@ -108,6 +110,7 @@ int main() { }; std::int32_t writeIds[2] = {0, 0}; Check(sceKernelAioSubmitWriteCommandsMultiple(writeBatch, 2, 0, writeIds) == 0); + Check(sceKernelLseek(batchFd, 0, 1) == 2); Check(writeIds[0] > 0 && writeIds[1] > 0 && writeIds[0] != writeIds[1]); Check(patchResult.state == 3 && patchResult.return_value == 4); Check(badResult.state == 4 && badResult.return_value == SCE_KERNEL_ERROR_EBADF); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 700549634..6a916577f 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -258,6 +258,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported - [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries - [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 - [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 - [sceKernelSync](../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows flushes only the host files and directories recorded through `RecordWrittenPath_nid_no_patch` (the libc, libkernel, save data and app content file functions) and their changed parent directories. A host write in a library that does not record its path is not flushed by `sceKernelSync` - [sceKernelSync](../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since `FlushFileBuffers` needs a writable handle. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way