diff --git a/core/libs/prx/libkernel/File/include/WindowsSymbolicLink.hpp b/core/libs/prx/libkernel/File/include/WindowsSymbolicLink.hpp index d4a17f6abb..cfe60f586d 100644 --- a/core/libs/prx/libkernel/File/include/WindowsSymbolicLink.hpp +++ b/core/libs/prx/libkernel/File/include/WindowsSymbolicLink.hpp @@ -26,8 +26,22 @@ class WindowsSymbolicLink { }; static constexpr ULONG relativeFlag = 1; + static std::wstring TextAt(const std::byte* data, DWORD size, USHORT offset, USHORT length, const char* operation) { + const std::size_t begin = sizeof(ReparseData) + offset; + if (length % sizeof(wchar_t) != 0 || begin + length > size) + throw std::runtime_error(std::string(operation) + ": malformed symbolic link reparse data"); + 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; + } + 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 +56,13 @@ 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, header.substituteNameOffset, header.substituteNameLength, operation) + : TextAt(data.data(), size, header.printNameOffset, header.printNameLength, operation); + if (!relative && name.empty()) + name = WithoutNtPrefix(TextAt(data.data(), size, header.substituteNameOffset, header.substituteNameLength, operation)); target = std::filesystem::path(name).generic_string(); return true; } diff --git a/core/libs/prx/libkernel/File/src/NativeStat.cpp b/core/libs/prx/libkernel/File/src/NativeStat.cpp index a0314cb45c..c83090182b 100644 --- a/core/libs/prx/libkernel/File/src/NativeStat.cpp +++ b/core/libs/prx/libkernel/File/src/NativeStat.cpp @@ -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") && !File::WindowsSymbolicLink::ReadJunction(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()); diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 80688e21df..d438fa7b6b 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -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") && !File::WindowsSymbolicLink::ReadJunction(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); diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index b358b88503..6ecd978e3d 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -714,24 +714,11 @@ 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(&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)); } diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index d2084f6920..d005fbc525 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -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(absoluteLength) < sizeof(absoluteBuffer) && + absoluteBuffer[absoluteLength] == '#'; + const std::string absoluteTarget = absoluteFits ? std::string(absoluteBuffer, static_cast(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(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);