diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index b00d71b5d470..0dff46deabb1 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -91,16 +91,7 @@ static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) { (void)mode; return ::_wmkdir(path.wstring().c_str()); } -static int NativeChmod(const std::filesystem::path& path, int mode) { - RecordWrittenPath_nid_no_patch(path); - return ::_wchmod(path.wstring().c_str(), mode); -} -std::optional File::WindowsDescriptorPath_nid_no_patch(int descriptor) { - const auto handle = reinterpret_cast(::_get_osfhandle(descriptor)); - if (handle == INVALID_HANDLE_VALUE) { - errno = EBADF; - return std::nullopt; - } +static std::optional HandlePath(HANDLE handle) { std::wstring path(MAX_PATH, L'\0'); auto length = ::GetFinalPathNameByHandleW(handle, path.data(), static_cast(path.size()), FILE_NAME_NORMALIZED); if (length >= path.size()) { @@ -120,6 +111,32 @@ std::optional File::WindowsDescriptorPath_nid_no_patch(in else if (path.starts_with(LR"(\\?\)") && path.size() > 6 && path[5] == L':' && path[6] == L'\\') path.erase(0, 4); return path; } +std::optional File::WindowsDescriptorPath_nid_no_patch(int descriptor) { + const auto handle = reinterpret_cast(::_get_osfhandle(descriptor)); + if (handle == INVALID_HANDLE_VALUE) { + errno = EBADF; + return std::nullopt; + } + return HandlePath(handle); +} +static int NativeChmod(const std::filesystem::path& path, int mode) { + const DWORD attributes = GetFileAttributesW(path.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) { + RecordWrittenPath_nid_no_patch(path); + return ::_wchmod(path.wstring().c_str(), mode); + } + const HANDLE handle = CreateFileW(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle == INVALID_HANDLE_VALUE) { + errno = WindowsOpenError_nid_no_patch(GetLastError()); + return -1; + } + const auto target = HandlePath(handle); + CloseHandle(handle); + if (!target) return -1; + RecordWrittenPath_nid_no_patch(*target); + return ::_wchmod(target->wstring().c_str(), mode); +} static int NativeFchmod(int descriptor, int mode) { const auto path = File::WindowsDescriptorPath_nid_no_patch(descriptor); return path ? NativeChmod(*path, mode) : -1; diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index 86b6b6c3e898..cb50a38167e2 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -914,6 +914,37 @@ int main() { }; Require(sceKernelFchmod(descriptor, 0400) == 0 && !ownerWrite()); Require(fchmod_nid_postfix(descriptor, 0600) == 0 && ownerWrite()); + const auto sizedLink = sized.parent_path() / "sized.link"; + const auto missingLink = sized.parent_path() / "missing.link"; + const auto loopLink = sized.parent_path() / "loop.link"; +#ifdef _WIN32 + const DWORD linkFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + const bool chmodLinked = CreateSymbolicLinkW(sizedLink.wstring().c_str(), sized.filename().wstring().c_str(), linkFlags) && + CreateSymbolicLinkW(missingLink.wstring().c_str(), L"missing.bin", linkFlags) && + CreateSymbolicLinkW(loopLink.wstring().c_str(), L"loop.link", linkFlags); +#else + std::error_code chmodLinkError; + std::filesystem::create_symlink(sized.filename(), sizedLink, chmodLinkError); + if (!chmodLinkError) std::filesystem::create_symlink("missing.bin", missingLink, chmodLinkError); + if (!chmodLinkError) std::filesystem::create_symlink("loop.link", loopLink, chmodLinkError); + const bool chmodLinked = !chmodLinkError; +#endif + if (chmodLinked) { + Require(chmod_nid_postfix(sizedLink.string().c_str(), 0400) == 0 && !ownerWrite()); +#ifdef _WIN32 + Require((GetFileAttributesW(sizedLink.wstring().c_str()) & FILE_ATTRIBUTE_READONLY) == 0); +#endif + Require(chmod_nid_postfix(sizedLink.string().c_str(), 0600) == 0 && ownerWrite()); + Require(chmod_nid_postfix(missingLink.string().c_str(), 0600) == -1 && *__error_nid_postfix() == 2); +#ifdef _WIN32 + Require(chmod_nid_postfix(loopLink.string().c_str(), 0600) == -1 && *__error_nid_postfix() == 62); +#endif + } else { + std::fprintf(stderr, "symbolic links unavailable: the chmod link cases not tested\n"); + } + unlink_nid_postfix(sizedLink.string().c_str()); + unlink_nid_postfix(missingLink.string().c_str()); + unlink_nid_postfix(loopLink.string().c_str()); Require(sceKernelFtruncate(descriptor, 3) == 0); FileStat times{}; const KernelTimeval past[2]{{1000000000, 0}, {1000000000, 500000}};