Merge branch fix/windows-absolute-symlink into fix/windows-junction

Keep the junction reader and try the absolute symbolic-link reader first.
This commit is contained in:
Maz committed 2026-10-18 12:50:08 +04:00
commit e964264d23
5 files changed
+42 -35

No files matched your search

@@ -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;
}
@@ -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());
+2 -2
View File
@@ -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);
+4 -17
View File
@@ -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<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));
}
+12 -7
View File
@@ -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);