1// Windows/FileLink.cpp 2 3#include "StdAfx.h" 4 5#include "../../C/CpuArch.h" 6 7#ifndef _WIN32 8#include <unistd.h> 9#endif 10 11#ifdef Z7_DEVICE_FILE 12#include "../../C/Alloc.h" 13#endif 14 15#include "../Common/UTFConvert.h" 16#include "../Common/StringConvert.h" 17 18#include "FileDir.h" 19#include "FileFind.h" 20#include "FileIO.h" 21#include "FileName.h" 22 23#ifdef Z7_OLD_WIN_SDK 24#ifndef ERROR_INVALID_REPARSE_DATA 25#define ERROR_INVALID_REPARSE_DATA 4392L 26#endif 27#ifndef ERROR_REPARSE_TAG_INVALID 28#define ERROR_REPARSE_TAG_INVALID 4393L 29#endif 30#endif 31 32#ifndef _UNICODE 33extern bool g_IsNT; 34#endif 35 36namespace NWindows { 37namespace NFile { 38 39using namespace NName; 40 41/* 42 Reparse Points (Junctions and Symbolic Links): 43 struct 44 { 45 UInt32 Tag; 46 UInt16 Size; // not including starting 8 bytes 47 UInt16 Reserved; // = 0 48 49 UInt16 SubstituteOffset; // offset in bytes from start of namesChars 50 UInt16 SubstituteLen; // size in bytes, it doesn't include tailed NUL 51 UInt16 PrintOffset; // offset in bytes from start of namesChars 52 UInt16 PrintLen; // size in bytes, it doesn't include tailed NUL 53 54 [UInt32] Flags; // for Symbolic Links only. 55 56 UInt16 namesChars[] 57 } 58 59 MOUNT_POINT (Junction point): 60 1) there is NUL wchar after path 61 2) Default Order in table: 62 Substitute Path 63 Print Path 64 3) pathnames can not contain dot directory names 65 66 SYMLINK: 67 1) there is no NUL wchar after path 68 2) Default Order in table: 69 Print Path 70 Substitute Path 71*/ 72 73/* 74Win10 WSL2: 75admin rights + sudo: it creates normal windows symbolic link. 76in another cases : it creates IO_REPARSE_TAG_LX_SYMLINK repare point. 77*/ 78 79/* 80static const UInt32 kReparseFlags_Alias = (1 << 29); 81static const UInt32 kReparseFlags_HighLatency = (1 << 30); 82static const UInt32 kReparseFlags_Microsoft = ((UInt32)1 << 31); 83 84#define Z7_WIN_IO_REPARSE_TAG_HSM (0xC0000004L) 85#define Z7_WIN_IO_REPARSE_TAG_HSM2 (0x80000006L) 86#define Z7_WIN_IO_REPARSE_TAG_SIS (0x80000007L) 87#define Z7_WIN_IO_REPARSE_TAG_WIM (0x80000008L) 88#define Z7_WIN_IO_REPARSE_TAG_CSV (0x80000009L) 89#define Z7_WIN_IO_REPARSE_TAG_DFS (0x8000000AL) 90#define Z7_WIN_IO_REPARSE_TAG_DFSR (0x80000012L) 91*/ 92 93#define Get16(p) GetUi16(p) 94#define Get32(p) GetUi32(p) 95 96static const wchar_t * const k_LinkPrefix = L"\\??\\"; 97static const unsigned k_LinkPrefix_Size = 4; 98 99static bool IsLinkPrefix(const wchar_t *s) 100{ 101 return IsString1PrefixedByString2(s, k_LinkPrefix); 102} 103 104/* 105static const wchar_t * const k_VolumePrefix = L"Volume{"; 106static const bool IsVolumeName(const wchar_t *s) 107{ 108 return IsString1PrefixedByString2(s, k_VolumePrefix); 109} 110*/ 111 112#if defined(_WIN32) && !defined(UNDER_CE) 113 114#define Set16(p, v) SetUi16(p, v) 115#define Set32(p, v) SetUi32(p, v) 116 117static void WriteString(Byte *dest, const wchar_t *path) 118{ 119 for (;;) 120 { 121 wchar_t c = *path++; 122 if (c == 0) 123 return; 124 Set16(dest, (UInt16)c) 125 dest += 2; 126 } 127} 128 129bool FillLinkData(CByteBuffer &dest, const wchar_t *path, bool isSymLink, bool isWSL) 130{ 131 bool isAbs = IsAbsolutePath(path); 132 if (!isAbs && !isSymLink) 133 return false; 134 135 if (isWSL) 136 { 137 // unsupported characters probably use Replacement Character UTF-16 0xFFFD 138 AString utf; 139 ConvertUnicodeToUTF8(path, utf); 140 const size_t size = 4 + utf.Len(); 141 if (size != (UInt16)size) 142 return false; 143 dest.Alloc(8 + size); 144 Byte *p = dest; 145 Set32(p, Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK) 146 Set16(p + 4, (UInt16)(size)) 147 Set16(p + 6, 0) 148 Set32(p + 8, Z7_WIN_LX_SYMLINK_FLAG) 149 memcpy(p + 12, utf.Ptr(), utf.Len()); 150 return true; 151 } 152 153 // usual symbolic LINK (NOT WSL) 154 155 bool needPrintName = true; 156 157 if (IsSuperPath(path)) 158 { 159 path += kSuperPathPrefixSize; 160 if (!IsDrivePath(path)) 161 needPrintName = false; 162 } 163 164 const unsigned add_Prefix_Len = isAbs ? k_LinkPrefix_Size : 0; 165 166 size_t len2 = (size_t)MyStringLen(path) * 2; 167 const size_t len1 = len2 + add_Prefix_Len * 2; 168 if (!needPrintName) 169 len2 = 0; 170 171 size_t totalNamesSize = (len1 + len2); 172 173 /* some WIM imagex software uses old scheme for symbolic links. 174 so we can old scheme for byte to byte compatibility */ 175 176 bool newOrderScheme = isSymLink; 177 // newOrderScheme = false; 178 179 if (!newOrderScheme) 180 totalNamesSize += 2 * 2; 181 182 const size_t size = 8 + 8 + (isSymLink ? 4 : 0) + totalNamesSize; 183 if (size != (UInt16)size) 184 return false; 185 dest.Alloc(size); 186 memset(dest, 0, size); 187 const UInt32 tag = isSymLink ? 188 Z7_WIN_IO_REPARSE_TAG_SYMLINK : 189 Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT; 190 Byte *p = dest; 191 Set32(p, tag) 192 Set16(p + 4, (UInt16)(size - 8)) 193 Set16(p + 6, 0) 194 p += 8; 195 196 unsigned subOffs = 0; 197 unsigned printOffs = 0; 198 if (newOrderScheme) 199 subOffs = (unsigned)len2; 200 else 201 printOffs = (unsigned)len1 + 2; 202 203 Set16(p + 0, (UInt16)subOffs) 204 Set16(p + 2, (UInt16)len1) 205 Set16(p + 4, (UInt16)printOffs) 206 Set16(p + 6, (UInt16)len2) 207 208 p += 8; 209 if (isSymLink) 210 { 211 UInt32 flags = isAbs ? 0 : Z7_WIN_SYMLINK_FLAG_RELATIVE; 212 Set32(p, flags) 213 p += 4; 214 } 215 216 if (add_Prefix_Len != 0) 217 WriteString(p + subOffs, k_LinkPrefix); 218 WriteString(p + subOffs + add_Prefix_Len * 2, path); 219 if (needPrintName) 220 WriteString(p + printOffs, path); 221 return true; 222} 223 224#endif // defined(_WIN32) && !defined(UNDER_CE) 225 226 227static void GetString(const Byte *p, unsigned len, UString &res) 228{ 229 wchar_t *s = res.GetBuf(len); 230 unsigned i; 231 for (i = 0; i < len; i++) 232 { 233 wchar_t c = Get16(p + i * 2); 234 if (c == 0) 235 break; 236 s[i] = c; 237 } 238 s[i] = 0; 239 res.ReleaseBuf_SetLen(i); 240} 241 242bool CReparseAttr::Parse(const Byte *p, size_t size) 243{ 244 ErrorCode = (DWORD)ERROR_INVALID_REPARSE_DATA; 245 HeaderError = true; 246 TagIsUnknown = true; 247 MinorError = false; 248 249 if (size < 8) 250 return false; 251 Tag = Get32(p); 252 UInt32 len = Get16(p + 4); 253 if (len + 8 != size) 254 // if (len + 8 > size) 255 return false; 256 /* 257 if ((type & kReparseFlags_Alias) == 0 || 258 (type & kReparseFlags_Microsoft) == 0 || 259 (type & 0xFFFF) != 3) 260 */ 261 262 if (Get16(p + 6) != 0) // padding 263 return false; 264 265 HeaderError = false; 266 267 if ( Tag != Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT 268 && Tag != Z7_WIN_IO_REPARSE_TAG_SYMLINK 269 && Tag != Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK) 270 { 271 // for unsupported reparse points 272 ErrorCode = (DWORD)ERROR_REPARSE_TAG_INVALID; // ERROR_REPARSE_TAG_MISMATCH 273 // errorCode = ERROR_REPARSE_TAG_MISMATCH; // ERROR_REPARSE_TAG_INVALID 274 return false; 275 } 276 277 TagIsUnknown = false; 278 279 p += 8; 280 size -= 8; 281 282 if (Tag == Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK) 283 { 284 if (len < 4) 285 return false; 286 Flags = Get32(p); // maybe it's not Flags 287 if (Flags != Z7_WIN_LX_SYMLINK_FLAG) 288 return false; 289 len -= 4; 290 p += 4; 291 char *s = WslName.GetBuf(len); 292 unsigned i; 293 for (i = 0; i < len; i++) 294 { 295 char c = (char)p[i]; 296 s[i] = c; 297 if (c == 0) 298 break; 299 } 300 WslName.ReleaseBuf_SetEnd(i); 301 MinorError = (i != len); 302 ErrorCode = 0; 303 return true; 304 } 305 306 if (len < 8) 307 return false; 308 unsigned subOffs = Get16(p); 309 unsigned subLen = Get16(p + 2); 310 unsigned printOffs = Get16(p + 4); 311 unsigned printLen = Get16(p + 6); 312 len -= 8; 313 p += 8; 314 315 Flags = 0; 316 if (Tag == Z7_WIN_IO_REPARSE_TAG_SYMLINK) 317 { 318 if (len < 4) 319 return false; 320 Flags = Get32(p); 321 len -= 4; 322 p += 4; 323 } 324 325 if ((subOffs & 1) != 0 || subOffs > len || len - subOffs < subLen) 326 return false; 327 if ((printOffs & 1) != 0 || printOffs > len || len - printOffs < printLen) 328 return false; 329 GetString(p + subOffs, subLen >> 1, SubsName); 330 GetString(p + printOffs, printLen >> 1, PrintName); 331 332 ErrorCode = 0; 333 return true; 334} 335 336 337bool CReparseShortInfo::Parse(const Byte *p, size_t size) 338{ 339 const Byte *start = p; 340 Offset= 0; 341 Size = 0; 342 if (size < 8) 343 return false; 344 UInt32 Tag = Get32(p); 345 UInt32 len = Get16(p + 4); 346 if (len + 8 > size) 347 return false; 348 /* 349 if ((type & kReparseFlags_Alias) == 0 || 350 (type & kReparseFlags_Microsoft) == 0 || 351 (type & 0xFFFF) != 3) 352 */ 353 if (Tag != Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT && 354 Tag != Z7_WIN_IO_REPARSE_TAG_SYMLINK) 355 // return true; 356 return false; 357 358 if (Get16(p + 6) != 0) // padding 359 return false; 360 361 p += 8; 362 size -= 8; 363 364 if (len != size) // do we need that check? 365 return false; 366 367 if (len < 8) 368 return false; 369 unsigned subOffs = Get16(p); 370 unsigned subLen = Get16(p + 2); 371 unsigned printOffs = Get16(p + 4); 372 unsigned printLen = Get16(p + 6); 373 len -= 8; 374 p += 8; 375 376 // UInt32 Flags = 0; 377 if (Tag == Z7_WIN_IO_REPARSE_TAG_SYMLINK) 378 { 379 if (len < 4) 380 return false; 381 // Flags = Get32(p); 382 len -= 4; 383 p += 4; 384 } 385 386 if ((subOffs & 1) != 0 || subOffs > len || len - subOffs < subLen) 387 return false; 388 if ((printOffs & 1) != 0 || printOffs > len || len - printOffs < printLen) 389 return false; 390 391 Offset = (unsigned)(p - start) + subOffs; 392 Size = subLen; 393 return true; 394} 395 396bool CReparseAttr::IsOkNamePair() const 397{ 398 if (IsLinkPrefix(SubsName)) 399 { 400 if (!IsDrivePath(SubsName.Ptr(k_LinkPrefix_Size))) 401 return PrintName.IsEmpty(); 402 if (wcscmp(SubsName.Ptr(k_LinkPrefix_Size), PrintName) == 0) 403 return true; 404 } 405 return wcscmp(SubsName, PrintName) == 0; 406} 407 408/* 409bool CReparseAttr::IsVolume() const 410{ 411 if (!IsLinkPrefix(SubsName)) 412 return false; 413 return IsVolumeName(SubsName.Ptr(k_LinkPrefix_Size)); 414} 415*/ 416 417UString CReparseAttr::GetPath() const 418{ 419 if (IsSymLink_WSL()) 420 { 421 UString u; 422 // if (CheckUTF8(attr.WslName) 423 if (!ConvertUTF8ToUnicode(WslName, u)) 424 MultiByteToUnicodeString2(u, WslName); 425 return u; 426 } 427 428 UString s (SubsName); 429 if (IsLinkPrefix(s)) 430 { 431 s.ReplaceOneCharAtPos(1, '\\'); // we normalize prefix from "\??\" to "\\?\" 432 if (IsDrivePath(s.Ptr(k_LinkPrefix_Size))) 433 s.DeleteFrontal(k_LinkPrefix_Size); 434 } 435 return s; 436} 437 438#ifdef Z7_DEVICE_FILE 439 440namespace NSystem 441{ 442bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize); 443} 444#endif // Z7_DEVICE_FILE 445 446#if defined(_WIN32) && !defined(UNDER_CE) 447 448namespace NIO { 449 450bool GetReparseData(CFSTR path, CByteBuffer &reparseData, BY_HANDLE_FILE_INFORMATION *fileInfo) 451{ 452 reparseData.Free(); 453 CInFile file; 454 if (!file.OpenReparse(path)) 455 return false; 456 457 if (fileInfo) 458 file.GetFileInformation(fileInfo); 459 460 const unsigned kBufSize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE; 461 CByteArr buf(kBufSize); 462 DWORD returnedSize; 463 if (!file.DeviceIoControlOut(my_FSCTL_GET_REPARSE_POINT, buf, kBufSize, &returnedSize)) 464 return false; 465 reparseData.CopyFrom(buf, returnedSize); 466 return true; 467} 468 469static bool CreatePrefixDirOfFile(CFSTR path) 470{ 471 FString path2 (path); 472 int pos = path2.ReverseFind_PathSepar(); 473 if (pos < 0) 474 return true; 475 #ifdef _WIN32 476 if (pos == 2 && path2[1] == L':') 477 return true; // we don't create Disk folder; 478 #endif 479 path2.DeleteFrom((unsigned)pos); 480 return NDir::CreateComplexDir(path2); 481} 482 483 484static bool OutIoReparseData(DWORD controlCode, CFSTR path, void *data, DWORD size) 485{ 486 COutFile file; 487 if (!file.Open(path, 488 FILE_SHARE_WRITE, 489 OPEN_EXISTING, 490 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS)) 491 return false; 492 493 DWORD returnedSize; 494 return file.DeviceIoControl(controlCode, data, size, NULL, 0, &returnedSize); 495} 496 497 498// If there is Reparse data already, it still writes new Reparse data 499bool SetReparseData(CFSTR path, bool isDir, const void *data, DWORD size) 500{ 501 NFile::NFind::CFileInfo fi; 502 if (fi.Find(path)) 503 { 504 if (fi.IsDir() != isDir) 505 { 506 ::SetLastError(ERROR_DIRECTORY); 507 return false; 508 } 509 } 510 else 511 { 512 if (isDir) 513 { 514 if (!NDir::CreateComplexDir(path)) 515 return false; 516 } 517 else 518 { 519 CreatePrefixDirOfFile(path); 520 COutFile file; 521 if (!file.Create(path, CREATE_NEW)) 522 return false; 523 } 524 } 525 526 return OutIoReparseData(my_FSCTL_SET_REPARSE_POINT, path, (void *)(const Byte *)(data), size); 527} 528 529 530bool DeleteReparseData(CFSTR path) 531{ 532 CByteBuffer reparseData; 533 if (!GetReparseData(path, reparseData, NULL)) 534 return false; 535 /* MSDN: The tag specified in the ReparseTag member of this structure 536 must match the tag of the reparse point to be deleted, 537 and the ReparseDataLength member must be zero */ 538 #define my_REPARSE_DATA_BUFFER_HEADER_SIZE 8 539 if (reparseData.Size() < my_REPARSE_DATA_BUFFER_HEADER_SIZE) 540 { 541 SetLastError(ERROR_INVALID_REPARSE_DATA); 542 return false; 543 } 544 BYTE buf[my_REPARSE_DATA_BUFFER_HEADER_SIZE]; 545 memset(buf, 0, sizeof(buf)); 546 memcpy(buf, reparseData, 4); // tag 547 return OutIoReparseData(my_FSCTL_DELETE_REPARSE_POINT, path, buf, sizeof(buf)); 548} 549 550} 551 552#endif // defined(_WIN32) && !defined(UNDER_CE) 553 554 555#ifndef _WIN32 556 557namespace NIO { 558 559bool GetReparseData(CFSTR path, CByteBuffer &reparseData) 560{ 561 reparseData.Free(); 562 563 #define MAX_PATHNAME_LEN 1024 564 char buf[MAX_PATHNAME_LEN + 2]; 565 const size_t request = sizeof(buf) - 1; 566 567 // printf("\nreadlink() path = %s \n", path); 568 const ssize_t size = readlink(path, buf, request); 569 // there is no tail zero 570 571 if (size < 0) 572 return false; 573 if ((size_t)size >= request) 574 { 575 SetLastError(EINVAL); // check it: ENAMETOOLONG 576 return false; 577 } 578 579 // printf("\nreadlink() res = %s size = %d \n", buf, (int)size); 580 reparseData.CopyFrom((const Byte *)buf, (size_t)size); 581 return true; 582} 583 584 585/* 586// If there is Reparse data already, it still writes new Reparse data 587bool SetReparseData(CFSTR path, bool isDir, const void *data, DWORD size) 588{ 589 // AString s; 590 // s.SetFrom_CalcLen(data, size); 591 // return (symlink(s, path) == 0); 592 UNUSED_VAR(path) 593 UNUSED_VAR(isDir) 594 UNUSED_VAR(data) 595 UNUSED_VAR(size) 596 SetLastError(ENOSYS); 597 return false; 598} 599*/ 600 601bool SetSymLink(CFSTR from, CFSTR to) 602{ 603 // printf("\nsymlink() %s -> %s\n", from, to); 604 int ir; 605 // ir = unlink(path); 606 // if (ir == 0) 607 ir = symlink(to, from); 608 return (ir == 0); 609} 610 611bool SetSymLink_UString(CFSTR from, const UString &to) 612{ 613 AString utf; 614 ConvertUnicodeToUTF8(to, utf); 615 return SetSymLink(from, utf); 616} 617 618} 619 620#endif // !_WIN32 621 622}} 623