feat(libkernel): sceKernelReadv, Writev, Preadv and Pwritev
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
109a3281f8
commit
ab98cd7f4a
|
|
@ -355,6 +355,10 @@ if(BUILD_TESTING)
|
|||
target_include_directories(guest_directory_entry_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_directory_entry_tests PRIVATE libkernel libc)
|
||||
add_test(NAME guest_directory_entries COMMAND guest_directory_entry_tests)
|
||||
add_test_executable(guest_file_vector_tests ${LIBS_INCLUDE_DIR}/tests/GuestFileVector.cpp)
|
||||
target_include_directories(guest_file_vector_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_file_vector_tests PRIVATE libkernel libc)
|
||||
add_test(NAME guest_file_vector COMMAND guest_file_vector_tests)
|
||||
else()
|
||||
add_test_executable(guest_directory_close_tests ${LIBS_INCLUDE_DIR}/tests/GuestDirectoryClose.cpp)
|
||||
target_include_directories(guest_directory_close_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@
|
|||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
struct KernelIovec {
|
||||
void* base;
|
||||
std::size_t length;
|
||||
};
|
||||
|
||||
static constexpr int KERNEL_IOV_MAX = 1024;
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
|
|
@ -119,6 +127,8 @@ static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nb
|
|||
#include <sys/time.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/uio.h>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
static int NativeRmdir(const std::filesystem::path& path) {
|
||||
return ::rmdir(path.c_str());
|
||||
|
|
@ -156,6 +166,12 @@ static std::int64_t NativePread(int descriptor, void* buf, std::size_t nbytes, s
|
|||
static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) {
|
||||
return static_cast<std::int64_t>(::pwrite(descriptor, buf, nbytes, static_cast<off_t>(offset)));
|
||||
}
|
||||
static_assert(sizeof(KernelIovec) == sizeof(struct iovec));
|
||||
static_assert(offsetof(KernelIovec, base) == offsetof(struct iovec, iov_base));
|
||||
static_assert(offsetof(KernelIovec, length) == offsetof(struct iovec, iov_len));
|
||||
static const struct iovec* NativeIovecs(const KernelIovec* iov) {
|
||||
return reinterpret_cast<const struct iovec*>(iov);
|
||||
}
|
||||
#endif
|
||||
|
||||
static constexpr int GUEST_ENOENT = 2;
|
||||
|
|
@ -471,6 +487,22 @@ int64_t APS5_VABI sceKernelPwrite(int d, const void* buf, size_t nbytes, int64_t
|
|||
return pwrite_nid_disambig1_nid_postfix(d, buf, nbytes, offset);
|
||||
}
|
||||
|
||||
int64_t APS5_VABI sceKernelReadv(int, const KernelIovec*, int) {
|
||||
throw std::runtime_error(std::string(__func__) + ": not implemented on Windows");
|
||||
}
|
||||
|
||||
int64_t APS5_VABI sceKernelWritev(int, const KernelIovec*, int) {
|
||||
throw std::runtime_error(std::string(__func__) + ": not implemented on Windows");
|
||||
}
|
||||
|
||||
int64_t APS5_VABI sceKernelPreadv(int, const KernelIovec*, int, int64_t) {
|
||||
throw std::runtime_error(std::string(__func__) + ": not implemented on Windows");
|
||||
}
|
||||
|
||||
int64_t APS5_VABI sceKernelPwritev(int, const KernelIovec*, int, int64_t) {
|
||||
throw std::runtime_error(std::string(__func__) + ": not implemented on Windows");
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int64_t APS5_VABI sceKernelPread(int d, void* buf, size_t nbytes, int64_t offset) {
|
||||
|
|
@ -489,6 +521,49 @@ int64_t APS5_VABI sceKernelPwrite(int d, const void* buf, size_t nbytes, int64_t
|
|||
return result < 0 ? SceErrorFromErrno(errno) : result;
|
||||
}
|
||||
|
||||
static int CheckIovecs(const KernelIovec* iov, int iovcnt) {
|
||||
if (iovcnt < 0 || iovcnt > KERNEL_IOV_MAX) return SceErrorFromErrno(GUEST_EINVAL);
|
||||
if (iov == nullptr && iovcnt != 0) return SceErrorFromErrno(GUEST_EFAULT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool OpenIovecs(const KernelIovec* iov, int iovcnt, std::deque<GuestArena::HostWrite>& destinations) {
|
||||
for (int i = 0; i < iovcnt; ++i) {
|
||||
if (!destinations.emplace_back(iov[i].base, iov[i].length).Open()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t APS5_VABI sceKernelReadv(int d, const KernelIovec* iov, int iovcnt) {
|
||||
if (const int error = CheckIovecs(iov, iovcnt)) return error;
|
||||
std::deque<GuestArena::HostWrite> destinations;
|
||||
if (!OpenIovecs(iov, iovcnt, destinations)) return SceErrorFromErrno(GUEST_EFAULT);
|
||||
const auto result = static_cast<std::int64_t>(::readv(d, NativeIovecs(iov), iovcnt));
|
||||
return result < 0 ? SceErrorFromErrno(errno) : result;
|
||||
}
|
||||
|
||||
int64_t APS5_VABI sceKernelWritev(int d, const KernelIovec* iov, int iovcnt) {
|
||||
if (const int error = CheckIovecs(iov, iovcnt)) return error;
|
||||
const auto result = static_cast<std::int64_t>(::writev(d, NativeIovecs(iov), iovcnt));
|
||||
return result < 0 ? SceErrorFromErrno(errno) : result;
|
||||
}
|
||||
|
||||
int64_t APS5_VABI sceKernelPreadv(int d, const KernelIovec* iov, int iovcnt, int64_t offset) {
|
||||
if (const int error = CheckIovecs(iov, iovcnt)) return error;
|
||||
if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL);
|
||||
std::deque<GuestArena::HostWrite> destinations;
|
||||
if (!OpenIovecs(iov, iovcnt, destinations)) return SceErrorFromErrno(GUEST_EFAULT);
|
||||
const auto result = static_cast<std::int64_t>(::preadv(d, NativeIovecs(iov), iovcnt, static_cast<off_t>(offset)));
|
||||
return result < 0 ? SceErrorFromErrno(errno) : result;
|
||||
}
|
||||
|
||||
int64_t APS5_VABI sceKernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset) {
|
||||
if (const int error = CheckIovecs(iov, iovcnt)) return error;
|
||||
if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL);
|
||||
const auto result = static_cast<std::int64_t>(::pwritev(d, NativeIovecs(iov), iovcnt, static_cast<off_t>(offset)));
|
||||
return result < 0 ? SceErrorFromErrno(errno) : result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int APS5_VABI sceKernelRename(const char* from, const char* to) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libkernel/File/include/FileFlags.hpp"
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
|
||||
struct GuestIovec {
|
||||
void* base;
|
||||
std::size_t length;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t);
|
||||
int APS5_VABI sceKernelClose(int);
|
||||
std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t);
|
||||
int APS5_VABI sceKernelLseek(int, std::int64_t, int);
|
||||
std::int64_t APS5_VABI sceKernelReadv(int, const GuestIovec*, int);
|
||||
std::int64_t APS5_VABI sceKernelWritev(int, const GuestIovec*, int);
|
||||
std::int64_t APS5_VABI sceKernelPreadv(int, const GuestIovec*, int, std::int64_t);
|
||||
std::int64_t APS5_VABI sceKernelPwritev(int, const GuestIovec*, int, std::int64_t);
|
||||
}
|
||||
|
||||
static void Check(bool value, int line) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "File vector check failed at line %d\n", line);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
#define Require(value) Check((value), __LINE__)
|
||||
|
||||
static constexpr std::int64_t ErrorEbadf = static_cast<int>(0x80020009u);
|
||||
static constexpr std::int64_t ErrorEfault = static_cast<int>(0x8002000Eu);
|
||||
static constexpr std::int64_t ErrorEinval = static_cast<int>(0x80020016u);
|
||||
|
||||
static std::string Contents(const std::filesystem::path& path) {
|
||||
std::ifstream stream(path, std::ios::binary);
|
||||
return std::string(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
int main() {
|
||||
const auto root = std::filesystem::path("anyps5-file-vector-test-" +
|
||||
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
Require(std::filesystem::create_directory(root));
|
||||
const auto path = root / "data.bin";
|
||||
{ std::ofstream stream(path, std::ios::binary); stream << "0123456789"; }
|
||||
|
||||
const int file = sceKernelOpen(path.string().c_str(), SCE_KERNEL_O_RDWR, 0);
|
||||
Require(file >= 0);
|
||||
|
||||
char first[3] = {};
|
||||
char second[4] = {};
|
||||
GuestIovec reads[2] = {{first, sizeof(first)}, {second, sizeof(second)}};
|
||||
Require(sceKernelReadv(file, reads, 2) == 7);
|
||||
Require(std::memcmp(first, "012", 3) == 0 && std::memcmp(second, "3456", 4) == 0);
|
||||
Require(sceKernelLseek(file, 0, 1) == 7);
|
||||
Require(sceKernelReadv(file, reads, 2) == 3);
|
||||
Require(std::memcmp(first, "789", 3) == 0);
|
||||
Require(sceKernelReadv(file, reads, 2) == 0);
|
||||
|
||||
Require(sceKernelPreadv(file, reads, 2, 1) == 7);
|
||||
Require(std::memcmp(first, "123", 3) == 0 && std::memcmp(second, "4567", 4) == 0);
|
||||
Require(sceKernelLseek(file, 0, 1) == 10);
|
||||
Require(sceKernelPreadv(file, reads, 2, 8) == 2);
|
||||
Require(std::memcmp(first, "89", 2) == 0);
|
||||
|
||||
char ab[] = "AB";
|
||||
char cde[] = "CDE";
|
||||
GuestIovec writes[2] = {{ab, 2}, {cde, 3}};
|
||||
Require(sceKernelPwritev(file, writes, 2, 2) == 5);
|
||||
Require(sceKernelLseek(file, 0, 1) == 10);
|
||||
Require(Contents(path) == "01ABCDE789");
|
||||
Require(sceKernelLseek(file, 8, 0) == 8);
|
||||
Require(sceKernelWritev(file, writes, 2) == 5);
|
||||
Require(sceKernelLseek(file, 0, 1) == 13);
|
||||
Require(Contents(path) == "01ABCDE7ABCDE");
|
||||
|
||||
GuestIovec empty[1] = {{nullptr, 0}};
|
||||
Require(sceKernelReadv(file, empty, 1) == 0);
|
||||
Require(sceKernelReadv(file, reads, 0) == 0);
|
||||
Require(sceKernelWritev(file, nullptr, 0) == 0);
|
||||
|
||||
Require(sceKernelReadv(file, reads, -1) == ErrorEinval);
|
||||
Require(sceKernelWritev(file, writes, -1) == ErrorEinval);
|
||||
Require(sceKernelPreadv(file, reads, -1, 0) == ErrorEinval);
|
||||
Require(sceKernelPwritev(file, writes, -1, 0) == ErrorEinval);
|
||||
Require(sceKernelReadv(file, reads, 1025) == ErrorEinval);
|
||||
Require(sceKernelWritev(file, writes, 1025) == ErrorEinval);
|
||||
Require(sceKernelReadv(file, nullptr, 1) == ErrorEfault);
|
||||
Require(sceKernelWritev(file, nullptr, 1) == ErrorEfault);
|
||||
Require(sceKernelPreadv(file, nullptr, 1, 0) == ErrorEfault);
|
||||
Require(sceKernelPwritev(file, nullptr, 1, 0) == ErrorEfault);
|
||||
Require(sceKernelPreadv(file, reads, 2, -1) == ErrorEinval);
|
||||
Require(sceKernelPwritev(file, writes, 2, -1) == ErrorEinval);
|
||||
Require(Contents(path) == "01ABCDE7ABCDE");
|
||||
|
||||
Require(sceKernelClose(file) == 0);
|
||||
Require(sceKernelReadv(file, reads, 2) == ErrorEbadf);
|
||||
Require(sceKernelWritev(file, writes, 2) == ErrorEbadf);
|
||||
Require(sceKernelPreadv(file, reads, 2, 0) == ErrorEbadf);
|
||||
Require(sceKernelPwritev(file, writes, 2, 0) == ErrorEbadf);
|
||||
|
||||
std::filesystem::remove_all(root);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -120,6 +120,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature
|
||||
- [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf`
|
||||
- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown.
|
||||
- [sceKernelReadv / sceKernelWritev / sceKernelPreadv / sceKernelPwritev](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) are implemented on Linux only; on Windows they throw
|
||||
- [_sceUlobjmgrRegisterObject](../../core/libs/prx/ulobjmgr/Export.cpp) (ulobjmgr) - parameters taken from shadPS4 (PS4): a nonzero 64-bit object, a nonzero 32-bit kind and a 32-bit id output; the meaning of the first two and the PS5 signature are unverified. The id bound of `_sceUlobjmgrUnregisterObject` (below 0x4000) and the raw `EINVAL` (22) return also come from shadPS4
|
||||
- [sceShareGetCurrentStatus](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - validation (feature flag non-zero, status non-null) and the all-zero 16-byte status taken from the [KytyPS5](https://github.com/KytyPS5/KytyPS5) reimplementation, not confirmed on a PS5 title; the meaning of the recording status values is unknown
|
||||
- [sceAgcDcbSetZPassPredicationEnableGetSize, sceAgcDcbSetPredicationDisableGetSize, sceAgcDcbSetBoolPredicationEnableGetSize](../../core/libs/prx/libSceAgc/DcbState/src/Predication.cpp) (libSceAgc) - the builders are inline in the SDK and not exported; the size is assumed to be that of the SET_PREDICATION packet `sceAgcDcbSetPredication` writes (16 bytes)
|
||||
|
|
|
|||
Loading…
Reference in New Issue