Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions core/libs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ endforeach()
add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS})

if(BUILD_TESTING)
add_test_executable(guest_posix_scalar_io_tests tests/GuestPosixScalarIo.cpp)
target_include_directories(guest_posix_scalar_io_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_posix_scalar_io_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_posix_scalar_io_tests)
add_test(NAME guest_posix_scalar_io COMMAND guest_posix_scalar_io_tests)
if(WIN32)
set_tests_properties(guest_posix_scalar_io PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
find_package(Python3 QUIET COMPONENTS Interpreter)
if(Python3_Interpreter_FOUND)
add_test(NAME nid_patcher_macho COMMAND ${Python3_EXECUTABLE}
Expand Down
27 changes: 18 additions & 9 deletions core/libs/prx/libkernel/File/src/Open.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,21 @@ static int SceErrorFromErrno(int error) {
return static_cast<int>(0x80020000u | static_cast<unsigned>(guest));
}

static int SceIoErrorFromErrno(int error) {
int guest = SceErrorFromErrno(error) & 0xffff;
if (error == EAGAIN) guest = 35;
#ifdef EDEADLK
else if (error == EDEADLK) guest = 11;
#endif
#ifdef EDQUOT
else if (error == EDQUOT) guest = 69;
#endif
#ifdef EOVERFLOW
else if (error == EOVERFLOW) guest = 84;
#endif
return static_cast<int>(0x80020000u | static_cast<unsigned>(guest));
}

extern "C" {

int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode) {
Expand Down Expand Up @@ -178,9 +193,7 @@ std::int64_t APS5_VABI sceKernelRead(int d, void* buf, std::size_t nbytes) {
if (!destination.Open()) errno = EFAULT;
else if (File::ReadRandomDevice(d, buf, nbytes)) return static_cast<std::int64_t>(nbytes);
auto n = destination.Open() ? NativeRead(d, buf, nbytes) : -1;
if (n < 0) {
throw std::runtime_error(std::string(__func__) + ": read failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
}
if (n < 0) return SceIoErrorFromErrno(errno);
return static_cast<std::int64_t>(n);
}

Expand All @@ -193,9 +206,7 @@ std::int64_t APS5_VABI sceKernelWrite(int d, const void* buf, std::size_t nbytes
throw std::invalid_argument(std::string(__func__) + ": buf is null");
}
auto n = NativeWrite(d, buf, nbytes);
if (n < 0) {
throw std::runtime_error(std::string(__func__) + ": write failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
}
if (n < 0) return SceIoErrorFromErrno(errno);
return static_cast<std::int64_t>(n);
}

Expand All @@ -204,9 +215,7 @@ std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence) {
throw std::invalid_argument(std::string(__func__) + ": invalid whence=" + std::to_string(whence));
}
std::int64_t result = NativeLseek(d, offset, whence);
if (result < 0) {
throw std::runtime_error(std::string(__func__) + ": lseek failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
}
if (result < 0) return SceIoErrorFromErrno(errno);
return result;
}

Expand Down
23 changes: 18 additions & 5 deletions core/libs/prx/libkernel/File/src/Stdio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ static int PosixResult(int result) {
return result < 0 ? PosixFailure(result & 0xffff) : result;
}

static std::int64_t PosixResult(std::int64_t result) {
return result < 0 ? PosixFailure(static_cast<int>(result) & 0xffff) : result;
}

#ifdef _WIN32
static int NativeFstatfs(int descriptor, KernelStatfs* fs) {
if (!File::DirectoryDescriptorPath(descriptor)) {
Expand Down Expand Up @@ -541,7 +545,8 @@ int APS5_VABI sceKernelFtruncate(int d, int64_t length) {
}

int64_t APS5_VABI lseek_nid_postfix(int d, int64_t offset, int whence) {
return static_cast<int64_t>(sceKernelLseek(d, offset, whence));
if (whence < 0 || whence > 2) return PosixFailure(GUEST_EINVAL);
return PosixResult(sceKernelLseek(d, offset, whence));
}

int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode) {
Expand Down Expand Up @@ -616,25 +621,33 @@ int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_

int64_t APS5_VABI read_nid_postfix(int d, void* buf, uint64_t nbytes) {
if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Read(d, buf, static_cast<size_t>(nbytes));
return sceKernelRead(d, buf, static_cast<size_t>(nbytes));
if (buf == nullptr && nbytes != 0) return PosixFailure(GUEST_EFAULT);
char emptyBuffer = 0;
return PosixResult(sceKernelRead(d, buf == nullptr ? &emptyBuffer : buf, static_cast<size_t>(nbytes)));
}

std::int64_t APS5_VABI _read_nid_postfix(int descriptor, void* buffer, std::size_t count) {
if (descriptor >= GuestSockets::FirstDescriptor) return GuestSockets::Read(descriptor, buffer, count);
return sceKernelRead(descriptor, buffer, count);
if (buffer == nullptr && count != 0) return PosixFailure(GUEST_EFAULT);
char emptyBuffer = 0;
return PosixResult(sceKernelRead(descriptor, buffer == nullptr ? &emptyBuffer : buffer, count));
}

int64_t APS5_VABI write_nid_postfix(int d, const char* str, int64_t size) {
if (size < 0) {
APS5_INVALID_ARG_EX;
}
if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Write(d, str, static_cast<std::size_t>(size));
return sceKernelWrite(d, str, static_cast<size_t>(size));
if (str == nullptr && size != 0) return PosixFailure(GUEST_EFAULT);
const char emptyBuffer = 0;
return PosixResult(sceKernelWrite(d, str == nullptr ? &emptyBuffer : str, static_cast<size_t>(size)));
}

std::int64_t APS5_VABI _write_nid_postfix(int descriptor, const void* buffer, std::size_t count) {
if (descriptor >= GuestSockets::FirstDescriptor) return GuestSockets::Write(descriptor, buffer, count);
return sceKernelWrite(descriptor, buffer, count);
if (buffer == nullptr && count != 0) return PosixFailure(GUEST_EFAULT);
const char emptyBuffer = 0;
return PosixResult(sceKernelWrite(descriptor, buffer == nullptr ? &emptyBuffer : buffer, count));
}

int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb) {
Expand Down
179 changes: 179 additions & 0 deletions core/libs/tests/GuestPosixScalarIo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/File/include/FileFlags.hpp"
#include <cerrno>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <filesystem>
#include <fstream>
#include <limits>
#include <stdexcept>
#include <string>
#ifdef _WIN32
#include <io.h>
#else
#include <fcntl.h>
#include <unistd.h>
#endif

extern "C" {
int APS5_VABI open_nid_postfix(const char*, int, int);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI pipe_nid_postfix(int*);
int* APS5_VABI __error_nid_postfix();
std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t);
std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t);
std::int64_t APS5_VABI _read_nid_postfix(int, void*, std::size_t);
std::int64_t APS5_VABI _write_nid_postfix(int, const void*, std::size_t);
std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int);
std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t);
std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t);
std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int);
}

