Merge pull request #56 from oneandonlydean/feat/linux-pread-getdents
feat(libkernel): pread, pwrite, getdents and getdirentries on linux - **pread/pwrite:** use the existing `NativePread`/`NativePwrite` and don't move the file offset. A null buffer with a non-zero size returns `EFAULT`, a negative offset `EINVAL`, and host failures go through `SceErrorFromErrno`. - **getdirentries:** - reads host `getdents64` records and converts them to the FreeBSD `struct dirent` layout the guest expects (`d_fileno` u32, `d_reclen` u16, `d_type` u8, `d_namlen` u8, NUL-terminated name, record padded to 4 bytes); - `*basep` gets the directory position from before the read; - when a record doesn't fit in the guest buffer, the descriptor is moved with `lseek` back to that entry's cookie, so the next call resumes there; - if not even the first record fits, the call returns `EINVAL` without consuming anything, and a name longer than 255 bytes returns `ENAMETOOLONG`. - **getdents** is `getdirentries` without `basep`. - Adds the guest `EFAULT`, `EINVAL` and `ENAMETOOLONG` constants.
This commit is contained in:
commit
73d014ed83
|
|
@ -373,6 +373,12 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_memory PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
if(NOT WIN32)
|
||||
add_executable(guest_directory_entry_tests tests/GuestDirectoryEntries.cpp)
|
||||
target_include_directories(guest_directory_entry_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_directory_entry_tests PRIVATE libkernel libc)
|
||||
add_test(NAME guest_directory_entries COMMAND guest_directory_entry_tests)
|
||||
endif()
|
||||
add_executable(guest_search_tests tests/GuestSearch.cpp)
|
||||
target_include_directories(guest_search_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_search_tests PRIVATE libc)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "prx/libkernel/File/include/NativeStat.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <cstdarg>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
|
|
@ -78,6 +79,9 @@ static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nb
|
|||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/file.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <vector>
|
||||
static int NativeRmdir(const std::filesystem::path& path) {
|
||||
return ::rmdir(path.c_str());
|
||||
}
|
||||
|
|
@ -103,7 +107,10 @@ static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nb
|
|||
|
||||
static constexpr int GUEST_ENOENT = 2;
|
||||
static constexpr int GUEST_EIO = 5;
|
||||
static constexpr int GUEST_EFAULT = 14;
|
||||
static constexpr int GUEST_EEXIST = 17;
|
||||
static constexpr int GUEST_EINVAL = 22;
|
||||
static constexpr int GUEST_ENAMETOOLONG = 63;
|
||||
static constexpr int GUEST_ENOTDIR = 20;
|
||||
static constexpr int GUEST_ENOTEMPTY = 66;
|
||||
|
||||
|
|
@ -283,6 +290,8 @@ int APS5_VABI sceKernelFsync(int fd) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
int APS5_VABI sceKernelGetdents(int fd, char* buf, int nbytes) {
|
||||
(void)fd;
|
||||
(void)buf;
|
||||
|
|
@ -300,6 +309,54 @@ int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* bas
|
|||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* basep) {
|
||||
constexpr std::size_t GuestHeaderBytes = 8;
|
||||
constexpr std::size_t GuestMaxName = 255;
|
||||
if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT);
|
||||
if (nbytes <= 0) return SceErrorFromErrno(GUEST_EINVAL);
|
||||
const off_t base = ::lseek(fd, 0, SEEK_CUR);
|
||||
if (base < 0) return SceErrorFromErrno(errno);
|
||||
if (basep != nullptr) *basep = static_cast<int64_t>(base);
|
||||
std::vector<char> host(static_cast<std::size_t>(nbytes) < 4096 ? 4096 : static_cast<std::size_t>(nbytes));
|
||||
const auto read = ::syscall(SYS_getdents64, fd, host.data(), host.size());
|
||||
if (read < 0) return SceErrorFromErrno(errno);
|
||||
std::size_t written = 0;
|
||||
off_t resume = base;
|
||||
for (long offset = 0; offset < read;) {
|
||||
const auto* entry = reinterpret_cast<const struct dirent64*>(host.data() + offset);
|
||||
const auto nameLength = std::strlen(entry->d_name);
|
||||
const auto record = (GuestHeaderBytes + nameLength + 1 + 3) & ~std::size_t{3};
|
||||
if (nameLength > GuestMaxName || written + record > static_cast<std::size_t>(nbytes)) {
|
||||
if (::lseek(fd, resume, SEEK_SET) < 0) return SceErrorFromErrno(errno);
|
||||
if (written != 0) return static_cast<int>(written);
|
||||
return nameLength > GuestMaxName ? static_cast<int>(0x80020000u | GUEST_ENAMETOOLONG) : SceErrorFromErrno(GUEST_EINVAL);
|
||||
}
|
||||
char* out = buf + written;
|
||||
std::memset(out, 0, record);
|
||||
const auto fileNumber = static_cast<std::uint32_t>(entry->d_ino);
|
||||
const auto recordLength = static_cast<std::uint16_t>(record);
|
||||
const auto nameBytes = static_cast<std::uint8_t>(nameLength);
|
||||
std::memcpy(out, &fileNumber, sizeof(fileNumber));
|
||||
std::memcpy(out + 4, &recordLength, sizeof(recordLength));
|
||||
out[6] = static_cast<char>(entry->d_type);
|
||||
out[7] = static_cast<char>(nameBytes);
|
||||
std::memcpy(out + GuestHeaderBytes, entry->d_name, nameLength);
|
||||
written += record;
|
||||
resume = entry->d_off;
|
||||
offset += entry->d_reclen;
|
||||
}
|
||||
if (::lseek(fd, resume, SEEK_SET) < 0) return SceErrorFromErrno(errno);
|
||||
return static_cast<int>(written);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelGetdents(int fd, char* buf, int nbytes) {
|
||||
return sceKernelGetdirentries(fd, buf, nbytes, nullptr);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int APS5_VABI sceKernelMkdir(const char* path, uint16_t mode) {
|
||||
(void)mode;
|
||||
if (path == nullptr) throw std::invalid_argument("sceKernelMkdir: path is null");
|
||||
|
|
@ -311,6 +368,8 @@ int APS5_VABI sceKernelMkdir(const char* path, uint16_t mode) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
int64_t APS5_VABI sceKernelPread(int d, void* buf, size_t nbytes, int64_t offset) {
|
||||
(void)d;
|
||||
(void)buf;
|
||||
|
|
@ -329,6 +388,24 @@ int64_t APS5_VABI sceKernelPwrite(int d, const void* buf, size_t nbytes, int64_t
|
|||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int64_t APS5_VABI sceKernelPread(int d, void* buf, size_t nbytes, int64_t offset) {
|
||||
if (buf == nullptr && nbytes != 0) return SceErrorFromErrno(GUEST_EFAULT);
|
||||
if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL);
|
||||
const auto result = NativePread(d, buf, nbytes, offset);
|
||||
return result < 0 ? SceErrorFromErrno(errno) : result;
|
||||
}
|
||||
|
||||
int64_t APS5_VABI sceKernelPwrite(int d, const void* buf, size_t nbytes, int64_t offset) {
|
||||
if (buf == nullptr && nbytes != 0) return SceErrorFromErrno(GUEST_EFAULT);
|
||||
if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL);
|
||||
const auto result = NativePwrite(d, buf, nbytes, offset);
|
||||
return result < 0 ? SceErrorFromErrno(errno) : result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int APS5_VABI sceKernelRename(const char* from, const char* to) {
|
||||
if (from == nullptr || to == nullptr) throw std::invalid_argument("sceKernelRename: path is null");
|
||||
const auto source = ResolvePath_nid_no_patch(from);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libkernel/File/include/FileFlags.hpp"
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
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);
|
||||
int APS5_VABI sceKernelGetdents(int, char*, int);
|
||||
int APS5_VABI sceKernelGetdirentries(int, char*, int, std::int64_t*);
|
||||
std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t);
|
||||
std::int64_t APS5_VABI sceKernelPwrite(int, const void*, std::size_t, std::int64_t);
|
||||
}
|
||||
|
||||
static void Check(bool value, int line) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "Directory entry check failed at line %d\n", line);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
#define Require(value) Check((value), __LINE__)
|
||||
|
||||
static constexpr int ErrorEinval = static_cast<int>(0x80020016u);
|
||||
static constexpr int ErrorEfault = static_cast<int>(0x8002000Eu);
|
||||
static constexpr int ErrorEbadf = static_cast<int>(0x80020009u);
|
||||
|
||||
struct Entry {
|
||||
std::uint8_t type;
|
||||
std::uint16_t recordLength;
|
||||
};
|
||||
|
||||
static int Collect(const char* buffer, int bytes, std::map<std::string, Entry>& entries) {
|
||||
int records = 0;
|
||||
for (int offset = 0; offset < bytes; ++records) {
|
||||
std::uint16_t recordLength = 0;
|
||||
std::memcpy(&recordLength, buffer + offset + 4, sizeof(recordLength));
|
||||
const auto nameLength = static_cast<std::uint8_t>(buffer[offset + 7]);
|
||||
const std::string name(buffer + offset + 8);
|
||||
Require(name.size() == nameLength);
|
||||
Require(recordLength == ((8 + nameLength + 1 + 3) & ~3));
|
||||
Require(offset + recordLength <= bytes);
|
||||
Require(entries.emplace(name, Entry{static_cast<std::uint8_t>(buffer[offset + 6]), recordLength}).second);
|
||||
offset += recordLength;
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
int main() {
|
||||
const auto root = std::filesystem::path("anyps5-directory-entries-test-" +
|
||||
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
Require(std::filesystem::create_directory(root));
|
||||
const std::string longName = "a-much-longer-entry-name-0123456789";
|
||||
{ std::ofstream stream(root / "data.bin", std::ios::binary); stream << "0123456789"; }
|
||||
{ std::ofstream stream(root / "b"); }
|
||||
{ std::ofstream stream(root / longName); }
|
||||
Require(std::filesystem::create_directory(root / "sub"));
|
||||
|
||||
const int directory = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
|
||||
Require(directory >= 0);
|
||||
std::array<char, 48> small{};
|
||||
std::map<std::string, Entry> entries;
|
||||
int calls = 0;
|
||||
std::int64_t previousBase = -1;
|
||||
for (;;) {
|
||||
std::int64_t base = -1;
|
||||
const int read = sceKernelGetdirentries(directory, small.data(), static_cast<int>(small.size()), &base);
|
||||
Require(read >= 0 && read <= static_cast<int>(small.size()));
|
||||
Require(base >= 0 && base != previousBase);
|
||||
if (calls == 0) Require(base == 0);
|
||||
previousBase = base;
|
||||
if (read == 0) break;
|
||||
Require(Collect(small.data(), read, entries) > 0);
|
||||
++calls;
|
||||
}
|
||||
Require(calls >= 3);
|
||||
Require(entries.size() == 6);
|
||||
for (const char* name : {".", "..", "data.bin", "b", "sub"}) Require(entries.contains(name));
|
||||
Require(entries.contains(longName) && entries[longName].recordLength == 44);
|
||||
Require(entries["b"].recordLength == 12);
|
||||
Require(entries["sub"].type == 4 || entries["sub"].type == 0);
|
||||
Require(entries["data.bin"].type == 8 || entries["data.bin"].type == 0);
|
||||
|
||||
Require(sceKernelLseek(directory, 0, 0) == 0);
|
||||
std::array<char, 8> tooSmall{};
|
||||
Require(sceKernelGetdents(directory, tooSmall.data(), static_cast<int>(tooSmall.size())) == ErrorEinval);
|
||||
Require(sceKernelGetdents(directory, nullptr, 64) == ErrorEfault);
|
||||
Require(sceKernelGetdents(directory, small.data(), 0) == ErrorEinval);
|
||||
std::array<char, 4096> large{};
|
||||
std::map<std::string, Entry> all;
|
||||
const int readAll = sceKernelGetdents(directory, large.data(), static_cast<int>(large.size()));
|
||||
Require(readAll > 0 && Collect(large.data(), readAll, all) == 6);
|
||||
Require(sceKernelGetdents(directory, large.data(), static_cast<int>(large.size())) == 0);
|
||||
Require(sceKernelClose(directory) == 0);
|
||||
|
||||
const int file = sceKernelOpen((root / "data.bin").string().c_str(), SCE_KERNEL_O_RDWR, 0);
|
||||
Require(file >= 0);
|
||||
Require(sceKernelLseek(file, 2, 0) == 2);
|
||||
char bytes[16] = {};
|
||||
Require(sceKernelPread(file, bytes, 4, 5) == 4 && std::memcmp(bytes, "5678", 4) == 0);
|
||||
Require(sceKernelLseek(file, 0, 1) == 2);
|
||||
Require(sceKernelPwrite(file, "XY", 2, 8) == 2);
|
||||
Require(sceKernelLseek(file, 0, 1) == 2);
|
||||
Require(sceKernelRead(file, bytes, 3) == 3 && std::memcmp(bytes, "234", 3) == 0);
|
||||
Require(sceKernelPread(file, bytes, sizeof(bytes), 0) == 10 && std::memcmp(bytes, "01234567XY", 10) == 0);
|
||||
Require(sceKernelPread(file, bytes, sizeof(bytes), 10) == 0);
|
||||
Require(sceKernelPread(file, bytes, 1, -1) == ErrorEinval);
|
||||
Require(sceKernelPwrite(file, nullptr, 1, 0) == ErrorEfault);
|
||||
Require(sceKernelClose(file) == 0);
|
||||
Require(sceKernelPread(file, bytes, 1, 0) == ErrorEbadf);
|
||||
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
Loading…
Reference in New Issue