AnyPS5/core/libs/tests/GuestNamedShmLifecycle.cpp

229 lines
14 KiB
C++

#include "SceTypes.hpp"
#include "nid/Sha1.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <dlfcn.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdexcept>
#include <string>
#include <sys/mman.h>
#include <unistd.h>
extern "C" {
int APS5_VABI shm_open_nid_postfix(const char*, int, int);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI ftruncate_nid_postfix(int, std::int64_t);
std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*);
std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t);
std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t);
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 lseek_nid_postfix(int, std::int64_t, int);
int APS5_VABI dup2_nid_postfix(int, int);
void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept;
int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept;
int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int);
int APS5_VABI open_nid_postfix(const char*, int, int);
std::int64_t APS5_VABI writev_nid_postfix(int, const struct GuestIovec*, int);
std::int64_t APS5_VABI pwritev_nid_postfix(int, const struct GuestIovec*, int, std::int64_t);
int* APS5_VABI __error_nid_postfix();
}
struct GuestIovec {
void* base;
std::size_t length;
};
namespace {
constexpr int ReadOnly = 0;
constexpr int ReadWrite = 2;
constexpr int Create = 0x200;
constexpr int Exclusive = 0x800;
constexpr int Shared = 0x1;
constexpr int Private = 0x2;
constexpr std::size_t Page = 0x4000;
using Unlink = int (*)(const char*);
int CreateOrdinaryFile() {
char path[] = "/tmp/anyps5-lifecycle-XXXXXX";
const int descriptor = ::mkstemp(path);
if (descriptor < 0) throw std::runtime_error("ordinary file creation failed");
if (::unlink(path) != 0) {
::close(descriptor);
throw std::runtime_error("ordinary file unlink failed");
}
constexpr char contents[] = "ordinary file contents";
if (::write(descriptor, contents, sizeof(contents)) != static_cast<ssize_t>(sizeof(contents)) ||
::lseek(descriptor, 0, SEEK_SET) != 0) {
::close(descriptor);
throw std::runtime_error("ordinary file initialization failed");
}
return descriptor;
}
void Require(bool condition, const char* message) {
if (!condition) throw std::runtime_error(std::string(message) + " (guest errno=" + std::to_string(*__error_nid_postfix()) + ")");
}
struct Object {
std::string name = "/anyps5-lifecycle-" + std::to_string(getpid());
std::string hostName = "/anyps5-shm-";
Object() {
constexpr char hex[] = "0123456789abcdef";
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(name.begin(), name.end()));
for (const auto byte : digest) {
hostName += hex[byte >> 4];
hostName += hex[byte & 15];
}
::shm_unlink(hostName.c_str());
}
~Object() { ::shm_unlink(hostName.c_str()); }
int Open(int flags) const { return shm_open_nid_postfix(name.c_str(), flags, 0600); }
};
void Run() {
Object object;
const char* anonymousName = reinterpret_cast<const char*>(1);
const int anonymous = shm_open_nid_postfix(anonymousName, 0x100e02, 0600);
Require(anonymous >= 0 && ftruncate_nid_postfix(anonymous, Page) == 0, "anonymous shared-memory setup failed");
auto* anonymousMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Shared, anonymous, 0));
Require(anonymousMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "anonymous MAP_SHARED failed");
anonymousMap[0] = 'a';
char anonymousByte = 0;
Require(pread_nid_postfix(anonymous, &anonymousByte, 1, 0) == 1 && anonymousByte == 'a',
"anonymous MAP_SHARED write was not visible through the descriptor");
Require(munmap_nid_postfix(anonymousMap, Page) == 0 && close_nid_postfix(anonymous) == 0, "anonymous shared-memory cleanup failed");
const int descriptor = object.Open(ReadWrite | Create | Exclusive);
Require(descriptor >= 0, "named object creation failed");
Require(ftruncate_nid_postfix(descriptor, Page) == 0, "guest resize failed");
Require(pwrite_nid_disambig1_nid_postfix(descriptor, "old", 4, 0) == 4, "guest positioned write failed");
char bytes[8]{};
Require(pread_nid_postfix(descriptor, bytes, sizeof(bytes), Page) == 0, "read at EOF was not empty");
Require(pwrite_nid_disambig1_nid_postfix(descriptor, nullptr, 0, Page) == 0, "zero-length write failed");
*__error_nid_postfix() = 0;
Require(lseek_nid_postfix(descriptor, Page + 2, SEEK_SET) == -1 && *__error_nid_postfix() == 22,
"seek past EOF did not fail with EINVAL");
Require(lseek_nid_postfix(descriptor, 0, SEEK_END) == static_cast<std::int64_t>(Page), "seek to EOF returned the wrong position");
Require(write_nid_postfix(descriptor, "z", 1) == 0, "write at EOF grew the shared object");
Require(lseek_nid_postfix(descriptor, Page - 1, SEEK_SET) == static_cast<std::int64_t>(Page - 1), "seek near EOF failed");
Require(write_nid_postfix(descriptor, "xy", 2) == 1, "cross-EOF write did not return its short length");
Require(lseek_nid_postfix(descriptor, 0, SEEK_END) == static_cast<std::int64_t>(Page), "cross-EOF write changed object size");
char vectorBytes[] = "uv";
GuestIovec vectorWrite{vectorBytes, 2};
Require(pwritev_nid_postfix(descriptor, nullptr, 0, Page) == 0, "zero-vector positioned write on a writable descriptor failed");
Require(pwritev_nid_postfix(descriptor, &vectorWrite, 1, Page) == 0, "positioned vector write at EOF grew the object");
Require(pwritev_nid_postfix(descriptor, &vectorWrite, 1, Page - 1) == 1, "positioned vector write past EOF was not short");
Require(lseek_nid_postfix(descriptor, Page - 1, SEEK_SET) == static_cast<std::int64_t>(Page - 1), "seek before vector write failed");
Require(writev_nid_postfix(descriptor, &vectorWrite, 1) == 1, "vector write past EOF was not short");
Require(lseek_nid_postfix(descriptor, 0, SEEK_END) == static_cast<std::int64_t>(Page), "vector write changed object size");
*__error_nid_postfix() = 0;
Require(lseek_nid_postfix(descriptor, -1, SEEK_SET) == -1 && *__error_nid_postfix() == 22,
"negative seek did not return EINVAL");
Require(lseek_nid_postfix(descriptor, std::numeric_limits<std::int64_t>::max(), SEEK_CUR) == -1 &&
*__error_nid_postfix() == 84, "overflowing seek did not return EOVERFLOW");
const int second = object.Open(ReadWrite);
Require(second >= 0 && pread_nid_postfix(second, bytes, 4, 0) == 4 && std::memcmp(bytes, "old", 4) == 0,
"independent open did not see the shared object");
auto* firstMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Shared, descriptor, 0));
Require(firstMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "guest shared mapping failed");
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast<std::uintptr_t>(firstMap), Page),
"file-backed shared mapping was registered with write watch");
auto* secondMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Shared, second, 0));
Require(secondMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "second guest shared mapping failed");
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast<std::uintptr_t>(secondMap), Page),
"file-backed shared alias was registered with write watch");
*__error_nid_postfix() = 0;
Require(mmap_nid_postfix(nullptr, Page, 1, Shared, second, 1) == reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1)) &&
*__error_nid_postfix() == 22, "unaligned shared mapping offset did not return EINVAL");
firstMap[0] = 'n';
Require(secondMap[0] == 'n', "shared mappings did not observe each other's writes");
auto* privateMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Private, second, 0));
Require(privateMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "guest private mapping failed");
firstMap[1] = 'v';
Require(privateMap[1] == 'v', "private mapping did not reflect backing changes before its first write");
privateMap[0] = 'p';
Require(firstMap[0] == 'n', "private mapping write escaped copy-on-write isolation");
Require(pread_nid_postfix(second, bytes, 1, 0) == 1 && bytes[0] == 'n', "private mapping write changed the backing object");
const int readOnly = object.Open(ReadOnly);
Require(readOnly >= 0, "read-only open failed");
*__error_nid_postfix() = 0;
Require(pwrite_nid_disambig1_nid_postfix(readOnly, "x", 1, 0) == -1 && *__error_nid_postfix() == 9,
"positioned write on a read-only descriptor did not return EBADF");
*__error_nid_postfix() = 0;
Require(pwrite_nid_disambig1_nid_postfix(readOnly, "x", 1, Page) == -1 && *__error_nid_postfix() == 9,
"positioned write at EOF on a read-only descriptor did not return EBADF");
*__error_nid_postfix() = 0;
Require(pwritev_nid_postfix(readOnly, nullptr, 0, 0) == -1 && *__error_nid_postfix() == 9,
"zero-vector positioned write on a read-only descriptor did not return EBADF");
FileStat readOnlyInfo{};
Require(fstat_nid_disambig1_nid_postfix(readOnly, &readOnlyInfo) == 0 && readOnlyInfo.st_size == Page,
"read-only EOF write changed the shared-object size");
void* rejected = mmap_nid_postfix(nullptr, Page, 3, Shared, readOnly, 0);
Require(rejected == reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1)), "writable shared mapping accepted a read-only descriptor");
auto* writableMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 1, Shared, descriptor, 0));
Require(writableMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "read-protected writable-fd mapping failed");
Require(mprotect_nid_postfix(writableMap, Page, 3) == 0, "mprotect could not restore write access from a writable descriptor");
writableMap[2] = 'm';
Require(firstMap[2] == 'm', "mprotect-upgraded mapping did not remain shared");
Require(munmap_nid_postfix(writableMap, Page) == 0, "upgraded mapping unmap failed");
auto* readOnlyMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 1, Shared, readOnly, 0));
Require(readOnlyMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "read-only shared mapping failed");
*__error_nid_postfix() = 0;
Require(mprotect_nid_postfix(readOnlyMap, Page, 3) == -1 && *__error_nid_postfix() == 13,
"mprotect on a read-only shared mapping did not return EACCES");
Require(munmap_nid_postfix(readOnlyMap, Page) == 0, "read-only mapping unmap failed");
const int ordinary = CreateOrdinaryFile();
Require(ordinary >= 0, "ordinary file setup failed");
Require(dup2_nid_postfix(ordinary, second) == second, "dup2 onto shared descriptor failed");
Require(lseek_nid_postfix(second, 0, SEEK_SET) == 0 && read_nid_postfix(second, bytes, sizeof(bytes)) > 0,
"dup2 target did not become the ordinary file");
Require(close_nid_postfix(second) == 0, "close after dup2 failed");
Require(lseek_nid_postfix(ordinary, 0, SEEK_SET) == 0 && read_nid_postfix(ordinary, bytes, sizeof(bytes)) > 0,
"closing reused descriptor damaged the ordinary file");
Require(close_nid_postfix(ordinary) == 0, "ordinary file close failed");
const int reused = object.Open(ReadWrite);
Require(reused >= 0, "shared object reopen after descriptor close failed");
Require(dup2_nid_postfix(reused, second) == second, "guest dup2 into the freed descriptor slot failed");
Require(pread_nid_postfix(second, bytes, 3, 0) == 3 && std::memcmp(bytes, "nvm", 3) == 0,
"reused descriptor slot retained ordinary-file state");
Require(close_nid_postfix(second) == 0, "reused descriptor slot close failed");
if (reused != second) Require(close_nid_postfix(reused) == 0, "reopened descriptor close failed");
Require(close_nid_postfix(descriptor) == 0 && close_nid_postfix(readOnly) == 0, "descriptor close failed");
Require(firstMap[0] == 'n', "mapping did not survive descriptor close");
const auto unlinkObject = reinterpret_cast<Unlink>(dlsym(RTLD_DEFAULT, "shm_unlink_nid_postfix"));
Require(unlinkObject != nullptr, "guest shm_unlink export is missing");
Require(unlinkObject(object.name.c_str()) == 0, "guest unlink failed");
*__error_nid_postfix() = 0;
Require(unlinkObject(object.name.c_str()) == -1 && *__error_nid_postfix() == 2, "missing-name unlink did not return ENOENT");
const int recreated = object.Open(ReadWrite | Create | Exclusive);
Require(recreated >= 0, "name recreation after unlink failed");
Require(ftruncate_nid_postfix(recreated, Page) == 0, "recreated object resize failed");
Require(pread_nid_postfix(recreated, bytes, 4, 0) == 4 && std::memcmp(bytes, "\0\0\0\0", 4) == 0,
"recreated name still refers to the unlinked object");
Require(firstMap[0] == 'n', "unlink or recreation changed the old mapping");
Require(close_nid_postfix(recreated) == 0, "recreated descriptor close failed");
Require(munmap_nid_postfix(firstMap, Page) == 0 && munmap_nid_postfix(secondMap, Page) == 0 && munmap_nid_postfix(privateMap, Page) == 0,
"guest unmap failed");
std::puts("named shared-memory lifecycle checks passed");
}
}
int main() {
try {
Run();
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}