Files
bluandClaude Opus 5.5 0ecef87c4c fix(libkernel): answer Windows reparse points that are not links
lstat and readlink threw NotImplemented for every Windows reparse point
other than a symbolic link or a drive junction. A tag without the name
surrogate bit (cloud files, deduplication) does not stand for another path,
so lstat now answers as stat does and readlink returns EINVAL, as FreeBSD
does for a file that is not a link and as CPython's os.lstat and os.readlink
do (bpo-37834). A name surrogate that is neither a symbolic link nor a
junction (a mounted volume, a WSL link) is reported as the reparse point
itself with the mode of its attributes, and readlink returns EINVAL.

The tests set reparse tags with FSCTL_SET_REPARSE_POINT, one without the
name surrogate bit and one with it. A compact /exe (WOF) file cannot serve
as the fixture: on Windows 11 24H2 the WOF filter hides the reparse point
from GetFileAttributesW and FileAttributeTagInfo alike. Windows refuses
every open of an unhandled tag without FILE_FLAG_OPEN_REPARSE_POINT, so the
test requires lstat to agree with stat rather than to succeed.

mashingaan's boykopovar/AnyPS5#6447 returned EINVAL for other reparse
points without the tag check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-20 08:21:33 -07:00

9.9 KiB

libkernel_sys technical debt

Silent stubs

  • fchmodat (libkernel_sys) - AT_SYMLINK_NOFOLLOW on a symbolic link (on Windows and Linux)

Functional

  • fchdir (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. Outside Windows it checks search permission on the directory with faccessat, which fails with EACCES as FreeBSD change_dir does; on Windows that check is not modelled. A directory renamed after it was opened is still reached through the recorded path. Console behaviour is unverified
  • openat (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
  • fchmodat (libkernel_sys) passes AT_FDCWD (-100) and absolute paths to chmod; any other relative path is joined to the descriptor's host directory the same way as for openat, so guest path aliases and case-insensitive name matching do not apply to it and a .. component is not kept inside the guest root. A flag other than 0 or AT_SYMLINK_NOFOLLOW (0x200) returns EINVAL; the mode is masked to 07777. With AT_SYMLINK_NOFOLLOW a symbolic link returns success silently on Windows and Linux, since the host cannot change a link's own mode, while a regular file is changed either way. The mode maps to the host file mode, which on Windows is only the CRT's read-only bit. PPSA99621 calls it through std::filesystem::permissions. Console behaviour is unverified
  • pathconf (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
  • mkdirat (libkernel_sys) passes AT_FDCWD (-100) and absolute paths to mkdir. 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. On Windows, mode is ignored. Console behaviour is unverified
  • readlink (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 returns its print name, or its substitute name when the print name is empty, without a \??\ or \\?\ prefix. A directory junction returns the same kind of name when it is a drive path (X:\...), using the print name when that is a drive path and the substitute name otherwise. A mounted volume (\??\Volume{GUID}\), any other name surrogate and every reparse point without the name surrogate bit (WOF compression, deduplication, cloud files) return EINVAL, as FreeBSD does for a file that is not a link and as CPython's os.readlink, which reads only symbolic links and junctions, does
  • readlinkat (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
  • fstatat (libkernel_sys) passes AT_FDCWD (-100) and absolute paths to stat, or to lstat with AT_SYMLINK_NOFOLLOW (0x200). 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, and flags other than AT_SYMLINK_NOFOLLOW return EINVAL. PPSA74126 imports it. Console behaviour is unverified
  • unlinkat (libkernel_sys) passes AT_FDCWD (-100) and absolute paths to rmdir (with AT_REMOVEDIR, 0x800) or unlink. 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. Removing a directory without AT_REMOVEDIR fails with EPERM, removing . or .. with AT_REMOVEDIR fails with EINVAL, and flags other than AT_REMOVEDIR fail with EINVAL. Console behaviour is unverified
  • renameat (libkernel_sys) passes AT_FDCWD (-100) and absolute paths to rename. Any other relative path is joined to its descriptor's host directory, found the same way as for fchdir, so guest path aliases and case-insensitive name matching do not apply to relative descriptors, 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 (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 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 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
  • chroot (libkernel_sys) follows FreeBSD 11.4's sys_chroot and pwd_chroot for a root caller: EFAULT for a null path, ENOENT for an empty or missing one, ENAMETOOLONG for a path of 1024 bytes or more or a component over 255, and ENOTDIR for a path that is not a directory or passes through a file. A directory that is the guest root (/, /.., or . after fchdir to a descriptor of /) succeeds and changes nothing, as the root stays the same directory; the guest is taken never to have changed its root, so open directory descriptors do not make it fail with EPERM (kern.chroot_allow_open_directories 1). Any other directory throws, naming the path: the guest path resolver has a fixed root and mount aliases (/app0, /savedata0) that do not move with it. On the console a title starts in a sandbox root, where pwd_chroot refuses with EPERM while a directory is open; whether that holds for titles, and whether a title holds PRIV_VFS_CHROOT, are unverified. Porpoise (PPSA99764) weak-imports it and calls it only as fchdir to its saved descriptor of / then chroot("."), to put its sandbox root back after a jailbreak daemon replaced it (put_root_back)
  • fchown (libkernel_sys) - ownership is not modelled: the guest's own owner and group (0) and the unchanged value (-1) are accepted, a descriptor that is not open fails with EBADF, and any other owner or group throws, naming it. The check reads the descriptor with fstat, ignoring the CRT's invalid-parameter handler on Windows as DescriptorControl does. The console's ownership rules are unverified. Morrowind (mshivam019/morrowind-ps5) imports it