mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
Merge pull request #7273 from mazen320/fix/windows-junction
fix(libkernel): report Windows directory junctions
This commit is contained in:
7 files changed
+110
-37
No files matched your search
@@ -26,8 +26,28 @@ class WindowsSymbolicLink {
|
||||
};
|
||||
static constexpr ULONG relativeFlag = 1;
|
||||
|
||||
static std::wstring TextAt(const std::byte* data, DWORD size, std::size_t origin, USHORT offset, USHORT length, const char* operation, const char* malformed) {
|
||||
const std::size_t begin = origin + offset;
|
||||
if (length % sizeof(wchar_t) != 0 || begin + length > size)
|
||||
throw std::runtime_error(std::string(operation) + ": " + malformed);
|
||||
std::wstring name(length / sizeof(wchar_t), L'\0');
|
||||
if (!name.empty()) std::memcpy(name.data(), data + begin, name.size() * sizeof(wchar_t));
|
||||
return name;
|
||||
}
|
||||
|
||||
static std::wstring WithoutNtPrefix(std::wstring name) {
|
||||
if (name.rfind(L"\\??\\", 0) == 0 || name.rfind(L"\\\\?\\", 0) == 0) name.erase(0, 4);
|
||||
return name;
|
||||
}
|
||||
|
||||
static bool IsDriveAbsolute(const std::wstring& name) {
|
||||
if (name.size() < 3 || name[1] != L':' || (name[2] != L'\\' && name[2] != L'/')) return false;
|
||||
const wchar_t letter = name[0];
|
||||
return (letter >= L'A' && letter <= L'Z') || (letter >= L'a' && letter <= L'z');
|
||||
}
|
||||
|
||||
public:
|
||||
static bool ReadRelative(const std::filesystem::path& native, std::string& target, const char* operation) {
|
||||
static bool Read(const std::filesystem::path& native, std::string& target, const char* operation) {
|
||||
const HANDLE handle = CreateFileW(native.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
@@ -42,12 +62,47 @@ public:
|
||||
ReparseData header{};
|
||||
if (size < sizeof(header)) return false;
|
||||
std::memcpy(&header, data.data(), sizeof(header));
|
||||
if (header.tag != IO_REPARSE_TAG_SYMLINK || (header.flags & relativeFlag) == 0) return false;
|
||||
const std::size_t begin = sizeof(header) + header.substituteNameOffset;
|
||||
if (begin + header.substituteNameLength > size)
|
||||
throw std::runtime_error(std::string(operation) + ": malformed symbolic link reparse data");
|
||||
std::wstring name(header.substituteNameLength / sizeof(wchar_t), L'\0');
|
||||
std::memcpy(name.data(), data.data() + begin, name.size() * sizeof(wchar_t));
|
||||
if (header.tag != IO_REPARSE_TAG_SYMLINK) return false;
|
||||
const bool relative = (header.flags & relativeFlag) != 0;
|
||||
std::wstring name = relative
|
||||
? TextAt(data.data(), size, sizeof(ReparseData), header.substituteNameOffset, header.substituteNameLength, operation, "malformed symbolic link reparse data")
|
||||
: TextAt(data.data(), size, sizeof(ReparseData), header.printNameOffset, header.printNameLength, operation, "malformed symbolic link reparse data");
|
||||
if (!relative && name.empty())
|
||||
name = WithoutNtPrefix(TextAt(data.data(), size, sizeof(ReparseData), header.substituteNameOffset, header.substituteNameLength, operation, "malformed symbolic link reparse data"));
|
||||
target = std::filesystem::path(name).generic_string();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ReadJunction(const std::filesystem::path& native, std::string& target, const char* operation) {
|
||||
const HANDLE handle = CreateFileW(native.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), std::string(operation) + ": CreateFileW");
|
||||
std::array<std::byte, MAXIMUM_REPARSE_DATA_BUFFER_SIZE> data{};
|
||||
DWORD size = 0;
|
||||
const BOOL read = DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, nullptr, 0, data.data(), static_cast<DWORD>(data.size()), &size, nullptr);
|
||||
const DWORD code = GetLastError();
|
||||
CloseHandle(handle);
|
||||
if (!read)
|
||||
throw std::system_error(static_cast<int>(code), std::system_category(), std::string(operation) + ": FSCTL_GET_REPARSE_POINT");
|
||||
struct MountPoint {
|
||||
ULONG tag;
|
||||
USHORT dataLength;
|
||||
USHORT reserved;
|
||||
USHORT substituteNameOffset;
|
||||
USHORT substituteNameLength;
|
||||
USHORT printNameOffset;
|
||||
USHORT printNameLength;
|
||||
} header{};
|
||||
if (size < sizeof(header)) return false;
|
||||
std::memcpy(&header, data.data(), sizeof(header));
|
||||
if (header.tag != IO_REPARSE_TAG_MOUNT_POINT) return false;
|
||||
const auto nameAt = [&](USHORT offset, USHORT length) {
|
||||
return WithoutNtPrefix(TextAt(data.data(), size, sizeof(header), offset, length, operation, "malformed junction reparse data"));
|
||||
};
|
||||
std::wstring name = nameAt(header.printNameOffset, header.printNameLength);
|
||||
if (!IsDriveAbsolute(name)) name = nameAt(header.substituteNameOffset, header.substituteNameLength);
|
||||
if (!IsDriveAbsolute(name)) return false;
|
||||
target = std::filesystem::path(name).generic_string();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ static int DoLstat(const std::filesystem::path& p, NativeStat* st) {
|
||||
const auto attributes = GetFileAttributesW(p.c_str());
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) return DoStat(p, st);
|
||||
std::string target;
|
||||
if (!File::WindowsSymbolicLink::ReadRelative(p, target, "lstat"))
|
||||
NotImplemented_nid_no_patch("lstat of a Windows reparse point other than a relative symbolic link");
|
||||
if (!File::WindowsSymbolicLink::Read(p, target, "lstat") && !File::WindowsSymbolicLink::ReadJunction(p, target, "lstat"))
|
||||
NotImplemented_nid_no_patch("lstat of a Windows reparse point other than a symbolic link");
|
||||
const auto handle = CreateFileW(p.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE) return File::WindowsFileTime::Failure(GetLastError());
|
||||
|
||||
@@ -130,8 +130,8 @@ std::int64_t ReadNativeLink(const std::filesystem::path& native, char* buffer, s
|
||||
const DWORD attributes = GetFileAttributesW(native.wstring().c_str());
|
||||
if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
|
||||
std::string target;
|
||||
if (!File::WindowsSymbolicLink::ReadRelative(native, target, "readlink")) {
|
||||
NotImplemented_nid_no_patch("readlink of a Windows reparse point other than a relative symbolic link");
|
||||
if (!File::WindowsSymbolicLink::Read(native, target, "readlink") && !File::WindowsSymbolicLink::ReadJunction(native, target, "readlink")) {
|
||||
NotImplemented_nid_no_patch("readlink of a Windows reparse point other than a symbolic link");
|
||||
return -1;
|
||||
}
|
||||
return CopyLinkTarget(target, buffer, size);
|
||||
|
||||
@@ -775,27 +775,25 @@ int main() {
|
||||
Require(stat_nid_postfix(fileLink.string().c_str(), &followed) == 0 && (followed.st_mode & 0170000) == 0100000);
|
||||
Require(followed.st_ino == status.st_ino && linkStatus.st_ino != status.st_ino && linkStatus.st_dev == status.st_dev);
|
||||
Require(stat_nid_postfix(directoryLink.string().c_str(), &followed) == 0 && (followed.st_mode & 0170000) == 0040000);
|
||||
const auto checkUnsupportedLink = [](const std::filesystem::path& link) {
|
||||
FileStat untouched;
|
||||
std::memset(&untouched, 0x5a, sizeof(untouched));
|
||||
constexpr int sentinelError = 123;
|
||||
*__error_nid_postfix() = sentinelError;
|
||||
bool rejected = false;
|
||||
try {
|
||||
lstat_nid_postfix(link.string().c_str(), &untouched);
|
||||
} catch (const std::runtime_error& error) {
|
||||
rejected = std::strcmp(error.what(), "lstat of a Windows reparse point other than a relative symbolic link not implemented") == 0;
|
||||
}
|
||||
const bool errorUnchanged = *__error_nid_postfix() == sentinelError;
|
||||
const auto* bytes = reinterpret_cast<const unsigned char*>(&untouched);
|
||||
const bool statusUnchanged = std::all_of(bytes, bytes + sizeof(untouched), [](unsigned char value) { return value == 0x5a; });
|
||||
return rejected && errorUnchanged && statusUnchanged;
|
||||
};
|
||||
Require(stat_nid_postfix(fileLink.string().c_str(), &status) == 0 && status.st_size == 5);
|
||||
Require(checkUnsupportedLink(absoluteLink));
|
||||
FileStat absoluteStatus{};
|
||||
Require(lstat_nid_postfix(absoluteLink.string().c_str(), &absoluteStatus) == 0);
|
||||
Require((absoluteStatus.st_mode & 0170000) == 0120000 && absoluteStatus.st_size > 0 && absoluteStatus.st_nlink == 1);
|
||||
Require(stat_nid_postfix(absoluteLink.string().c_str(), &followed) == 0 && followed.st_ino == status.st_ino);
|
||||
Require(std::filesystem::remove(fileLink) && std::filesystem::remove(directoryLink) && DeleteFileW(danglingLink.c_str()) != 0 &&
|
||||
std::filesystem::remove(absoluteLink) && std::filesystem::remove(targetDirectory));
|
||||
}
|
||||
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"\" \"" + junctionTarget.wstring() + L"\"";
|
||||
Require(_wsystem(junctionCommand.c_str()) == 0);
|
||||
FileStat junctionStatus{};
|
||||
Require(lstat_nid_postfix(junction.string().c_str(), &junctionStatus) == 0);
|
||||
Require((junctionStatus.st_mode & 0170000) == 0120000 && junctionStatus.st_size > 0);
|
||||
FileStat junctionFollowed{};
|
||||
Require(stat_nid_postfix(junction.string().c_str(), &junctionFollowed) == 0 && (junctionFollowed.st_mode & 0170000) == 0040000);
|
||||
Require(std::filesystem::remove(junction) && std::filesystem::remove(junctionTarget));
|
||||
#endif
|
||||
FileStat identity{};
|
||||
FileStat byDescriptor{};
|
||||
|
||||
@@ -289,13 +289,18 @@ int main() {
|
||||
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);
|
||||
bool refused = false;
|
||||
try {
|
||||
readlink_nid_postfix((root / "absolute").string().c_str(), buffer, sizeof(buffer));
|
||||
} catch (const std::runtime_error& failure) {
|
||||
refused = std::string(failure.what()) == "readlink of a Windows reparse point other than a relative symbolic link not implemented";
|
||||
}
|
||||
Require(refused);
|
||||
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);
|
||||
@@ -465,6 +470,21 @@ int main() {
|
||||
Require(fchmodat_nid_postfix(base, longName.c_str(), 0600, 0) == -1 && Error() == 63);
|
||||
#endif
|
||||
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();
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
- [Windows file timestamps](../../../core/libs/prx/libkernel/File/include/WindowsFileTime.hpp) use the host filesystem's timestamp range and resolution. `utimes`, `futimes`, and `lutimes` reject timestamps outside nonnegative signed 64-bit Windows file-time ticks with `EINVAL`, including exactly 1601-01-01 00:00:00 UTC, whose zero value means "leave unchanged" to `SetFileTime`. Guest directory descriptors still resolve through their recorded path. Filesystems with coarser timestamp resolution cannot retain every guest microsecond
|
||||
- [EVFILT_VNODE on macOS](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) (libkernel) registers `EVFILT_VNODE` on a duplicate of the descriptor in a host kqueue, whose notes have FreeBSD's values, and drops the `NOTE_LINK` that XNU adds to `NOTE_DELETE` when the watched file or directory is removed, since FreeBSD's `vop_remove_post` and `vop_rmdir_post` report `NOTE_DELETE` alone. As on Windows, a directory registered with `NOTE_EXTEND` is accepted but never receives it: XNU reports a move between directories as `NOTE_WRITE` alone, with `NOTE_LINK` for a subdirectory
|
||||
- [Windows file identity](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) - `stat` and `fstat` report the volume serial number as `st_dev` and the 64-bit file index folded into the guest's 32-bit `st_ino` (never 0), so two different files can in principle share an `st_ino`; the fold is not taken from a reference. `st_nlink` is clamped to 65535, the width of the guest field. The call keeps the `_wstat64` values when `GetFileInformationByHandle` fails
|
||||
- [lstat of a Windows symbolic link](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) reports a link whose target is relative, as `symlink` creates them, with the link's own times, `st_dev` and `st_ino`, mode `S_IFLNK | 0777` and the length of the target `readlink` returns as `st_size`. FreeBSD gives a new link `0777` less the creating process's umask, which the host does not keep, so the permission bits are not the console's. A link with an absolute target, a junction and any other reparse point throw, as for `readlink`
|
||||
- [lstat of a Windows symbolic link](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) reports a link whose target is relative, as `symlink` creates them, with the link's own times, `st_dev` and `st_ino`, mode `S_IFLNK | 0777` and the length of the target `readlink` returns as `st_size`. FreeBSD gives a new link `0777` less the creating process's umask, which the host does not keep, so the permission bits are not the console's. A link with an absolute target is reported the same way, using its print name, or its substitute name when the print name is empty, without a `\??\` or `\\?\` prefix. A directory junction is reported the same way when the name left after that prefix is a drive path (`X:\...`): the print name when that name is a drive path, otherwise the substitute name. A mounted volume, whose substitute name is `\??\Volume{GUID}\`, and any other reparse point throw, as for `readlink`
|
||||
- [setitimer](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) follows FreeBSD's `sys_setitimer` and `kern_setitimer`: `EINVAL` for timers other than `ITIMER_REAL`, `ITIMER_VIRTUAL` and `ITIMER_PROF`, and for a value, or the interval of a non-zero value, with a negative field, `tv_usec` of 1000000 or more, or more than `INT32_MAX / 2` seconds; a null value reads the timer as `getitimer` does, with `EFAULT` when the output is null too. `ITIMER_REAL` shares the timer thread of `alarm`, which clears its interval: a zero value disarms it, the previous value is returned, and each expiry re-arms it by `it_interval`. With `SIG_IGN` an expiry is dropped. With `SIGALRM` blocked it is left on the pending set of `sigtimedwait`, as `kern_psignal` does, where `sigwait`, `sigtimedwait` and `sigwaitinfo` take it; unblocking it runs a guest handler on the unblocking thread, and with the default disposition exits the process with status 142. Unblocked, the default disposition exits the process with status 142 and a handler throws, since guest `SIGALRM` handlers are never run from the timer thread. The value is not raised to `tick / 16` as `itimerfix` does, and an interval below 1 ms throws, since the console's `hz`, and so that rounding, is unknown (1 ms covers any `hz` of 63 or more). `ITIMER_VIRTUAL` and `ITIMER_PROF` throw when armed, as process CPU time is not accounted, and read and disarm as zero. PPSA99420 imports it and fails to load without it
|
||||
- On Linux, [direct memory](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) lives in one sparse memfd at file offset = physical address, so the [GPU imports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) can share one udmabuf buffer object per 64 MiB of it. Released direct memory is not punched out of the file, because a live import of its chunk pins the pages and the GPU would keep reading the old ones: its pages stay committed (the process keeps the high-water mark of the direct memory it used) and are zeroed when the range is allocated again.
|
||||
- [system](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) runs no command. It follows FreeBSD 11's `lib/libc/stdlib/system.c` for a sandbox without `/bin/sh`: a null command returns 1 unconditionally, as that version does, and any command returns the wait status of a child that could not exec the shell and called `_exit(127)` (`0x7f00`). Whether the console's application sandbox has no shell, and whether `vfork` succeeds there (FreeBSD returns -1 when it fails), is unverified
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
- [fchmodat](../../../core/libs/prx/libkernel_sys/Export.cpp) (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](../../../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`
|
||||
- [mkdirat](../../../core/libs/prx/libkernel_sys/Export.cpp) (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](../../../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
|
||||
- [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 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, whose substitute name is `\??\Volume{GUID}\`, stays a throw, because the name left after the prefix is not a drive path. 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
|
||||
- [unlinkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (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](../../../core/libs/prx/libkernel_sys/Export.cpp) (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
|
||||
|
||||
Reference in new issue
Block a user