Files
AnyPS5/core/libs/tests/GuestKernelAppArchive.cpp
T

224 lines
10 KiB
C++

#include "prx/libkernel/include/Api.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libc/include/Api.hpp"
#include "AppArchiveFixture.hpp"
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/File/include/FileFlags.hpp"
#include "prx/libkernel/KernelErrors.hpp"
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <stdexcept>
#include <string>
#include <typeinfo>
#include <vector>
extern "C" {
using TerminateHandler = void (*)();
}
namespace {
void Require(bool value, int line) {
if (!value) {
std::fprintf(stderr, "app0.zar kernel check failed at line %d\n", line);
std::exit(1);
}
}
#define Check(value) Require((value), __LINE__)
void OnMmapRefused() {
const auto* type = __cxa_current_exception_type_nid_postfix();
const auto* error = static_cast<const std::runtime_error*>(__cxa_current_primary_exception_nid_postfix());
const bool refused = type != nullptr && error != nullptr && std::strcmp(type->name(), typeid(std::runtime_error).name()) == 0 &&
std::strstr(error->what(), "mmap on a file read from app0.zar") != nullptr && std::strstr(error->what(), "not implemented") != nullptr;
std::_Exit(refused ? 0 : 1);
}
template <typename TCall> bool NotImplemented(TCall call) {
try {
call();
} catch (const std::runtime_error& error) {
return std::strstr(error.what(), "not implemented") != nullptr;
}
return false;
}
constexpr std::size_t BigSize = 3 * 65536 + 77;
constexpr std::uint16_t TypeMask = 0170000;
constexpr std::uint16_t RegularType = 0100000;
constexpr std::uint16_t DirectoryType = 0040000;
constexpr int DuplicateFrom = 0;
constexpr std::uint64_t ReadableRequest = 0x4004667f;
void Archive(const std::filesystem::path& root) {
AppArchiveFixture::WriteArchive(root / "app0.zar", {
{"Data/Big.bin", AppArchiveFixture::Pattern(BigSize)},
{"Data/Small.txt", {'z', 'a', 'r'}},
{"sce_sys/Packed.json", {'{', '}'}},
{"eboot.bin", {'o', 'l', 'd'}},
});
std::ofstream eboot(root / "app0" / "eboot.bin", std::ios::binary);
eboot << "disk";
}
void Reads() {
const auto expected = AppArchiveFixture::Pattern(BigSize);
const int fd = sceKernelOpen("/app0/data/big.BIN", SCE_KERNEL_O_RDONLY, 0);
Check(fd >= 0);
std::vector<char> buffer(BigSize + 16);
Check(sceKernelRead(fd, buffer.data(), 70000) == 70000);
Check(std::equal(buffer.begin(), buffer.begin() + 70000, expected.begin()));
Check(sceKernelLseek(fd, 0, 1) == 70000);
Check(lseek_nid_postfix(fd, 0, 1) == 70000);
std::vector<char> left(10), right(65536);
const KernelIovec vector[] = {{left.data(), left.size()}, {right.data(), right.size()}};
Check(sceKernelReadv(fd, vector, 2) == 65546);
Check(std::equal(left.begin(), left.end(), expected.begin() + 70000) && std::equal(right.begin(), right.end(), expected.begin() + 70010));
Check(sceKernelPreadv(fd, vector, 2, 5) == 65546);
Check(std::equal(left.begin(), left.end(), expected.begin() + 5) && std::equal(right.begin(), right.end(), expected.begin() + 15));
Check(sceKernelLseek(fd, 0, 1) == 135546);
Check(sceKernelPread(fd, buffer.data(), 100, 65500) == 100 && std::equal(buffer.begin(), buffer.begin() + 100, expected.begin() + 65500));
Check(pread_nid_postfix(fd, buffer.data(), 100, BigSize - 40) == 40 && std::equal(buffer.begin(), buffer.begin() + 40, expected.end() - 40));
Check(sceKernelPread(fd, buffer.data(), 1, -1) == SCE_KERNEL_ERROR_EINVAL);
Check(sceKernelLseek(fd, 0, 1) == 135546);
int available = 0;
Check(ioctl_nid_postfix(fd, ReadableRequest, &available) == 0 && available == static_cast<int>(BigSize - 135546));
Check(sceKernelLseek(fd, -7, 2) == static_cast<std::int64_t>(BigSize - 7));
Check(sceKernelRead(fd, buffer.data(), 100) == 7 && std::equal(buffer.begin(), buffer.begin() + 7, expected.end() - 7));
Check(sceKernelRead(fd, buffer.data(), 100) == 0);
Check(sceKernelLseek(fd, -1, 0) == SCE_KERNEL_ERROR_EINVAL);
Check(sceKernelLseek(fd, 0, 1) == static_cast<std::int64_t>(BigSize));
Check(sceKernelWrite(fd, "x", 1) == SCE_KERNEL_ERROR_EBADF);
FileStat status{};
Check(sceKernelFstat(fd, &status) == 0);
Check((status.st_mode & TypeMask) == RegularType && status.st_size == static_cast<std::int64_t>(BigSize) && status.st_nlink == 1);
KernelAioResult result{-1, 0};
KernelAioRwRequest request{65530, 20, buffer.data(), &result, fd};
std::int32_t id = 0;
std::int32_t state = 0;
Check(sceKernelAioSubmitReadCommands(&request, 1, 1, &id) == 0);
Check(sceKernelAioWaitRequest(id, &state, nullptr) == 0);
Check(result.return_value == 20 && std::equal(buffer.begin(), buffer.begin() + 20, expected.begin() + 65530));
std::int32_t removed = 0;
Check(sceKernelAioDeleteRequest(id, &removed) == 0);
Check(sceKernelLseek(fd, 1000, 0) == 1000);
const int duplicate = dup_nid_postfix(fd);
Check(duplicate >= 0 && sceKernelLseek(duplicate, 0, 1) == 1000);
Check(sceKernelRead(duplicate, buffer.data(), 24) == 24 && sceKernelLseek(fd, 0, 1) == 1024);
const int above = fcntl_nid_postfix(fd, DuplicateFrom, 200);
Check(above >= 200 && sceKernelLseek(above, 0, 1) == 1024);
Check(sceKernelClose(fd) == 0);
Check(sceKernelRead(duplicate, buffer.data(), 4) == 4 && std::equal(buffer.begin(), buffer.begin() + 4, expected.begin() + 1024));
Check(sceKernelLseek(above, 0, 1) == 1028);
Check(close_nid_postfix(above) == 0);
const int plain = sceKernelOpen("/app0/eboot.bin", SCE_KERNEL_O_RDONLY, 0);
Check(plain >= 0);
Check(dup2_nid_postfix(plain, duplicate) == duplicate);
Check(sceKernelPread(duplicate, buffer.data(), 8, 0) == 4 && std::memcmp(buffer.data(), "disk", 4) == 0);
Check(sceKernelClose(plain) == 0);
Check(sceKernelClose(duplicate) == 0);
}
void Status() {
FileStat status{};
Check(sceKernelStat("/app0/Data/Small.txt", &status) == 0);
Check((status.st_mode & TypeMask) == RegularType && (status.st_mode & 0222) == 0 && status.st_size == 3 && status.st_blocks == 1);
Check(sceKernelStat("/app0/data", &status) == 0 && (status.st_mode & TypeMask) == DirectoryType);
Check(sceKernelStat("/app0/Data/", &status) == 0);
Check(sceKernelStat("/app0/Data/Small.txt/", &status) == SCE_KERNEL_ERROR_ENOTDIR);
Check(sceKernelStat("/app0/Data/Missing.txt", &status) == SCE_KERNEL_ERROR_ENOENT);
Check(sceKernelStat("/app0/eboot.bin", &status) == 0 && status.st_size == 4);
Check(sceKernelCheckReachability("/app0/Data/Small.txt") == 0);
Check(sceKernelCheckReachability("/app0/Data/Missing.txt") == SCE_KERNEL_ERROR_ENOENT);
}
void Refusals() {
Check(sceKernelOpen("/app0/Data/Small.txt", SCE_KERNEL_O_RDWR, 0) == SCE_KERNEL_ERROR_EROFS);
Check(sceKernelOpen("/app0/Data/Small.txt", SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_TRUNC, 0) == SCE_KERNEL_ERROR_EROFS);
Check(sceKernelOpen("/app0/Data/Small.txt", SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL, 0666) == SCE_KERNEL_ERROR_EEXIST);
Check(sceKernelOpen("/app0/Data/Small.txt", SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0) == SCE_KERNEL_ERROR_ENOTDIR);
Check(sceKernelOpen("/app0/Data/Missing.txt", SCE_KERNEL_O_RDONLY, 0) == SCE_KERNEL_ERROR_ENOENT);
Check(NotImplemented([] { (void)sceKernelOpen("/app0/Data", SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); }));
Check(NotImplemented([] { (void)sceKernelOpen("/app0/sce_sys", SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); }));
Check(NotImplemented([] { (void)opendir_nid_postfix("/app0/sce_sys"); }));
Check(NotImplemented([] { (void)opendir_nid_postfix("/app0/Data"); }));
const int fd = sceKernelOpen("/app0/Data/Small.txt", SCE_KERNEL_O_RDONLY, 0);
Check(fd >= 0);
Check(NotImplemented([fd] { (void)fdopen_nid_postfix(fd, "r"); }));
Check(NotImplemented([fd] { (void)flock_nid_postfix(fd, 1); }));
Check(NotImplemented([fd] { alignas(8) char statfs[512]; (void)_fstatfs_nid_postfix(fd, reinterpret_cast<KernelStatfs*>(statfs)); }));
Check(NotImplemented([fd] { (void)ftruncate_nid_postfix(fd, 0); }));
Check(NotImplemented([fd] { (void)sceKernelFsync(fd); }));
Check(NotImplemented([fd] { (void)sceKernelFdatasync(fd); }));
Check(NotImplemented([fd] { char entries[512]; (void)sceKernelGetdents(fd, entries, sizeof(entries)); }));
Check(NotImplemented([fd] { (void)sceKernelFchmod(fd, 0444); }));
Check(NotImplemented([fd] { (void)sceKernelFutimes(fd, nullptr); }));
FileStat status{};
Check(sceKernelFstat(fd, &status) == 0 && status.st_size == 3);
Check(sceKernelClose(fd) == 0);
}
void WithoutArchive() {
{ std::ofstream file(std::filesystem::current_path() / "app0" / "Plain.txt", std::ios::binary); file << "plain"; }
const int fd = sceKernelOpen("/app0/Plain.txt", SCE_KERNEL_O_RDONLY, 0);
Check(fd >= 0);
char buffer[8] = {};
Check(sceKernelRead(fd, buffer, sizeof(buffer)) == 5 && std::memcmp(buffer, "plain", 5) == 0);
Check(sceKernelClose(fd) == 0);
Check(sceKernelOpen("/app0/Data/Small.txt", SCE_KERNEL_O_RDONLY, 0) == SCE_KERNEL_ERROR_ENOENT);
FileStat status{};
Check(sceKernelStat("/app0/Data/Small.txt", &status) == SCE_KERNEL_ERROR_ENOENT);
const int directory = sceKernelOpen("/app0/sce_sys", SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
Check(directory >= 0 && sceKernelClose(directory) == 0);
}
}
int main(int argc, char** argv) {
if (argc != 2) {
std::fprintf(stderr, "usage: guest_kernel_app_archive_tests archive|plain\n");
return 2;
}
const std::string mode = argv[1];
const auto root = AppArchiveFixture::EnterFreshRoot("anyps5-kernel-app-archive");
if (mode == "archive") {
Archive(root);
Reads();
Status();
Refusals();
} else if (mode == "mmap") {
Archive(root);
const int fd = sceKernelOpen("/app0/Data/Small.txt", SCE_KERNEL_O_RDONLY, 0);
Check(fd >= 0);
_ZSt13set_terminatePFvvE_nid_postfix(OnMmapRefused);
(void)mmap_nid_postfix(nullptr, 16384, 1, 2, fd, 0);
return 1;
} else if (mode == "plain") {
WithoutArchive();
} else {
return 2;
}
std::error_code error;
std::filesystem::current_path(std::filesystem::temp_directory_path(), error);
std::filesystem::remove_all(root, error);
return 0;
}