feat(libkernel_sys): implement pathconf
Porpoise 2.7 (PPSA99764) stops at load on main 0f863c10:
FAIL: unresolved ELF import oc98+zOImus from libkernel_sys.prx
oc98+zOImus is pathconf (tools/nid_names.py, aerolib.csv at
zecoxao/sce_symbols 2883963a). It is exported from libkernel_sys next to
link and readlink.
The lookup follows FreeBSD 11's kern_pathconf and namei (release/11.0.0,
671fb7df): links are followed, a path of 1024 bytes or more or a
component over 255 fails with ENAMETOOLONG, a missing path with ENOENT.
The values are the constants of FreeBSD 11's UFS ufs_pathconf, which
vop_stdpathconf shares where both answer. Names whose UFS value depends
on the mount, the kernel options or the page size, or on which UFS and
vop_stdpathconf disagree, throw; other names fail with EINVAL. The
console's filesystem values are unverified and recorded in
TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
529f7bfd4a
commit
62f6764cc2
|
|
@ -8,6 +8,7 @@
|
|||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#ifdef __linux__
|
||||
#include <cerrno>
|
||||
|
|
@ -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<const char8_t*>(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<std::int64_t>(GuestNameMax);
|
||||
case PcPathMax: return static_cast<std::int64_t>(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<std::int64_t>(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<int>(Fail(GuestEfault));
|
||||
if (*existing == '\0' || *created == '\0') return static_cast<int>(Fail(GuestEnoent));
|
||||
|
|
|
|||
|
|
@ -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] = {};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue