mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
Merge pull request #9095 from mazen320/fix/unlinkat-windows-junction
fix(libkernel): treat Windows junctions in unlinkat like unlink and rmdir
This commit is contained in:
6 files changed
+79
-2
No files matched your search
@@ -469,7 +469,20 @@ int APS5_VABI sceKernelUnlink(const char* path) {
|
||||
if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error);
|
||||
constexpr int GuestEperm = 1;
|
||||
std::error_code error;
|
||||
if (std::filesystem::is_directory(std::filesystem::symlink_status(native, error))) return SceErrorFromErrno(GuestEperm);
|
||||
if (std::filesystem::is_directory(std::filesystem::symlink_status(native, error))) {
|
||||
#ifdef _WIN32
|
||||
const DWORD attributes = GetFileAttributesW(native.c_str());
|
||||
if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
|
||||
std::string junctionTarget;
|
||||
if (File::WindowsSymbolicLink::ReadJunction(native, junctionTarget, "unlink")) {
|
||||
if (RemoveDirectoryW(native.c_str()) == 0) return SceErrorFromErrno(GuestEperm);
|
||||
RecordWrittenPath_nid_no_patch(native);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return SceErrorFromErrno(GuestEperm);
|
||||
}
|
||||
if (NativeUnlink(native) != 0) {
|
||||
#ifdef _WIN32
|
||||
if (NativeSymbolicLinkLoop(native.parent_path())) return SCE_KERNEL_ERROR_ELOOP;
|
||||
|
||||
@@ -1610,6 +1610,13 @@ int APS5_VABI sceKernelRmdir(const char* path) {
|
||||
std::error_code error;
|
||||
if (std::filesystem::is_symlink(native, error)) return SceErrorFromErrno(GUEST_ENOTDIR);
|
||||
if (const int failure = PathLookupFailure(error)) return failure;
|
||||
#ifdef _WIN32
|
||||
const DWORD attributes = GetFileAttributesW(native.c_str());
|
||||
if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
|
||||
std::string junctionTarget;
|
||||
if (File::WindowsSymbolicLink::ReadJunction(native, junctionTarget, "rmdir")) return SceErrorFromErrno(GUEST_ENOTDIR);
|
||||
}
|
||||
#endif
|
||||
if (!std::filesystem::is_directory(native, error)) {
|
||||
#ifdef _WIN32
|
||||
if (File::WindowsSymbolicLinkLoop(WithoutTrailingSeparator(native).parent_path())) return SceKernelError(GUEST_ELOOP);
|
||||
|
||||
@@ -823,6 +823,13 @@ int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) {
|
||||
if (IsDotOrDotDot(path)) return static_cast<int>(Fail(GuestEinval));
|
||||
if (flag & GuestAtRemovedir) {
|
||||
if (std::filesystem::is_symlink(symlinkStatus)) return static_cast<int>(Fail(GuestEnotdir));
|
||||
#ifdef _WIN32
|
||||
const DWORD attributes = GetFileAttributesW(native.c_str());
|
||||
if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
|
||||
std::string junctionTarget;
|
||||
if (File::WindowsSymbolicLink::ReadJunction(native, junctionTarget, "unlinkat")) return static_cast<int>(Fail(GuestEnotdir));
|
||||
}
|
||||
#endif
|
||||
if (!std::filesystem::is_directory(symlinkStatus)) {
|
||||
if (HasNonDirectoryPrefix(native) || std::filesystem::exists(symlinkStatus))
|
||||
return static_cast<int>(Fail(GuestEnotdir));
|
||||
@@ -852,6 +859,17 @@ int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) {
|
||||
return static_cast<int>(Fail(GuestEnotdir));
|
||||
return static_cast<int>(Fail(GuestEnoent));
|
||||
}
|
||||
#ifdef _WIN32
|
||||
const DWORD attributes = GetFileAttributesW(native.c_str());
|
||||
if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
|
||||
std::string junctionTarget;
|
||||
if (File::WindowsSymbolicLink::ReadJunction(native, junctionTarget, "unlinkat")) {
|
||||
if (RemoveDirectoryW(native.c_str()) == 0) return static_cast<int>(Fail(GuestEperm));
|
||||
RecordWrittenPath_nid_no_patch(native);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (std::filesystem::is_directory(symlinkStatus)) return static_cast<int>(Fail(GuestEperm));
|
||||
#ifdef _WIN32
|
||||
struct _stat64 status{};
|
||||
|
||||
@@ -1194,6 +1194,22 @@ int main() {
|
||||
nullptr, 0, &returned, nullptr) != 0);
|
||||
Require(CloseHandle(taggedCleanup) != 0 && std::filesystem::remove(tagged));
|
||||
}
|
||||
|
||||
const auto fullTarget = root / "junction-full-target";
|
||||
const auto fullJunction = root / "junction-full";
|
||||
Require(std::filesystem::create_directory(fullTarget));
|
||||
FILE* const inside = std::fopen((fullTarget / "inside.txt").string().c_str(), "wb");
|
||||
Require(inside != nullptr);
|
||||
std::fclose(inside);
|
||||
const auto fullCommand = L"cmd /c mklink /J \"" + fullJunction.wstring() + L"\" \"" + fullTarget.wstring() + L"\"";
|
||||
Require(_wsystem(fullCommand.c_str()) == 0);
|
||||
Require(rmdir_nid_postfix(fullJunction.string().c_str()) == -1 && *__error_nid_postfix() == 20);
|
||||
Require(sceKernelRmdir(fullJunction.string().c_str()) == static_cast<int>(0x80020014u));
|
||||
Require(std::filesystem::is_regular_file(fullTarget / "inside.txt"));
|
||||
Require(sceKernelUnlink(fullJunction.string().c_str()) == 0);
|
||||
Require(!std::filesystem::exists(std::filesystem::symlink_status(fullJunction)));
|
||||
Require(std::filesystem::is_regular_file(fullTarget / "inside.txt"));
|
||||
Require(std::filesystem::remove(fullTarget / "inside.txt") && std::filesystem::remove(fullTarget));
|
||||
#endif
|
||||
FileStat identity{};
|
||||
FileStat byDescriptor{};
|
||||
|
||||
@@ -972,6 +972,29 @@ int main() {
|
||||
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);
|
||||
#ifdef _WIN32
|
||||
const auto fullJunctionTarget = root / "junction-full-target";
|
||||
Require(std::filesystem::create_directory(fullJunctionTarget));
|
||||
{ std::ofstream out(fullJunctionTarget / "inside.txt"); out << 'x'; }
|
||||
const auto fullJunctionCommand = L"cmd /c mklink /J \"" + (root / "junction-full").wstring() + L"\" \"" + std::filesystem::absolute(fullJunctionTarget).wstring() + L"\"";
|
||||
Require(_wsystem(fullJunctionCommand.c_str()) == 0);
|
||||
Require(unlinkat_nid_postfix(base, "junction-full", 0x800) == -1 && Error() == 20);
|
||||
Require(std::filesystem::is_regular_file(fullJunctionTarget / "inside.txt"));
|
||||
Require(unlinkat_nid_postfix(base, "junction-full", 0) == 0);
|
||||
Require(!std::filesystem::exists(std::filesystem::symlink_status(root / "junction-full")));
|
||||
Require(std::filesystem::is_regular_file(fullJunctionTarget / "inside.txt"));
|
||||
Require(std::filesystem::remove(fullJunctionTarget / "inside.txt") && std::filesystem::remove(fullJunctionTarget));
|
||||
{ std::ofstream out(root / "symlink-target.bin"); out << 'x'; }
|
||||
const int madeLink = symlink_nid_postfix("symlink-target.bin", (root / "symlink-file").string().c_str());
|
||||
if (madeLink == 0) {
|
||||
Require(unlinkat_nid_postfix(base, "symlink-file", 0) == 0);
|
||||
Require(!std::filesystem::exists(std::filesystem::symlink_status(root / "symlink-file")));
|
||||
Require(std::filesystem::is_regular_file(root / "symlink-target.bin"));
|
||||
Require(std::filesystem::remove(root / "symlink-target.bin"));
|
||||
} else {
|
||||
Require(std::filesystem::remove(root / "symlink-target.bin"));
|
||||
}
|
||||
#endif
|
||||
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'; }
|
||||
|
||||
@@ -155,7 +155,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`. Directory timestamp updates resolve the current path of the opened handle. Filesystems with coarser timestamp resolution cannot retain every guest microsecond. Birth-time clamping is not atomic with concurrent host timestamp updates and has not been measured on a console
|
||||
- [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 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. `open` with `O_NOFOLLOW` returns `EMLINK` for that junction and does not open the target
|
||||
- [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 treated as a link only when `ReadJunction` in [WindowsSymbolicLink.hpp](../../../core/libs/prx/libkernel/File/include/WindowsSymbolicLink.hpp) accepts a drive-path target (`X:\...`) after stripping `\??\` / `\\?\`; volume mounts and other non-drive substitutes are not. Consumers of that drive-target path: [NativeStat.cpp](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (`lstat`/`readlink`), [Open.cpp](../../../core/libs/prx/libkernel/File/src/Open.cpp) (`O_NOFOLLOW` → `EMLINK`, and `unlink` removing the junction), [Stdio.cpp](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (`rmdir` → `ENOTDIR`), and [Export.cpp](../../../core/libs/prx/libkernel_sys/Export.cpp) (`unlinkat` with and without `AT_REMOVEDIR`). `unlink` / plain `unlinkat` leave the target, including when it is not empty
|
||||
- [lstat of a Windows reparse point that is not a link](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) reports a mounted volume (`\??\Volume{GUID}\`) or another name surrogate as the reparse point itself, not followed, with the mode of its attributes, as CPython's `os.lstat` does, and a reparse point without the name surrogate bit (deduplication, cloud files) as `stat` reports it
|
||||
- [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.
|
||||
|
||||
Reference in new issue
Block a user