diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index dcd97deca..fee23a918 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #ifdef __linux__ #include @@ -47,6 +48,33 @@ constexpr int GuestEnametoolong = 63; constexpr int GuestEnotempty = 66; constexpr int GuestEoverflow = 84; constexpr int GuestAtFdcwd = -100; +constexpr std::size_t GuestPathMax = 1024; +constexpr std::size_t GuestNameMax = 255; + +constexpr int PcLinkMax = 1; +constexpr int PcMaxCanon = 2; +constexpr int PcMaxInput = 3; +constexpr int PcNameMax = 4; +constexpr int PcPathMax = 5; +constexpr int PcPipeBuf = 6; +constexpr int PcChownRestricted = 7; +constexpr int PcNoTrunc = 8; +constexpr int PcVdisable = 9; +constexpr int PcAllocSizeMin = 10; +constexpr int PcFileSizeBits = 12; +constexpr int PcRecIncrXferSize = 14; +constexpr int PcRecMaxXferSize = 15; +constexpr int PcRecMinXferSize = 16; +constexpr int PcRecXferAlign = 17; +constexpr int PcSymlinkMax = 18; +constexpr int PcMinHoleSize = 21; +constexpr int PcAsyncIo = 53; +constexpr int PcPrioIo = 54; +constexpr int PcSyncIo = 55; +constexpr int PcAclExtended = 59; +constexpr int PcAclPathMax = 60; +constexpr int PcMacPresent = 63; +constexpr int PcAclNfs4 = 64; std::int64_t Fail(int error) { *__error_nid_postfix() = error; @@ -130,6 +158,21 @@ std::int64_t ReadNativeLink(const std::filesystem::path& native, char* buffer, s if (error) return Fail(GuestError(error)); return CopyLinkTarget(target, buffer, size); } + +bool HasLongComponent(std::string_view path) { + for (std::size_t start = 0; start < path.size();) { + const auto end = std::min(path.find('/', start), path.size()); + if (end - start > GuestNameMax) { + if (start == 0) return true; + std::filesystem::path prefix; + if (ResolvePathError_nid_no_patch(std::string(path.substr(0, start)).c_str(), prefix)) return false; + std::error_code error; + return std::filesystem::is_directory(prefix, error); + } + start = end + 1; + } + return false; +} } extern "C" { @@ -174,6 +217,46 @@ std::int64_t APS5_VABI readlinkat_nid_postfix(int fd, const char* path, char* bu std::filesystem::path(reinterpret_cast(path)), buffer, size); } +std::int64_t APS5_VABI pathconf_nid_postfix(const char* path, int name) { + if (path == nullptr) return Fail(GuestEfault); + if (*path == '\0') return Fail(GuestEnoent); + if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return Fail(GuestEnametoolong); + std::filesystem::path native; + if (const int error = ResolvePathError_nid_no_patch(path, native)) return Fail(error); + std::error_code error; + const auto status = std::filesystem::status(native, error); + if (error) return Fail(GuestError(error)); + if (!std::filesystem::exists(status)) return Fail(GuestEnoent); + switch (name) { + case PcLinkMax: return 32767; + case PcNameMax: return static_cast(GuestNameMax); + case PcPathMax: return static_cast(GuestPathMax); + case PcPipeBuf: return 512; + case PcChownRestricted: return 1; + case PcNoTrunc: return 1; + case PcFileSizeBits: return 64; + case PcRecMaxXferSize: return -1; + case PcSymlinkMax: return static_cast(GuestPathMax); + case PcPrioIo: return 0; + case PcSyncIo: return 0; + case PcMaxCanon: + case PcMaxInput: + case PcVdisable: + case PcAllocSizeMin: + case PcRecIncrXferSize: + case PcRecMinXferSize: + case PcRecXferAlign: + case PcMinHoleSize: + case PcAsyncIo: + case PcAclExtended: + case PcAclPathMax: + case PcMacPresent: + case PcAclNfs4: + throw std::runtime_error("pathconf: name " + std::to_string(name) + " depends on the console filesystem or kernel configuration"); + default: return Fail(GuestEinval); + } +} + int APS5_VABI link_nid_postfix(const char* existing, const char* created) { if (existing == nullptr || created == nullptr) return static_cast(Fail(GuestEfault)); if (*existing == '\0' || *created == '\0') return static_cast(Fail(GuestEnoent)); diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index 46854c770..8f5a1b913 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -27,6 +27,7 @@ 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*); char* APS5_VABI getcwd_nid_postfix(char*, std::size_t); @@ -53,6 +54,48 @@ 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; +} + +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); +} + int main() { Require(setsid_nid_postfix() == -1 && Error() == 1); Require(umask_nid_postfix(0) == 022); @@ -63,6 +106,7 @@ int main() { Require(std::filesystem::create_directory(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] = {}; diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 6bababc45..4b2ca4352 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -4,6 +4,7 @@ - [fchdir](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) changes the guest working directory to the descriptor's directory: on Windows through the path recorded when the descriptor was opened, on Linux through the `/proc/self/fd` link target. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. FreeBSD `change_dir`'s search-permission check (`EACCES`) is not modelled, and a directory renamed after it was opened is still reached through the recorded path. Console behaviour is unverified - [openat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `open`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. As with `open`, `O_NOFOLLOW` and `O_NONBLOCK` are ignored. PPSA99003 opens directory entries this way. Console behaviour is unverified +- [pathconf](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) looks the path up as FreeBSD 11's `kern_pathconf` does (links followed, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255) and returns the constants of FreeBSD 11's UFS `ufs_pathconf` for files and directories alike: `_PC_LINK_MAX` 32767, `_PC_NAME_MAX` 255, `_PC_PATH_MAX` and `_PC_SYMLINK_MAX` 1024, `_PC_PIPE_BUF` 512, `_PC_CHOWN_RESTRICTED` and `_PC_NO_TRUNC` 1, `_PC_FILESIZEBITS` 64, `_PC_REC_MAX_XFER_SIZE` -1, `_PC_PRIO_IO` and `_PC_SYNC_IO` 0. Whether the console's filesystems report the same is not verified. It throws for the names whose UFS value depends on the mount (`_PC_ALLOC_SIZE_MIN`, `_PC_REC_INCR_XFER_SIZE`, `_PC_REC_MIN_XFER_SIZE`, `_PC_MIN_HOLE_SIZE`), the kernel options (`_PC_ACL_EXTENDED`, `_PC_ACL_PATH_MAX`, `_PC_ACL_NFS4`, `_PC_MAC_PRESENT`) or the page size (`_PC_REC_XFER_ALIGN`), and on which UFS and `vop_stdpathconf` disagree (`_PC_MAX_CANON`, `_PC_MAX_INPUT`, `_PC_VDISABLE`, `_PC_ASYNC_IO`); other names fail with `EINVAL` - [readlink](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) on Windows reads a symbolic link's target from its reparse data and returns it with `/` separators, only when the target is relative, as `symlink` creates them. A symbolic link with an absolute target and a junction throw, as a host path has no guest form. Any other reparse point throws too, where FreeBSD reports `EINVAL` for a file that is not a link - [readlinkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `readlink`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Console behaviour is unverified - [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask