mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
MinGW libstdc++ up to GCC 16 does not treat a reparse point as a link in std::filesystem::remove_all, so a host tree that holds a symlink is walked through the link and never returns. Add RemoveTree, which removes the tree itself on Windows, and use it where save data, app content and the shader disk cache removed host trees. The libstdc++ fix is in GCC 17 (88856ad855ae).
665 lines
41 KiB
C++
665 lines
41 KiB
C++
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include "prx/libc/include/RemoveTree.hpp"
|
|
#include "prx/libkernel/File/include/FileFlags.hpp"
|
|
#include "SceTypes.hpp"
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <source_location>
|
|
#include <stdexcept>
|
|
#include <set>
|
|
#include <string>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
|
|
#define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2
|
|
#endif
|
|
#endif
|
|
#ifndef _WIN32
|
|
#include <cerrno>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
extern "C" {
|
|
int APS5_VABI fstatfs_nid_postfix(int, void*);
|
|
int APS5_VABI _fstatfs_nid_postfix(int, void*);
|
|
int APS5_VABI open_nid_postfix(const char*, int, int);
|
|
int APS5_VABI close_nid_postfix(int);
|
|
std::int64_t APS5_VABI readlink_nid_postfix(const char*, char*, std::size_t);
|
|
std::int64_t APS5_VABI pathconf_nid_postfix(const char*, int);
|
|
int APS5_VABI fchdir_nid_postfix(int);
|
|
int APS5_VABI chdir_nid_postfix(const char*);
|
|
int APS5_VABI chroot_nid_postfix(const char*);
|
|
char* APS5_VABI getcwd_nid_postfix(char*, std::size_t);
|
|
int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t);
|
|
int APS5_VABI sceKernelClose(int);
|
|
int APS5_VABI sceKernelRename(const char*, const char*);
|
|
int APS5_VABI sceKernelFstat(int, FileStat*);
|
|
int APS5_VABI sceKernelGetdents(int, char*, int);
|
|
std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int);
|
|
std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t);
|
|
int APS5_VABI dup_nid_postfix(int);
|
|
int APS5_VABI dup2_nid_postfix(int, int);
|
|
int APS5_VABI setsid_nid_postfix(void);
|
|
int APS5_VABI link_nid_postfix(const char*, const char*);
|
|
int APS5_VABI symlink_nid_postfix(const char*, const char*);
|
|
int APS5_VABI openat_nid_postfix(int, const char*, int, ...);
|
|
int APS5_VABI unlinkat_nid_postfix(int, const char*, int);
|
|
std::int64_t APS5_VABI readlinkat_nid_postfix(int, const char*, char*, std::size_t);
|
|
int APS5_VABI mkdirat_nid_postfix(int, const char*, std::uint16_t);
|
|
int APS5_VABI renameat_nid_postfix(int, const char*, int, const char*);
|
|
int APS5_VABI fchmodat_nid_postfix(int, const char*, int, int);
|
|
int APS5_VABI fchown_nid_postfix(int, std::uint32_t, std::uint32_t);
|
|
std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t);
|
|
std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t);
|
|
int* APS5_VABI __error_nid_postfix();
|
|
}
|
|
|
|
void Require(bool condition, std::source_location location = std::source_location::current()) {
|
|
if (!condition) {
|
|
std::fprintf(stderr, "Kernel sys check failed at line %u (guest errno %d)\n", location.line(), *__error_nid_postfix());
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
int Error() {
|
|
return *__error_nid_postfix();
|
|
}
|
|
|
|
bool PathconfThrows(const char* path, int name) {
|
|
try {
|
|
pathconf_nid_postfix(path, name);
|
|
} catch (const std::runtime_error&) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ChrootThrows(const std::string& path) {
|
|
try {
|
|
chroot_nid_postfix(path.c_str());
|
|
} catch (const std::runtime_error& error) {
|
|
return std::strstr(error.what(), path.c_str()) != nullptr;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SymlinkTargetThrows(const char* target, const std::string& created) {
|
|
try {
|
|
symlink_nid_postfix(target, created.c_str());
|
|
} catch (const std::runtime_error&) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RemoveSymlink(const std::string& link) {
|
|
#ifdef _WIN32
|
|
// std::filesystem::remove follows the link, reports a dangling one as
|
|
// missing and clears the error, so remove the reparse point itself. A
|
|
// directory reparse point, such as the one the "..." target creates, is
|
|
// refused by DeleteFileW and needs RemoveDirectoryW instead.
|
|
const auto wide = std::filesystem::path(link).wstring();
|
|
return DeleteFileW(wide.c_str()) != FALSE || RemoveDirectoryW(wide.c_str()) != FALSE;
|
|
#else
|
|
return std::filesystem::remove(link);
|
|
#endif
|
|
}
|
|
|
|
void CheckSymlinkTargetRestrictions(const std::filesystem::path& root) {
|
|
const auto created = (root / "rejected").string();
|
|
for (const auto& target : {std::string(".."), std::string("../x"), std::string("a/../b"), std::string("a/../../x"), std::string("/abs")})
|
|
Require(SymlinkTargetThrows(target.c_str(), created));
|
|
#ifdef _WIN32
|
|
for (const auto& target : {std::string("C:/"), std::string("\\\\server\\share")})
|
|
Require(SymlinkTargetThrows(target.c_str(), created));
|
|
#endif
|
|
const auto links = [](const std::string& created, const char* target) {
|
|
char buffer[16];
|
|
const auto length = std::strlen(target);
|
|
return readlink_nid_postfix(created.c_str(), buffer, sizeof(buffer)) == static_cast<std::int64_t>(length) &&
|
|
std::memcmp(buffer, target, length) == 0;
|
|
};
|
|
const auto nested = (root / "accepted-nested").string();
|
|
const int link = symlink_nid_postfix("sub/dir", nested.c_str());
|
|
if (link == 0) {
|
|
Require(links(nested, "sub/dir"));
|
|
const auto dots = (root / "accepted-dots").string();
|
|
Require(symlink_nid_postfix("a..b", dots.c_str()) == 0 && links(dots, "a..b"));
|
|
const auto ellipsis = (root / "accepted-ellipsis").string();
|
|
Require(symlink_nid_postfix("...", ellipsis.c_str()) == 0 && links(ellipsis, "..."));
|
|
Require(RemoveSymlink(nested) && RemoveSymlink(dots) && RemoveSymlink(ellipsis));
|
|
} else {
|
|
Require(link == -1 && Error() == 1);
|
|
std::fprintf(stderr, "symbolic links unavailable: the symlink target success cases not tested\n");
|
|
}
|
|
}
|
|
|
|
void CheckPathconf(const std::filesystem::path& root, const std::string& file) {
|
|
const auto directory = root.generic_string();
|
|
for (const auto& path : {file, directory}) {
|
|
Require(pathconf_nid_postfix(path.c_str(), 1) == 32767);
|
|
Require(pathconf_nid_postfix(path.c_str(), 4) == 255);
|
|
Require(pathconf_nid_postfix(path.c_str(), 5) == 1024);
|
|
Require(pathconf_nid_postfix(path.c_str(), 6) == 512);
|
|
Require(pathconf_nid_postfix(path.c_str(), 7) == 1);
|
|
Require(pathconf_nid_postfix(path.c_str(), 8) == 1);
|
|
Require(pathconf_nid_postfix(path.c_str(), 12) == 64);
|
|
*__error_nid_postfix() = 0;
|
|
Require(pathconf_nid_postfix(path.c_str(), 15) == -1 && Error() == 0);
|
|
Require(pathconf_nid_postfix(path.c_str(), 18) == 1024);
|
|
Require(pathconf_nid_postfix(path.c_str(), 54) == 0);
|
|
Require(pathconf_nid_postfix(path.c_str(), 55) == 0);
|
|
}
|
|
for (const int name : {0, 11, 61, 62, 65, -1})
|
|
Require(pathconf_nid_postfix(file.c_str(), name) == -1 && Error() == 22);
|
|
for (const int name : {2, 3, 9, 10, 14, 16, 17, 21, 53, 59, 60, 63, 64})
|
|
Require(PathconfThrows(file.c_str(), name));
|
|
const auto missing = (root / "missing").string();
|
|
Require(pathconf_nid_postfix(missing.c_str(), 4) == -1 && Error() == 2);
|
|
Require(pathconf_nid_postfix(missing.c_str(), 0) == -1 && Error() == 2);
|
|
Require(pathconf_nid_postfix((root / "missing" / "file").string().c_str(), 4) == -1 && Error() == 2);
|
|
Require(pathconf_nid_postfix("", 4) == -1 && Error() == 2);
|
|
Require(pathconf_nid_postfix(nullptr, 4) == -1 && Error() == 14);
|
|
const auto longName = directory + "/" + std::string(256, 'n');
|
|
Require(pathconf_nid_postfix(longName.c_str(), 4) == -1 && Error() == 63);
|
|
Require(pathconf_nid_postfix((directory + "/missing/" + std::string(256, 'n')).c_str(), 4) == -1 && Error() == 2);
|
|
const auto longPath = directory + "/" + std::string(1024, 'p');
|
|
Require(pathconf_nid_postfix(longPath.c_str(), 4) == -1 && Error() == 63);
|
|
}
|
|
|
|
void DirectoryRenameTests(const std::filesystem::path& parent) {
|
|
const auto fixture = parent / "directory-identity";
|
|
const auto original = fixture / "original";
|
|
const auto moved = fixture / "moved";
|
|
const auto twice = fixture / "twice";
|
|
Require(std::filesystem::create_directory(fixture));
|
|
Require(std::filesystem::create_directory(original));
|
|
{ std::ofstream(original / "marker").put('o'); }
|
|
{ std::ofstream(original / "old-only").put('a'); }
|
|
const auto readMarker = [](int directory) {
|
|
const int file = openat_nid_postfix(directory, "marker", SCE_KERNEL_O_RDONLY);
|
|
Require(file >= 0);
|
|
char marker = 0;
|
|
Require(sceKernelRead(file, &marker, 1) == 1);
|
|
Require(sceKernelClose(file) == 0);
|
|
return marker;
|
|
};
|
|
const auto names = [](int directory) {
|
|
std::set<std::string> result;
|
|
std::array<char, 4096> buffer{};
|
|
for (;;) {
|
|
const int bytes = sceKernelGetdents(directory, buffer.data(), buffer.size());
|
|
Require(bytes >= 0);
|
|
if (bytes == 0) return result;
|
|
for (int offset = 0; offset < bytes;) {
|
|
std::uint16_t length = 0;
|
|
std::memcpy(&length, buffer.data() + offset + 4, sizeof(length));
|
|
Require(length >= 12 && offset + length <= bytes);
|
|
result.emplace(buffer.data() + offset + 8);
|
|
offset += length;
|
|
}
|
|
}
|
|
};
|
|
const int first = sceKernelOpen(original.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
|
|
Require(first >= 0 && readMarker(first) == 'o');
|
|
FileStat before{};
|
|
Require(sceKernelFstat(first, &before) == 0 && (before.st_mode & 0170000) == 0040000);
|
|
const int duplicate = dup_nid_postfix(first);
|
|
const int target = sceKernelOpen((original / "marker").string().c_str(), SCE_KERNEL_O_RDONLY, 0);
|
|
Require(duplicate >= 0 && target >= 0);
|
|
Require(dup2_nid_postfix(first, target) == target);
|
|
Require(sceKernelClose(first) == 0);
|
|
Require(sceKernelRename(original.string().c_str(), moved.string().c_str()) == 0);
|
|
Require(std::filesystem::create_directory(original));
|
|
{ std::ofstream(original / "marker").put('r'); }
|
|
{ std::ofstream(original / "new-only").put('b'); }
|
|
const char marker = readMarker(duplicate);
|
|
std::fprintf(stderr, "directory marker after rename/replacement: %c, expected o\n", marker);
|
|
Require(marker == 'o' && readMarker(target) == 'o');
|
|
const std::set<std::string> expected{".", "..", "marker", "old-only"};
|
|
Require(names(duplicate) == expected);
|
|
Require(names(target).empty());
|
|
FileStat after{};
|
|
Require(sceKernelFstat(target, &after) == 0 && after.st_dev == before.st_dev && after.st_ino == before.st_ino);
|
|
Require(sceKernelLseek(target, 0, 0) == 0);
|
|
char directoryByte = 0;
|
|
Require(sceKernelRead(target, &directoryByte, 1) == static_cast<int>(0x80020015u));
|
|
Require(sceKernelPread(target, &directoryByte, 1, 0) == static_cast<int>(0x80020015u));
|
|
char previous[4096];
|
|
Require(getcwd_nid_postfix(previous, sizeof(previous)) == previous);
|
|
Require(fchdir_nid_postfix(target) == 0 && readMarker(-100) == 'o');
|
|
Require(chdir_nid_postfix(previous) == 0);
|
|
Require(mkdirat_nid_postfix(target, "created", 0755) == 0);
|
|
Require(std::filesystem::is_directory(moved / "created") && !std::filesystem::exists(original / "created"));
|
|
Require(unlinkat_nid_postfix(duplicate, "created", 0x800) == 0);
|
|
Require(sceKernelRename(moved.string().c_str(), twice.string().c_str()) == 0);
|
|
Require(std::filesystem::create_directory(moved));
|
|
{ std::ofstream(moved / "different").put('c'); }
|
|
Require(sceKernelLseek(target, 0, 0) == 0 && names(target) == expected);
|
|
Require(sceKernelClose(duplicate) == 0);
|
|
Require(readMarker(target) == 'o');
|
|
Require(unlinkat_nid_postfix(target, "marker", 0) == 0);
|
|
Require(unlinkat_nid_postfix(target, "old-only", 0) == 0);
|
|
Require(std::filesystem::remove(twice));
|
|
Require(sceKernelFstat(target, &after) == 0 && after.st_dev == before.st_dev && after.st_ino == before.st_ino && (after.st_mode & 0170000) == 0040000);
|
|
Require(sceKernelClose(target) == 0);
|
|
Require(std::filesystem::remove(original / "marker") && std::filesystem::remove(original / "new-only"));
|
|
Require(std::filesystem::remove(moved / "different"));
|
|
Require(std::filesystem::remove(original) && std::filesystem::remove(moved) && std::filesystem::remove(fixture));
|
|
}
|
|
|
|
int main() {
|
|
#ifdef _WIN32
|
|
auto hostDirectorySpelling = std::filesystem::current_path().native();
|
|
CharUpperBuffW(hostDirectorySpelling.data(), static_cast<DWORD>(hostDirectorySpelling.size()));
|
|
Require(SetCurrentDirectoryW(hostDirectorySpelling.c_str()) != 0);
|
|
#endif
|
|
Require(setsid_nid_postfix() == -1 && Error() == 1);
|
|
Require(umask_nid_postfix(0) == 022);
|
|
Require(umask_nid_postfix(07777) == 0);
|
|
Require(umask_nid_postfix(022) == 07777);
|
|
const auto root = std::filesystem::path("anyps5-kernel-sys-test-" +
|
|
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
|
Require(std::filesystem::create_directory(root));
|
|
DirectoryRenameTests(root);
|
|
const auto file = (root / "file.txt").string();
|
|
std::ofstream(file).put('x');
|
|
CheckPathconf(root, file);
|
|
const int descriptor = open_nid_postfix(file.c_str(), 0, 0);
|
|
Require(descriptor >= 0);
|
|
unsigned char viaPublic[472] = {};
|
|
unsigned char viaSyscall[472] = {};
|
|
Require(fstatfs_nid_postfix(descriptor, viaPublic) == 0 && _fstatfs_nid_postfix(descriptor, viaSyscall) == 0);
|
|
Require(std::memcmp(viaPublic, viaSyscall, 4) == 0 && std::memcmp(viaPublic + 16, viaSyscall + 16, 8) == 0);
|
|
Require(std::memcmp(viaPublic + 280, viaSyscall + 280, 16) == 0);
|
|
Require(fstatfs_nid_postfix(descriptor, nullptr) == -1 && Error() == 14);
|
|
Require(close_nid_postfix(descriptor) == 0);
|
|
Require(fstatfs_nid_postfix(-1, viaPublic) == -1 && Error() == 9);
|
|
char buffer[64];
|
|
for (const auto& component : {std::string("missing"), std::string("file.txt")}) {
|
|
const auto prefix = root.generic_string() + "/" + component + "/../";
|
|
const auto invalid = prefix + "file.txt";
|
|
const auto created = prefix + "new.txt";
|
|
const int expected = component == "missing" ? 2 : 20;
|
|
Require(readlink_nid_postfix(invalid.c_str(), buffer, sizeof(buffer)) == -1 && Error() == expected);
|
|
Require(link_nid_postfix(invalid.c_str(), created.c_str()) == -1 && Error() == expected);
|
|
Require(link_nid_postfix(file.c_str(), created.c_str()) == -1 && Error() == expected);
|
|
Require(symlink_nid_postfix("file.txt", created.c_str()) == -1 && Error() == expected);
|
|
Require(std::filesystem::hard_link_count(file) == 1 && !std::filesystem::exists(root / "new.txt"));
|
|
}
|
|
Require(readlink_nid_postfix(file.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22);
|
|
Require(readlink_nid_postfix(root.string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22);
|
|
Require(readlink_nid_postfix((root / "missing").string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 2);
|
|
Require(readlink_nid_postfix("", buffer, sizeof(buffer)) == -1 && Error() == 2);
|
|
Require(readlink_nid_postfix(nullptr, buffer, sizeof(buffer)) == -1 && Error() == 14);
|
|
#ifndef _WIN32
|
|
Require(readlink_nid_postfix((root / std::string(300, 'n')).string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 63);
|
|
#endif
|
|
std::error_code error;
|
|
const auto link = (root / "link").string();
|
|
std::filesystem::create_symlink("sub/target.txt", link, error);
|
|
if (!error) {
|
|
std::memset(buffer, '#', sizeof(buffer));
|
|
Require(readlink_nid_postfix(link.c_str(), buffer, sizeof(buffer)) == 14);
|
|
Require(std::memcmp(buffer, "sub/target.txt", 14) == 0 && buffer[14] == '#');
|
|
std::memset(buffer, '#', sizeof(buffer));
|
|
Require(readlink_nid_postfix(link.c_str(), buffer, 3) == 3);
|
|
Require(std::memcmp(buffer, "sub", 3) == 0 && buffer[3] == '#');
|
|
Require(readlink_nid_postfix(link.c_str(), nullptr, 0) == 0);
|
|
Require(readlink_nid_postfix(link.c_str(), nullptr, sizeof(buffer)) == -1 && Error() == 14);
|
|
Require(readlink_nid_postfix((root / "LINK").string().c_str(), buffer, sizeof(buffer)) == 14);
|
|
const int linkDir = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
|
|
Require(linkDir >= 0);
|
|
std::memset(buffer, '#', sizeof(buffer));
|
|
Require(readlinkat_nid_postfix(linkDir, "link", buffer, sizeof(buffer)) == 14);
|
|
Require(std::memcmp(buffer, "sub/target.txt", 14) == 0 && buffer[14] == '#');
|
|
Require(readlinkat_nid_postfix(linkDir, "link", buffer, 3) == 3 && std::memcmp(buffer, "sub", 3) == 0);
|
|
Require(readlinkat_nid_postfix(linkDir, "link", nullptr, 0) == 0);
|
|
Require(readlinkat_nid_postfix(linkDir, "link", nullptr, sizeof(buffer)) == -1 && Error() == 14);
|
|
const auto guestLink = "/" + root.filename().string() + "/link";
|
|
Require(readlinkat_nid_postfix(-1, guestLink.c_str(), buffer, sizeof(buffer)) == 14 && std::memcmp(buffer, "sub/target.txt", 14) == 0);
|
|
Require(readlinkat_nid_postfix(-100, link.c_str(), buffer, sizeof(buffer)) == 14 && std::memcmp(buffer, "sub/target.txt", 14) == 0);
|
|
Require(sceKernelClose(linkDir) == 0);
|
|
#ifndef _WIN32
|
|
std::filesystem::create_symlink("loopB", root / "loopA");
|
|
std::filesystem::create_symlink("loopA", root / "loopB");
|
|
Require(readlink_nid_postfix((root / "loopA" / "inner").string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 62);
|
|
#endif
|
|
} else {
|
|
std::fprintf(stderr, "symbolic links unavailable: the link cases not tested\n");
|
|
}
|
|
const auto original = (root / "original.bin").string();
|
|
const auto linked = (root / "linked.bin").string();
|
|
std::ofstream(original) << "abc";
|
|
Require(link_nid_postfix(original.c_str(), linked.c_str()) == 0);
|
|
Require(std::filesystem::hard_link_count(original) == 2 && std::filesystem::file_size(linked) == 3);
|
|
std::ofstream(linked, std::ios::trunc).close();
|
|
Require(std::filesystem::file_size(original) == 0);
|
|
Require(link_nid_postfix(original.c_str(), linked.c_str()) == -1 && Error() == 17);
|
|
Require(link_nid_postfix((root / "missing").string().c_str(), (root / "other").string().c_str()) == -1 && Error() == 2);
|
|
Require(link_nid_postfix(original.c_str(), (root / "missing" / "linked.bin").string().c_str()) == -1 && Error() == 2);
|
|
Require(link_nid_postfix(root.string().c_str(), (root / "directory").string().c_str()) == -1 && Error() == 1);
|
|
Require(link_nid_postfix(nullptr, linked.c_str()) == -1 && Error() == 14);
|
|
Require(link_nid_postfix(original.c_str(), "") == -1 && Error() == 2);
|
|
Require(std::filesystem::remove(linked) && std::filesystem::hard_link_count(original) == 1);
|
|
const int directory = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
|
|
Require(directory >= 0);
|
|
const int fileDescriptor = sceKernelOpen(file.c_str(), SCE_KERNEL_O_RDONLY, 0);
|
|
Require(fileDescriptor >= 0);
|
|
Require(fchdir_nid_postfix(-1) == -1 && Error() == 9);
|
|
Require(fchdir_nid_postfix(fileDescriptor) == -1 && Error() == 20);
|
|
Require(fchdir_nid_postfix(directory) == 0);
|
|
char directoryPath[1024];
|
|
Require(getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath);
|
|
Require(std::strcmp(directoryPath, ("/" + root.filename().string()).c_str()) == 0);
|
|
#ifndef _WIN32
|
|
Require(chdir_nid_postfix("/") == 0);
|
|
const auto originalPermissions = std::filesystem::status(root).permissions();
|
|
std::filesystem::permissions(root, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write);
|
|
const bool searchDenied = ::access(root.c_str(), X_OK) == -1 && errno == EACCES;
|
|
const int deniedResult = searchDenied ? fchdir_nid_postfix(directory) : 0;
|
|
const int deniedError = Error();
|
|
const bool cwdUnchanged = getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath && std::strcmp(directoryPath, "/") == 0;
|
|
std::filesystem::permissions(root, std::filesystem::perms::owner_exec);
|
|
const int searchOnlyResult = fchdir_nid_postfix(directory);
|
|
std::filesystem::permissions(root, originalPermissions);
|
|
if (searchDenied) Require(deniedResult == -1 && deniedError == EACCES && cwdUnchanged);
|
|
Require(searchOnlyResult == 0);
|
|
#endif
|
|
Require(sceKernelClose(fileDescriptor) == 0);
|
|
Require(sceKernelClose(directory) == 0);
|
|
Require(fchdir_nid_postfix(directory) == -1 && Error() == 9);
|
|
Require(chdir_nid_postfix("/") == 0);
|
|
Require(chroot_nid_postfix(nullptr) == -1 && Error() == 14);
|
|
Require(chroot_nid_postfix("") == -1 && Error() == 2);
|
|
Require(chroot_nid_postfix((root / "missing").string().c_str()) == -1 && Error() == 2);
|
|
Require(chroot_nid_postfix(file.c_str()) == -1 && Error() == 20);
|
|
Require(chroot_nid_postfix((root / "file.txt" / "child").string().c_str()) == -1 && Error() == 20);
|
|
Require(chroot_nid_postfix((file + "/").c_str()) == -1 && Error() == 20);
|
|
Require(chroot_nid_postfix((root / "file.txt" / "..").string().c_str()) == -1 && Error() == 20);
|
|
Require(chroot_nid_postfix((root / std::string(256, 'n')).string().c_str()) == -1 && Error() == 63);
|
|
Require(chroot_nid_postfix(("/" + std::string(1024, 'p')).c_str()) == -1 && Error() == 63);
|
|
Require(ChrootThrows(root.string()));
|
|
Require(ChrootThrows("/" + root.filename().string() + "/."));
|
|
const auto guestRoot = "/" + root.filename().string();
|
|
const auto chrootFile = guestRoot + "/file.txt";
|
|
const int top = sceKernelOpen("/", SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
|
|
Require(top >= 0);
|
|
Require(chdir_nid_postfix(guestRoot.c_str()) == 0);
|
|
Require(fchdir_nid_postfix(top) == 0 && chroot_nid_postfix(".") == 0);
|
|
Require(getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath && std::strcmp(directoryPath, "/") == 0);
|
|
Require(sceKernelClose(top) == 0);
|
|
Require(chroot_nid_postfix("/") == 0 && chroot_nid_postfix("/..") == 0 && chroot_nid_postfix("//./") == 0);
|
|
Require(chdir_nid_postfix(guestRoot.c_str()) == 0);
|
|
Require(chroot_nid_postfix("..") == 0 && chroot_nid_postfix("../..") == 0);
|
|
Require(getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath && guestRoot == directoryPath);
|
|
Require(ChrootThrows("."));
|
|
for (const auto& path : {std::string("file.txt"), chrootFile, "/../../.." + chrootFile, "../.." + chrootFile}) {
|
|
const int opened = sceKernelOpen(path.c_str(), SCE_KERNEL_O_RDONLY, 0);
|
|
char byte = 0;
|
|
Require(opened >= 0 && sceKernelRead(opened, &byte, 1) == 1 && byte == 'x');
|
|
Require(sceKernelClose(opened) == 0);
|
|
}
|
|
Require(chdir_nid_postfix("/") == 0);
|
|
Require(getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath && std::strcmp(directoryPath, "/") == 0);
|
|
Require(readlink_nid_postfix(file.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22);
|
|
const auto symbolic = (root / "symbolic").string();
|
|
const int created = symlink_nid_postfix("original.bin", symbolic.c_str());
|
|
if (created == 0) {
|
|
const auto dangling = (root / "dangling").string();
|
|
#ifdef _WIN32
|
|
Require((GetFileAttributesW(std::filesystem::path(symbolic).wstring().c_str()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0);
|
|
Require(symlink_nid_postfix("original.bin", symbolic.c_str()) == -1 && Error() == 17);
|
|
Require(symlink_nid_postfix("missing.bin", dangling.c_str()) == 0);
|
|
Require((GetFileAttributesW(std::filesystem::path(dangling).wstring().c_str()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0);
|
|
Require(symlink_nid_postfix("missing.bin", dangling.c_str()) == -1 && Error() == 17);
|
|
Require(readlink_nid_postfix(symbolic.c_str(), buffer, sizeof(buffer)) == 12 && std::memcmp(buffer, "original.bin", 12) == 0);
|
|
Require(readlink_nid_postfix(dangling.c_str(), buffer, sizeof(buffer)) == 11 && std::memcmp(buffer, "missing.bin", 11) == 0);
|
|
const auto nested = (root / "nested").string();
|
|
Require(symlink_nid_postfix("sub/target.txt", nested.c_str()) == 0);
|
|
std::memset(buffer, '#', sizeof(buffer));
|
|
Require(readlink_nid_postfix(nested.c_str(), buffer, sizeof(buffer)) == 14);
|
|
Require(std::memcmp(buffer, "sub/target.txt", 14) == 0 && buffer[14] == '#');
|
|
Require(readlink_nid_postfix(nested.c_str(), buffer, 3) == 3 && std::memcmp(buffer, "sub", 3) == 0);
|
|
Require(readlink_nid_postfix(nested.c_str(), nullptr, 0) == 0);
|
|
Require(readlink_nid_postfix(nested.c_str(), nullptr, sizeof(buffer)) == -1 && Error() == 14);
|
|
const auto accented = (root / "accented").string();
|
|
Require(symlink_nid_postfix("a\xc3\xa7\xc3\xa3o.bin", accented.c_str()) == 0);
|
|
Require(readlink_nid_postfix(accented.c_str(), buffer, sizeof(buffer)) == 10 && std::memcmp(buffer, "a\xc3\xa7\xc3\xa3o.bin", 10) == 0);
|
|
Require(std::filesystem::create_directory(root / "sub"));
|
|
const auto directoryLink = (root / "directory-link").string();
|
|
Require(symlink_nid_postfix("sub", directoryLink.c_str()) == 0);
|
|
Require(readlink_nid_postfix(directoryLink.c_str(), buffer, sizeof(buffer)) == 3 && std::memcmp(buffer, "sub", 3) == 0);
|
|
const auto absolute = std::filesystem::absolute(root / "absolute");
|
|
Require(CreateSymbolicLinkW(absolute.wstring().c_str(), std::filesystem::absolute(original).wstring().c_str(),
|
|
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0);
|
|
char absoluteBuffer[512];
|
|
std::memset(absoluteBuffer, '#', sizeof(absoluteBuffer));
|
|
const auto absoluteLength = readlink_nid_postfix((root / "absolute").string().c_str(), absoluteBuffer, sizeof(absoluteBuffer));
|
|
const bool absoluteFits = absoluteLength > 0 && static_cast<std::size_t>(absoluteLength) < sizeof(absoluteBuffer) &&
|
|
absoluteBuffer[absoluteLength] == '#';
|
|
const std::string absoluteTarget = absoluteFits ? std::string(absoluteBuffer, static_cast<std::size_t>(absoluteLength)) : std::string();
|
|
std::error_code absoluteError;
|
|
const bool absoluteMatches = absoluteFits && absoluteTarget.find('?') == std::string::npos &&
|
|
std::filesystem::equivalent(absoluteTarget, original, absoluteError) && !absoluteError;
|
|
if (!absoluteMatches)
|
|
std::fprintf(stderr, "absolute link returned %lld bytes\n", static_cast<long long>(absoluteLength));
|
|
Require(absoluteMatches);
|
|
#else
|
|
Require(std::filesystem::is_symlink(std::filesystem::symlink_status(symbolic)));
|
|
Require(readlink_nid_postfix(symbolic.c_str(), buffer, sizeof(buffer)) == 12 && std::memcmp(buffer, "original.bin", 12) == 0);
|
|
Require(std::filesystem::file_size(symbolic) == std::filesystem::file_size(original));
|
|
Require(symlink_nid_postfix("original.bin", symbolic.c_str()) == -1 && Error() == 17);
|
|
Require(symlink_nid_postfix("missing.bin", dangling.c_str()) == 0);
|
|
Require(readlink_nid_postfix(dangling.c_str(), buffer, sizeof(buffer)) == 11 && !std::filesystem::exists(dangling));
|
|
Require(symlink_nid_postfix("missing.bin", dangling.c_str()) == -1 && Error() == 17);
|
|
#endif
|
|
} else {
|
|
Require(created == -1 && Error() == 1);
|
|
std::fprintf(stderr, "symbolic links unavailable: the symlink success cases not tested\n");
|
|
}
|
|
Require(symlink_nid_postfix("original.bin", (root / "missing" / "symbolic").string().c_str()) == -1 && Error() == 2);
|
|
Require(symlink_nid_postfix(nullptr, symbolic.c_str()) == -1 && Error() == 14);
|
|
Require(symlink_nid_postfix("original.bin", "") == -1 && Error() == 2);
|
|
CheckSymlinkTargetRestrictions(root);
|
|
const int base = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
|
|
Require(base >= 0);
|
|
const int relative = openat_nid_postfix(base, "file.txt", SCE_KERNEL_O_RDONLY);
|
|
char byte = 0;
|
|
Require(relative >= 0 && sceKernelRead(relative, &byte, 1) == 1 && byte == 'x');
|
|
Require(sceKernelClose(relative) == 0);
|
|
const int made = openat_nid_postfix(base, "made.bin", SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL, 0644);
|
|
Require(made >= 0 && std::filesystem::is_regular_file(root / "made.bin"));
|
|
Require(sceKernelClose(made) == 0);
|
|
Require(openat_nid_postfix(base, "made.bin", SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL, 0644) == -1 && Error() == 17);
|
|
Require(openat_nid_postfix(base, "missing.bin", SCE_KERNEL_O_RDONLY) == -1 && Error() == 2);
|
|
const auto guestFile = "/" + root.filename().string() + "/file.txt";
|
|
const int absolute = openat_nid_postfix(-1, guestFile.c_str(), SCE_KERNEL_O_RDONLY);
|
|
Require(absolute >= 0 && sceKernelClose(absolute) == 0);
|
|
const int current = openat_nid_postfix(-100, file.c_str(), SCE_KERNEL_O_RDONLY);
|
|
Require(current >= 0 && sceKernelClose(current) == 0);
|
|
const int plain = sceKernelOpen(file.c_str(), SCE_KERNEL_O_RDONLY, 0);
|
|
Require(plain >= 0);
|
|
Require(openat_nid_postfix(plain, "file.txt", SCE_KERNEL_O_RDONLY) == -1 && Error() == 20);
|
|
Require(readlinkat_nid_postfix(base, "file.txt", buffer, sizeof(buffer)) == -1 && Error() == 22);
|
|
Require(readlinkat_nid_postfix(base, "missing.bin", buffer, sizeof(buffer)) == -1 && Error() == 2);
|
|
Require(readlinkat_nid_postfix(plain, "file.txt", buffer, sizeof(buffer)) == -1 && Error() == 20);
|
|
Require(readlinkat_nid_postfix(-1, "file.txt", buffer, sizeof(buffer)) == -1 && Error() == 9);
|
|
Require(readlinkat_nid_postfix(base, nullptr, buffer, sizeof(buffer)) == -1 && Error() == 14);
|
|
Require(readlinkat_nid_postfix(base, "", buffer, sizeof(buffer)) == -1 && Error() == 2);
|
|
Require(readlinkat_nid_postfix(-1, guestFile.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22);
|
|
Require(readlinkat_nid_postfix(-100, file.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22);
|
|
Require(mkdirat_nid_postfix(base, "dir", 0700) == 0 && std::filesystem::is_directory(root / "dir"));
|
|
Require(mkdirat_nid_postfix(base, "dir", 0700) == -1 && Error() == 17);
|
|
Require(mkdirat_nid_postfix(base, "file.txt", 0700) == -1 && Error() == 17);
|
|
Require(mkdirat_nid_postfix(base, "missing/child", 0700) == -1 && Error() == 2);
|
|
Require(mkdirat_nid_postfix(base, "file.txt/child", 0700) == -1 && Error() == 20);
|
|
Require(mkdirat_nid_postfix(base, "slash_dir/", 0700) == 0 && std::filesystem::is_directory(root / "slash_dir"));
|
|
const auto guestSubdir = "/" + root.filename().string() + "/abs_dir";
|
|
Require(mkdirat_nid_postfix(-1, guestSubdir.c_str(), 0700) == 0 && std::filesystem::is_directory(root / "abs_dir"));
|
|
const auto cwdDir = (root / "cwd_dir").string();
|
|
Require(mkdirat_nid_postfix(-100, cwdDir.c_str(), 0700) == 0 && std::filesystem::is_directory(root / "cwd_dir"));
|
|
Require(mkdirat_nid_postfix(plain, "child", 0700) == -1 && Error() == 20);
|
|
Require(mkdirat_nid_postfix(-1, "child", 0700) == -1 && Error() == 9);
|
|
Require(mkdirat_nid_postfix(base, nullptr, 0700) == -1 && Error() == 14);
|
|
Require(mkdirat_nid_postfix(base, "", 0700) == -1 && Error() == 2);
|
|
const std::string longName(256, 'a');
|
|
Require(mkdirat_nid_postfix(base, longName.c_str(), 0700) == -1 && Error() == 63);
|
|
Require(mkdirat_nid_postfix(base, "dir/../dotdot_dir", 0700) == 0 && std::filesystem::is_directory(root / "dotdot_dir"));
|
|
Require(unlinkat_nid_postfix(plain, "file.txt", 0) == -1 && Error() == 20);
|
|
Require(unlinkat_nid_postfix(-1, "file.txt", 0) == -1 && Error() == 9);
|
|
Require(sceKernelClose(plain) == 0);
|
|
Require(unlinkat_nid_postfix(base, "file.txt", 1) == -1 && Error() == 22);
|
|
Require(unlinkat_nid_postfix(base, nullptr, 1) == -1 && Error() == 22);
|
|
Require(unlinkat_nid_postfix(base, nullptr, 0) == -1 && Error() == 14);
|
|
Require(unlinkat_nid_postfix(base, "", 0) == -1 && Error() == 2);
|
|
Require(unlinkat_nid_postfix(base, "missing.bin", 0) == -1 && Error() == 2);
|
|
Require(unlinkat_nid_postfix(base, "missing.bin", 0x800) == -1 && Error() == 2);
|
|
Require(unlinkat_nid_postfix(base, "missing/child", 0) == -1 && Error() == 2);
|
|
Require(unlinkat_nid_postfix(base, "file.txt/child", 0) == -1 && Error() == 20);
|
|
Require(unlinkat_nid_postfix(base, "file.txt/child", 0x800) == -1 && Error() == 20);
|
|
Require(unlinkat_nid_postfix(base, "file.txt/", 0) == -1 && Error() == 20);
|
|
Require(unlinkat_nid_postfix(base, longName.c_str(), 0) == -1 && Error() == 63);
|
|
Require(unlinkat_nid_postfix(base, "file.txt", 0x800) == -1 && Error() == 20);
|
|
Require(unlinkat_nid_postfix(base, ".", 0x800) == -1 && Error() == 22);
|
|
Require(unlinkat_nid_postfix(base, "..", 0x800) == -1 && Error() == 22);
|
|
Require(unlinkat_nid_postfix(base, ".", 0) == -1 && Error() == 1);
|
|
std::filesystem::create_directory(root / "usub");
|
|
Require(unlinkat_nid_postfix(base, "usub", 0) == -1 && Error() == 1);
|
|
std::filesystem::create_directory(root / "nonempty");
|
|
{ std::ofstream out(root / "nonempty" / "f.bin"); out << 'x'; }
|
|
Require(unlinkat_nid_postfix(base, "nonempty", 0x800) == -1 && Error() == 66);
|
|
Require(unlinkat_nid_postfix(base, "nonempty/f.bin", 0) == 0 && !std::filesystem::exists(root / "nonempty" / "f.bin"));
|
|
Require(unlinkat_nid_postfix(base, "nonempty", 0x800) == 0 && !std::filesystem::exists(root / "nonempty"));
|
|
{ std::ofstream out(root / "to_unlink.bin"); out << 'x'; }
|
|
Require(unlinkat_nid_postfix(base, "to_unlink.bin", 0) == 0 && !std::filesystem::exists(root / "to_unlink.bin"));
|
|
{ std::ofstream out(root / "dotdot.bin"); out << 'x'; }
|
|
Require(unlinkat_nid_postfix(base, "usub/../dotdot.bin", 0) == 0 && !std::filesystem::exists(root / "dotdot.bin"));
|
|
std::filesystem::create_directory(root / "uslash_sub");
|
|
Require(unlinkat_nid_postfix(base, "uslash_sub/", 0x800) == 0 && !std::filesystem::exists(root / "uslash_sub"));
|
|
Require(unlinkat_nid_postfix(base, "usub", 0x800) == 0 && !std::filesystem::exists(root / "usub"));
|
|
{ std::ofstream out(root / "abs.bin"); out << 'x'; }
|
|
const auto guestAbsFile = "/" + root.filename().string() + "/abs.bin";
|
|
Require(unlinkat_nid_postfix(-1, guestAbsFile.c_str(), 0) == 0 && !std::filesystem::exists(root / "abs.bin"));
|
|
std::filesystem::create_directory(root / "unlink_abs_dir");
|
|
const auto guestAbsDir = "/" + root.filename().string() + "/unlink_abs_dir";
|
|
Require(unlinkat_nid_postfix(-1, guestAbsDir.c_str(), 0x800) == 0 && !std::filesystem::exists(root / "unlink_abs_dir"));
|
|
{ std::ofstream out(root / "cwd.bin"); out << 'x'; }
|
|
Require(unlinkat_nid_postfix(-100, (root / "cwd.bin").string().c_str(), 0) == 0 && !std::filesystem::exists(root / "cwd.bin"));
|
|
std::filesystem::create_directory(root / "unlink_cwd_dir");
|
|
Require(unlinkat_nid_postfix(-100, (root / "unlink_cwd_dir").string().c_str(), 0x800) == 0 && !std::filesystem::exists(root / "unlink_cwd_dir"));
|
|
Require(openat_nid_postfix(-1, "file.txt", SCE_KERNEL_O_RDONLY) == -1 && Error() == 9);
|
|
Require(openat_nid_postfix(base, nullptr, SCE_KERNEL_O_RDONLY) == -1 && Error() == 14);
|
|
Require(openat_nid_postfix(base, "", SCE_KERNEL_O_RDONLY) == -1 && Error() == 2);
|
|
Require(renameat_nid_postfix(base, nullptr, base, "renamed.txt") == -1 && Error() == 14);
|
|
Require(renameat_nid_postfix(base, "made.bin", base, nullptr) == -1 && Error() == 14);
|
|
Require(renameat_nid_postfix(base, "", base, "renamed.txt") == -1 && Error() == 2);
|
|
Require(renameat_nid_postfix(base, "made.bin", base, "") == -1 && Error() == 2);
|
|
const int bad = sceKernelOpen(file.c_str(), SCE_KERNEL_O_RDONLY, 0);
|
|
Require(bad >= 0);
|
|
Require(renameat_nid_postfix(-1, "made.bin", base, "renamed.txt") == -1 && Error() == 9);
|
|
Require(renameat_nid_postfix(base, "made.bin", -1, "renamed.txt") == -1 && Error() == 9);
|
|
Require(renameat_nid_postfix(bad, "made.bin", base, "renamed.txt") == -1 && Error() == 20);
|
|
Require(renameat_nid_postfix(base, "made.bin", bad, "renamed.txt") == -1 && Error() == 20);
|
|
Require(sceKernelClose(bad) == 0);
|
|
const auto longPart = std::string(256, 'y');
|
|
Require(renameat_nid_postfix(base, longPart.c_str(), base, "renamed.txt") == -1 && Error() == 63);
|
|
Require(renameat_nid_postfix(base, "made.bin", base, longPart.c_str()) == -1 && Error() == 63);
|
|
Require(renameat_nid_postfix(base, ".", base, "renamed.txt") == -1 && Error() == 22);
|
|
Require(renameat_nid_postfix(base, "..", base, "renamed.txt") == -1 && Error() == 22);
|
|
Require(renameat_nid_postfix(base, "made.bin", base, ".") == -1 && Error() == 22);
|
|
Require(renameat_nid_postfix(base, "made.bin", base, "..") == -1 && Error() == 22);
|
|
Require(renameat_nid_postfix(base, "missing.bin", base, "renamed.txt") == -1 && Error() == 2);
|
|
Require(renameat_nid_postfix(base, "made.bin", base, "renamed.bin") == 0);
|
|
Require(!std::filesystem::exists(root / "made.bin"));
|
|
Require(std::filesystem::is_regular_file(root / "renamed.bin"));
|
|
Require(renameat_nid_postfix(base, "renamed.bin", base, "renamed.bin") == 0);
|
|
const auto subPath = root / "sub_dir";
|
|
std::filesystem::create_directory(subPath);
|
|
Require(renameat_nid_postfix(base, "renamed.bin", base, "sub_dir") == -1 && Error() == 21);
|
|
Require(renameat_nid_postfix(base, "sub_dir", base, "renamed.bin") == -1 && Error() == 20);
|
|
Require(renameat_nid_postfix(base, "sub_dir", base, "sub_renamed") == 0);
|
|
Require(!std::filesystem::exists(subPath));
|
|
Require(std::filesystem::is_directory(root / "sub_renamed"));
|
|
const int subFd = sceKernelOpen((root / "sub_renamed").string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
|
|
Require(subFd >= 0);
|
|
Require(renameat_nid_postfix(base, "renamed.bin", subFd, "nested.bin") == 0);
|
|
Require(!std::filesystem::exists(root / "renamed.bin"));
|
|
Require(std::filesystem::is_regular_file(root / "sub_renamed" / "nested.bin"));
|
|
Require(renameat_nid_postfix(base, "sub_renamed", subFd, "child") == -1 && Error() == 22);
|
|
const auto otherDir = root / "other_dir";
|
|
std::filesystem::create_directory(otherDir);
|
|
{ std::ofstream stream(otherDir / "item.txt"); }
|
|
Require(renameat_nid_postfix(base, "sub_renamed", base, "other_dir") == -1 && Error() == 66);
|
|
Require(renameat_nid_postfix(subFd, "nested.bin/", base, "back.bin") == -1 && Error() == 20);
|
|
Require(renameat_nid_postfix(subFd, "nested.bin", base, "nonexistent/") == -1 && Error() == 20);
|
|
Require(renameat_nid_postfix(subFd, "nested.bin", base, "made.bin") == 0);
|
|
Require(std::filesystem::is_regular_file(root / "made.bin"));
|
|
Require(sceKernelClose(subFd) == 0);
|
|
Require(renameat_nid_postfix(-100, (root / "made.bin").string().c_str(), -100, (root / "final.bin").string().c_str()) == 0);
|
|
Require(std::filesystem::is_regular_file(root / "final.bin"));
|
|
Require(fchmodat_nid_postfix(base, "file.txt", 0600, 0x0200) == 0);
|
|
Require(fchmodat_nid_postfix(-100, (root / "file.txt").string().c_str(), 0644, 0) == 0);
|
|
#ifndef _WIN32
|
|
Require((std::filesystem::status(root / "file.txt").permissions() & std::filesystem::perms::all) ==
|
|
(std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::group_read | std::filesystem::perms::others_read));
|
|
#endif
|
|
Require(fchmodat_nid_postfix(base, "missing.bin", 0600, 0) == -1 && Error() == 2);
|
|
Require(fchmodat_nid_postfix(base, nullptr, 0600, 0) == -1 && Error() == 14);
|
|
Require(fchmodat_nid_postfix(base, "", 0600, 0) == -1 && Error() == 2);
|
|
Require(fchmodat_nid_postfix(base, "file.txt", 0600, 0x4000) == -1 && Error() == 22);
|
|
Require(fchmodat_nid_postfix(-1, "file.txt", 0600, 0) == -1 && Error() == 9);
|
|
#ifndef _WIN32
|
|
Require(symlink_nid_postfix("loop", (root / "loop").string().c_str()) == 0);
|
|
Require(fchmodat_nid_postfix(base, "loop/x", 0600, 0) == -1 && Error() == 62);
|
|
Require(fchmodat_nid_postfix(base, longName.c_str(), 0600, 0) == -1 && Error() == 63);
|
|
#endif
|
|
Require(fchown_nid_postfix(base, 0, 0) == 0);
|
|
Require(fchown_nid_postfix(base, 0xFFFFFFFFu, 0xFFFFFFFFu) == 0);
|
|
Require(fchown_nid_postfix(-1, 0, 0) == -1 && Error() == 9);
|
|
bool fchownThrew = false;
|
|
try { fchown_nid_postfix(base, 1, 0); } catch (const std::exception&) { fchownThrew = true; }
|
|
Require(fchownThrew);
|
|
fchownThrew = false;
|
|
try { fchown_nid_postfix(base, 0, 1); } catch (const std::exception&) { fchownThrew = true; }
|
|
Require(fchownThrew);
|
|
Require(sceKernelClose(base) == 0);
|
|
#ifdef _WIN32
|
|
const auto junctionTarget = root / "junction-target";
|
|
const auto junction = root / "junction";
|
|
Require(std::filesystem::create_directory(junctionTarget));
|
|
const auto junctionCommand = L"cmd /c mklink /J \"" + junction.wstring() + L"\" \"" + std::filesystem::absolute(junctionTarget).wstring() + L"\"";
|
|
Require(_wsystem(junctionCommand.c_str()) == 0);
|
|
char junctionBuffer[512];
|
|
std::memset(junctionBuffer, '#', sizeof(junctionBuffer));
|
|
const auto junctionLength = readlink_nid_postfix(junction.string().c_str(), junctionBuffer, sizeof(junctionBuffer));
|
|
Require(junctionLength > 0 && static_cast<std::size_t>(junctionLength) < sizeof(junctionBuffer) && junctionBuffer[junctionLength] == '#');
|
|
const std::string junctionText(junctionBuffer, static_cast<std::size_t>(junctionLength));
|
|
std::error_code junctionError;
|
|
Require(junctionText.find('?') == std::string::npos && std::filesystem::equivalent(junctionText, junctionTarget, junctionError) && !junctionError);
|
|
Require(std::filesystem::remove(junction) && std::filesystem::remove(junctionTarget));
|
|
#endif
|
|
#ifndef _WIN32
|
|
if (created == 0) {
|
|
const auto throughLink = (root / "through-link.bin").string();
|
|
Require(link_nid_postfix(symbolic.c_str(), throughLink.c_str()) == 0);
|
|
Require(!std::filesystem::is_symlink(std::filesystem::symlink_status(throughLink)));
|
|
Require(std::filesystem::equivalent(throughLink, original) && std::filesystem::hard_link_count(original) == 2);
|
|
Require(std::filesystem::remove(throughLink) && std::filesystem::hard_link_count(original) == 1);
|
|
Require(link_nid_postfix((root / "dangling").string().c_str(), throughLink.c_str()) == -1 && Error() == 2);
|
|
const auto directoryLink = (root / "directory-link").string();
|
|
Require(symlink_nid_postfix(".", directoryLink.c_str()) == 0);
|
|
Require(link_nid_postfix(directoryLink.c_str(), throughLink.c_str()) == -1 && Error() == 1);
|
|
Require(!std::filesystem::exists(std::filesystem::symlink_status(throughLink)));
|
|
}
|
|
#endif
|
|
RemoveTree(root);
|
|
return 0;
|
|
}
|