static void Check(bool value, int line) {
if (!value) {
std::fprintf(stderr, "POSIX scalar I/O check failed at line %d (guest errno %d)\n", line, *__error_nid_postfix());
std::abort();
}
}

#define Require(value) Check((value), __LINE__)

#ifndef _WIN32
static constexpr int GuestEagain = 35;
static constexpr int GuestEisdir = 21;
#endif

template <typename Operation>
static void CheckPosixFailure(Operation operation, int expectedError, int line) {
std::int64_t result = 0;
try {
result = operation();
} catch (const std::exception& error) {
std::fprintf(stderr, "POSIX syscall threw instead of returning -1: %s\n", error.what());
Check(false, line);
return;
}
Check(result == -1 && *__error_nid_postfix() == expectedError, line);
}

#define RequirePosixFailure(operation, error) CheckPosixFailure((operation), (error), __LINE__)

int main() {
const auto root = std::filesystem::path("anyps5-posix-scalar-io-" +
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
Require(std::filesystem::create_directory(root));
const auto file = root / "scalar.txt";
{ std::ofstream stream(file, std::ios::binary); stream << "posix"; }
const auto fileName = file.string();
const int descriptor = open_nid_postfix(fileName.c_str(), SCE_KERNEL_O_RDWR, 0);
Require(descriptor >= 0);
int* guestErrno = __error_nid_postfix();
char buffer[3]{};
*guestErrno = EACCES;
Require(lseek_nid_postfix(descriptor, 0, SEEK_SET) == 0);
Require(*guestErrno == EACCES);
*guestErrno = EACCES;
Require(read_nid_postfix(descriptor, buffer, 1) == 1 && buffer[0] == 'p');
Require(*guestErrno == EACCES);
*guestErrno = EACCES;
Require(_read_nid_postfix(descriptor, buffer + 1, 1) == 1 && buffer[1] == 'o');
Require(*guestErrno == EACCES);
constexpr std::int64_t sparseOffset = (std::int64_t{1} << 32) + 17;
*guestErrno = EACCES;
Require(lseek_nid_postfix(descriptor, sparseOffset, SEEK_SET) == sparseOffset);
Require(*guestErrno == EACCES);
*guestErrno = EACCES;
Require(lseek_nid_postfix(descriptor, 0, SEEK_SET) == 0);
Require(*guestErrno == EACCES);
const char payload[] = {'P', 'S'};
*guestErrno = EACCES;
Require(write_nid_postfix(descriptor, payload, 1) == 1);
Require(*guestErrno == EACCES);
*guestErrno = EACCES;
Require(_write_nid_postfix(descriptor, payload + 1, 1) == 1);
Require(*guestErrno == EACCES);
*guestErrno = EACCES;
Require(lseek_nid_postfix(descriptor, 0, SEEK_SET) == 0);
Require(*guestErrno == EACCES);
Require(_read_nid_postfix(descriptor, buffer, 2) == 2 && buffer[0] == 'P' && buffer[1] == 'S');
RequirePosixFailure([&] { return read_nid_postfix(-1, buffer, 1); }, EBADF);
RequirePosixFailure([&] { return _read_nid_postfix(-1, buffer, 1); }, EBADF);
RequirePosixFailure([&] { return write_nid_postfix(-1, payload, 1); }, EBADF);
RequirePosixFailure([&] { return _write_nid_postfix(-1, payload, 1); }, EBADF);
RequirePosixFailure([&] { return lseek_nid_postfix(-1, 0, SEEK_SET); }, EBADF);
RequirePosixFailure([&] { return lseek_nid_postfix(descriptor, 0, -1); }, EINVAL);
RequirePosixFailure([&] { return lseek_nid_postfix(descriptor, 0, 3); }, EINVAL);
RequirePosixFailure([&] { return read_nid_postfix(descriptor, nullptr, 1); }, EFAULT);
RequirePosixFailure([&] { return _read_nid_postfix(descriptor, nullptr, 1); }, EFAULT);
RequirePosixFailure([&] { return write_nid_postfix(descriptor, nullptr, 1); }, EFAULT);
RequirePosixFailure([&] { return _write_nid_postfix(descriptor, nullptr, 1); }, EFAULT);
Require(read_nid_postfix(descriptor, nullptr, 0) == 0);
Require(_read_nid_postfix(descriptor, nullptr, 0) == 0);
Require(write_nid_postfix(descriptor, nullptr, 0) == 0);
Require(_write_nid_postfix(descriptor, nullptr, 0) == 0);
RequirePosixFailure([&] { return read_nid_postfix(-1, nullptr, 0); }, EBADF);
RequirePosixFailure([&] { return _read_nid_postfix(-1, nullptr, 0); }, EBADF);
RequirePosixFailure([&] { return write_nid_postfix(-1, nullptr, 0); }, EBADF);
RequirePosixFailure([&] { return _write_nid_postfix(-1, nullptr, 0); }, EBADF);
#ifdef _WIN32
errno = EACCES;
bool limitDiagnosticPreserved = false;
try {
write_nid_postfix(descriptor, payload, static_cast<std::int64_t>(std::numeric_limits<unsigned int>::max()) + 1);
} catch (const std::runtime_error& error) {
limitDiagnosticPreserved = std::string(error.what()).find("nbytes exceeds platform limit") != std::string::npos;
}
Require(limitDiagnosticPreserved && errno == EACCES);
#endif
char direct[1]{};
Require(sceKernelRead(-1, direct, 1) == static_cast<std::int32_t>(0x80020009u));
Require(sceKernelWrite(-1, direct, 1) == static_cast<std::int32_t>(0x80020009u));
Require(sceKernelLseek(-1, 0, 0) == static_cast<std::int32_t>(0x80020009u));
Require(close_nid_postfix(descriptor) == 0);
const int readOnly = open_nid_postfix(fileName.c_str(), SCE_KERNEL_O_RDONLY, 0);
Require(readOnly >= 0);
RequirePosixFailure([&] { return write_nid_postfix(readOnly, payload, 1); }, EBADF);
RequirePosixFailure([&] { return _write_nid_postfix(readOnly, payload, 1); }, EBADF);
Require(close_nid_postfix(readOnly) == 0);
const int writeOnly = open_nid_postfix(fileName.c_str(), SCE_KERNEL_O_WRONLY, 0);
Require(writeOnly >= 0);
RequirePosixFailure([&] { return read_nid_postfix(writeOnly, buffer, 1); }, EBADF);
RequirePosixFailure([&] { return _read_nid_postfix(writeOnly, buffer, 1); }, EBADF);
Require(close_nid_postfix(writeOnly) == 0);
int descriptors[2] = {-1, -1};
Require(pipe_nid_postfix(descriptors) == 0);
const char pipePayload[] = {'p', 'i', 'p', 'e'};
Require(write_nid_postfix(descriptors[1], pipePayload, sizeof(pipePayload)) == sizeof(pipePayload));
Require(close_nid_postfix(descriptors[1]) == 0);
char pipeBuffer[sizeof(pipePayload)]{};
Require(_read_nid_postfix(descriptors[0], pipeBuffer, sizeof(pipeBuffer)) == sizeof(pipeBuffer));
Require(std::memcmp(pipeBuffer, pipePayload, sizeof(pipePayload)) == 0);
Require(close_nid_postfix(descriptors[0]) == 0);
#ifndef _WIN32
int nonBlockingPipe[2] = {-1, -1};
Require(pipe_nid_postfix(nonBlockingPipe) == 0);
const int pipeFlags = fcntl(nonBlockingPipe[0], F_GETFL);
Require(pipeFlags >= 0 && fcntl(nonBlockingPipe[0], F_SETFL, pipeFlags | O_NONBLOCK) == 0);
RequirePosixFailure([&] { return read_nid_postfix(nonBlockingPipe[0], buffer, 1); }, GuestEagain);
RequirePosixFailure([&] { return _read_nid_postfix(nonBlockingPipe[0], buffer, 1); }, GuestEagain);
Require(sceKernelRead(nonBlockingPipe[0], direct, 1) == static_cast<std::int32_t>(0x80020000u | GuestEagain));
Require(close_nid_postfix(nonBlockingPipe[0]) == 0);
Require(close_nid_postfix(nonBlockingPipe[1]) == 0);
#endif
#ifndef _WIN32
const std::string rootName = root.string();
const int directory = open_nid_postfix(rootName.c_str(), SCE_KERNEL_O_RDONLY, 0);
Require(directory >= 0);
RequirePosixFailure([&] { return read_nid_postfix(directory, buffer, 1); }, GuestEisdir);
RequirePosixFailure([&] { return _read_nid_postfix(directory, buffer, 1); }, GuestEisdir);
Require(close_nid_postfix(directory) == 0);
#endif
Require(std::filesystem::remove_all(root) == 2);
return 0;
}
Loading