From 395149956d696e6e3099d8b76d797437f94a6942 Mon Sep 17 00:00:00 2001 From: Igor Pavlov <87184205+ip7z@users.noreply.github.com> Date: Sat, 5 Jul 2025 00:00:00 +0000 Subject: 25.00 --- CPP/Windows/FileLink.cpp | 244 +++++++++++++++++++++++++++++++---------------- 1 file changed, 160 insertions(+), 84 deletions(-) (limited to 'CPP/Windows/FileLink.cpp') diff --git a/CPP/Windows/FileLink.cpp b/CPP/Windows/FileLink.cpp index bb380ec..2883c82 100644 --- a/CPP/Windows/FileLink.cpp +++ b/CPP/Windows/FileLink.cpp @@ -38,13 +38,25 @@ namespace NFile { using namespace NName; +/* +Win10 Junctions/SymLinks: + - (/) slash doesn't work as path separator + - Win10 preinstalled junctions don't use tail backslash, but tail backslashes also work. + - double backslash works only after drive prefix "c:\\dir1\dir2\", + and doesn't work in another places. + - absolute path without \??\ prefix doesn't work + - absolute path "c:" doesn't work +*/ + /* Reparse Points (Junctions and Symbolic Links): struct { UInt32 Tag; UInt16 Size; // not including starting 8 bytes - UInt16 Reserved; // = 0 + UInt16 Reserved; // = 0, DOCs: // Length, in bytes, of the unparsed portion of + // the file name pointed to by the FileName member of the associated file object. + // This member is only valid for create operations when the I/O fails with STATUS_REPARSE. UInt16 SubstituteOffset; // offset in bytes from start of namesChars UInt16 SubstituteLen; // size in bytes, it doesn't include tailed NUL @@ -68,6 +80,16 @@ using namespace NName; 2) Default Order in table: Print Path Substitute Path + +DOCS: + The print name SHOULD be an informative pathname, suitable for display + to a user, that also identifies the target of the mount point. + Neither of these pathnames can contain dot directory names. + +reparse tags, with the exception of IO_REPARSE_TAG_SYMLINK, +are processed on the server and are not processed by a client +after transmission over the wire. +Clients SHOULD treat associated reparse data as opaque data. */ /* @@ -93,7 +115,8 @@ static const UInt32 kReparseFlags_Microsoft = ((UInt32)1 << 31); #define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) -static const wchar_t * const k_LinkPrefix = L"\\??\\"; +static const char * const k_LinkPrefix = "\\??\\"; +static const char * const k_LinkPrefix_UNC = "\\??\\UNC\\"; static const unsigned k_LinkPrefix_Size = 4; static bool IsLinkPrefix(const wchar_t *s) @@ -102,7 +125,7 @@ static bool IsLinkPrefix(const wchar_t *s) } /* -static const wchar_t * const k_VolumePrefix = L"Volume{"; +static const char * const k_VolumePrefix = "Volume{"; static const bool IsVolumeName(const wchar_t *s) { return IsString1PrefixedByString2(s, k_VolumePrefix); @@ -118,7 +141,7 @@ static void WriteString(Byte *dest, const wchar_t *path) { for (;;) { - wchar_t c = *path++; + const wchar_t c = *path++; if (c == 0) return; Set16(dest, (UInt16)c) @@ -126,62 +149,103 @@ static void WriteString(Byte *dest, const wchar_t *path) } } -bool FillLinkData(CByteBuffer &dest, const wchar_t *path, bool isSymLink, bool isWSL) +#ifdef _WIN32 +void Convert_WinPath_to_WslLinuxPath(FString &s, bool convertDrivePath) { - bool isAbs = IsAbsolutePath(path); - if (!isAbs && !isSymLink) - return false; - - if (isWSL) + if (convertDrivePath && IsDrivePath(s)) { - // unsupported characters probably use Replacement Character UTF-16 0xFFFD - AString utf; - ConvertUnicodeToUTF8(path, utf); - const size_t size = 4 + utf.Len(); - if (size != (UInt16)size) - return false; - dest.Alloc(8 + size); - Byte *p = dest; - Set32(p, Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK) - Set16(p + 4, (UInt16)(size)) - Set16(p + 6, 0) - Set32(p + 8, Z7_WIN_LX_SYMLINK_FLAG) - memcpy(p + 12, utf.Ptr(), utf.Len()); - return true; + FChar c = s[0]; + c = MyCharLower_Ascii(c); + s.DeleteFrontal(2); + s.InsertAtFront(c); + s.Insert(0, FTEXT("/mnt/")); } + s.Replace(FCHAR_PATH_SEPARATOR, FTEXT('/')); +} +#endif - // usual symbolic LINK (NOT WSL) - bool needPrintName = true; +static const unsigned k_Link_Size_Limit = 1u << 16; // 16-bit field is used for size. + +void FillLinkData_WslLink(CByteBuffer &dest, const wchar_t *path) +{ + // dest.Free(); // it's empty already + // WSL probably uses Replacement Character UTF-16 0xFFFD for unsupported characters? + AString utf; + ConvertUnicodeToUTF8(path, utf); + const unsigned size = 4 + utf.Len(); + if (size >= k_Link_Size_Limit) + return; + dest.Alloc(8 + size); + Byte *p = dest; + Set32(p, Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK) + // Set32(p + 4, (UInt32)size) + Set16(p + 4, (UInt16)size) + Set16(p + 6, 0) + Set32(p + 8, Z7_WIN_LX_SYMLINK_VERSION_2) + memcpy(p + 12, utf.Ptr(), utf.Len()); +} + - if (IsSuperPath(path)) +void FillLinkData_WinLink(CByteBuffer &dest, const wchar_t *path, bool isSymLink) +{ + // dest.Free(); // it's empty already + bool isAbs = false; + if (IS_PATH_SEPAR(path[0])) { - path += kSuperPathPrefixSize; - if (!IsDrivePath(path)) - needPrintName = false; + // root paths "\dir1\path" are marked as relative + if (IS_PATH_SEPAR(path[1])) + isAbs = true; + } + else + isAbs = IsAbsolutePath(path); + if (!isAbs && !isSymLink) + { + // Win10 allows us to create relative MOUNT_POINT. + // But relative MOUNT_POINT will not work when accessing it. + // So we prevent useless creation of a relative MOUNT_POINT. + return; } - const unsigned add_Prefix_Len = isAbs ? k_LinkPrefix_Size : 0; - + bool needPrintName = true; + UString subs (path); + if (isAbs) + { + const bool isSuperPath = IsSuperPath(path); + if (!isSuperPath && NName::IsNetworkPath(us2fs(path))) + { + subs = k_LinkPrefix_UNC; + subs += (path + 2); + } + else + { + if (isSuperPath) + { + // we remove super prefix: + path += kSuperPathPrefixSize; + // we want to get correct abolute path in PrintName still. + if (!IsDrivePath(path)) + needPrintName = false; // we need "\\server\path" for print name. + } + subs = k_LinkPrefix; + subs += path; + } + } + const size_t len1 = subs.Len() * 2; size_t len2 = (size_t)MyStringLen(path) * 2; - const size_t len1 = len2 + add_Prefix_Len * 2; if (!needPrintName) len2 = 0; - - size_t totalNamesSize = (len1 + len2); - + size_t totalNamesSize = len1 + len2; /* some WIM imagex software uses old scheme for symbolic links. - so we can old scheme for byte to byte compatibility */ - - bool newOrderScheme = isSymLink; + so we can use old scheme for byte to byte compatibility */ + const bool newOrderScheme = isSymLink; // newOrderScheme = false; - if (!newOrderScheme) - totalNamesSize += 2 * 2; + totalNamesSize += 2 * 2; // we use NULL terminators in old scheme. const size_t size = 8 + 8 + (isSymLink ? 4 : 0) + totalNamesSize; - if (size != (UInt16)size) - return false; + if (size >= k_Link_Size_Limit) + return; dest.Alloc(size); memset(dest, 0, size); const UInt32 tag = isSymLink ? @@ -189,6 +253,7 @@ bool FillLinkData(CByteBuffer &dest, const wchar_t *path, bool isSymLink, bool i Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT; Byte *p = dest; Set32(p, tag) + // Set32(p + 4, (UInt32)(size - 8)) Set16(p + 4, (UInt16)(size - 8)) Set16(p + 6, 0) p += 8; @@ -204,21 +269,16 @@ bool FillLinkData(CByteBuffer &dest, const wchar_t *path, bool isSymLink, bool i Set16(p + 2, (UInt16)len1) Set16(p + 4, (UInt16)printOffs) Set16(p + 6, (UInt16)len2) - p += 8; if (isSymLink) { - UInt32 flags = isAbs ? 0 : Z7_WIN_SYMLINK_FLAG_RELATIVE; + const UInt32 flags = isAbs ? 0 : Z7_WIN_SYMLINK_FLAG_RELATIVE; Set32(p, flags) p += 4; } - - if (add_Prefix_Len != 0) - WriteString(p + subOffs, k_LinkPrefix); - WriteString(p + subOffs + add_Prefix_Len * 2, path); + WriteString(p + subOffs, subs); if (needPrintName) WriteString(p + printOffs, path); - return true; } #endif // defined(_WIN32) && !defined(UNDER_CE) @@ -230,7 +290,7 @@ static void GetString(const Byte *p, unsigned len, UString &res) unsigned i; for (i = 0; i < len; i++) { - wchar_t c = Get16(p + i * 2); + const wchar_t c = Get16(p + (size_t)i * 2); if (c == 0) break; s[i] = c; @@ -239,6 +299,7 @@ static void GetString(const Byte *p, unsigned len, UString &res) res.ReleaseBuf_SetLen(i); } + bool CReparseAttr::Parse(const Byte *p, size_t size) { ErrorCode = (DWORD)ERROR_INVALID_REPARSE_DATA; @@ -250,7 +311,12 @@ bool CReparseAttr::Parse(const Byte *p, size_t size) return false; Tag = Get32(p); if (Get16(p + 6) != 0) // padding - return false; + { + // DOCs: Reserved : the field SHOULD be set to 0 + // and MUST be ignored (by parser). + // Win10 ignores it. + MinorError = true; // optional + } unsigned len = Get16(p + 4); p += 8; size -= 8; @@ -262,8 +328,6 @@ bool CReparseAttr::Parse(const Byte *p, size_t size) (type & kReparseFlags_Microsoft) == 0 || (type & 0xFFFF) != 3) */ - - HeaderError = false; if ( Tag != Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT @@ -282,8 +346,7 @@ bool CReparseAttr::Parse(const Byte *p, size_t size) { if (len < 4) return false; - Flags = Get32(p); // maybe it's not Flags - if (Flags != Z7_WIN_LX_SYMLINK_FLAG) + if (Get32(p) != Z7_WIN_LX_SYMLINK_VERSION_2) return false; len -= 4; p += 4; @@ -291,12 +354,13 @@ bool CReparseAttr::Parse(const Byte *p, size_t size) unsigned i; for (i = 0; i < len; i++) { - char c = (char)p[i]; + const char c = (char)p[i]; s[i] = c; if (c == 0) break; } - WslName.ReleaseBuf_SetEnd(i); + s[i] = 0; + WslName.ReleaseBuf_SetLen(i); MinorError = (i != len); ErrorCode = 0; return true; @@ -304,10 +368,10 @@ bool CReparseAttr::Parse(const Byte *p, size_t size) if (len < 8) return false; - unsigned subOffs = Get16(p); - unsigned subLen = Get16(p + 2); - unsigned printOffs = Get16(p + 4); - unsigned printLen = Get16(p + 6); + const unsigned subOffs = Get16(p); + const unsigned subLen = Get16(p + 2); + const unsigned printOffs = Get16(p + 4); + const unsigned printLen = Get16(p + 6); len -= 8; p += 8; @@ -335,15 +399,17 @@ bool CReparseAttr::Parse(const Byte *p, size_t size) bool CReparseShortInfo::Parse(const Byte *p, size_t size) { - const Byte *start = p; - Offset= 0; + const Byte * const start = p; + Offset = 0; Size = 0; if (size < 8) return false; - UInt32 Tag = Get32(p); + const UInt32 Tag = Get32(p); UInt32 len = Get16(p + 4); + /* if (len + 8 > size) return false; + */ /* if ((type & kReparseFlags_Alias) == 0 || (type & kReparseFlags_Microsoft) == 0 || @@ -353,16 +419,14 @@ bool CReparseShortInfo::Parse(const Byte *p, size_t size) Tag != Z7_WIN_IO_REPARSE_TAG_SYMLINK) // return true; return false; - + /* if (Get16(p + 6) != 0) // padding return false; - + */ p += 8; size -= 8; - if (len != size) // do we need that check? return false; - if (len < 8) return false; unsigned subOffs = Get16(p); @@ -396,10 +460,14 @@ bool CReparseAttr::IsOkNamePair() const { if (IsLinkPrefix(SubsName)) { + if (PrintName == GetPath()) + return true; +/* if (!IsDrivePath(SubsName.Ptr(k_LinkPrefix_Size))) return PrintName.IsEmpty(); if (wcscmp(SubsName.Ptr(k_LinkPrefix_Size), PrintName) == 0) return true; +*/ } return wcscmp(SubsName, PrintName) == 0; } @@ -415,21 +483,26 @@ bool CReparseAttr::IsVolume() const UString CReparseAttr::GetPath() const { + UString s (SubsName); if (IsSymLink_WSL()) { - UString u; // if (CheckUTF8(attr.WslName) - if (!ConvertUTF8ToUnicode(WslName, u)) - MultiByteToUnicodeString2(u, WslName); - return u; + if (!ConvertUTF8ToUnicode(WslName, s)) + MultiByteToUnicodeString2(s, WslName); } - - UString s (SubsName); - if (IsLinkPrefix(s)) + else if (IsLinkPrefix(s)) { - s.ReplaceOneCharAtPos(1, '\\'); // we normalize prefix from "\??\" to "\\?\" - if (IsDrivePath(s.Ptr(k_LinkPrefix_Size))) - s.DeleteFrontal(k_LinkPrefix_Size); + if (IsString1PrefixedByString2_NoCase_Ascii(s.Ptr(), k_LinkPrefix_UNC)) + { + s.DeleteFrontal(6); + s.ReplaceOneCharAtPos(0, '\\'); + } + else + { + s.ReplaceOneCharAtPos(1, '\\'); // we normalize prefix from "\??\" to "\\?\" + if (IsDrivePath(s.Ptr(k_LinkPrefix_Size))) + s.DeleteFrontal(k_LinkPrefix_Size); + } } return s; } @@ -468,7 +541,7 @@ bool GetReparseData(CFSTR path, CByteBuffer &reparseData, BY_HANDLE_FILE_INFORMA static bool CreatePrefixDirOfFile(CFSTR path) { FString path2 (path); - int pos = path2.ReverseFind_PathSepar(); + const int pos = path2.ReverseFind_PathSepar(); if (pos < 0) return true; #ifdef _WIN32 @@ -494,6 +567,8 @@ static bool OutIoReparseData(DWORD controlCode, CFSTR path, void *data, DWORD si } +// MOUNT_POINT (Junction Point) and LX_SYMLINK (WSL) can be written without administrator rights. +// SYMLINK requires administrator rights. // If there is Reparse data already, it still writes new Reparse data bool SetReparseData(CFSTR path, bool isDir, const void *data, DWORD size) { @@ -540,10 +615,11 @@ bool DeleteReparseData(CFSTR path) SetLastError(ERROR_INVALID_REPARSE_DATA); return false; } - BYTE buf[my_REPARSE_DATA_BUFFER_HEADER_SIZE]; - memset(buf, 0, sizeof(buf)); - memcpy(buf, reparseData, 4); // tag - return OutIoReparseData(my_FSCTL_DELETE_REPARSE_POINT, path, buf, sizeof(buf)); + // BYTE buf[my_REPARSE_DATA_BUFFER_HEADER_SIZE]; + // memset(buf, 0, sizeof(buf)); + // memcpy(buf, reparseData, 4); // tag + memset(reparseData + 4, 0, my_REPARSE_DATA_BUFFER_HEADER_SIZE - 4); + return OutIoReparseData(my_FSCTL_DELETE_REPARSE_POINT, path, reparseData, my_REPARSE_DATA_BUFFER_HEADER_SIZE); } } -- cgit v1.2.3-55-g6feb