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
33 extern bool g_IsNT;
34 #endif
35
36 namespace NWindows {
37 namespace NFile {
38
39 using 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 /*
74 Win10 WSL2:
75 admin rights + sudo: it creates normal windows symbolic link.
76 in another cases : it creates IO_REPARSE_TAG_LX_SYMLINK repare point.
77 */
78
79 /*
80 static const UInt32 kReparseFlags_Alias = (1 << 29);
81 static const UInt32 kReparseFlags_HighLatency = (1 << 30);
82 static 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
96 static const wchar_t * const k_LinkPrefix = L"\\??\\";
97 static const unsigned k_LinkPrefix_Size = 4;
98
IsLinkPrefix(const wchar_t * s)99 static bool IsLinkPrefix(const wchar_t *s)
100 {
101 return IsString1PrefixedByString2(s, k_LinkPrefix);
102 }
103
104 /*
105 static const wchar_t * const k_VolumePrefix = L"Volume{";
106 static 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
WriteString(Byte * dest,const wchar_t * path)117 static 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
FillLinkData(CByteBuffer & dest,const wchar_t * path,bool isSymLink,bool isWSL)129 bool 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
GetString(const Byte * p,unsigned len,UString & res)227 static 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
Parse(const Byte * p,size_t size)242 bool 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
Parse(const Byte * p,size_t size)337 bool 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
IsOkNamePair() const396 bool 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 /*
409 bool CReparseAttr::IsVolume() const
410 {
411 if (!IsLinkPrefix(SubsName))
412 return false;
413 return IsVolumeName(SubsName.Ptr(k_LinkPrefix_Size));
414 }
415 */
416
GetPath() const417 UString 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
440 namespace NSystem
441 {
442 bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize);
443 }
444 #endif // Z7_DEVICE_FILE
445
446 #if defined(_WIN32) && !defined(UNDER_CE)
447
448 namespace NIO {
449
GetReparseData(CFSTR path,CByteBuffer & reparseData,BY_HANDLE_FILE_INFORMATION * fileInfo)450 bool 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
CreatePrefixDirOfFile(CFSTR path)469 static 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
OutIoReparseData(DWORD controlCode,CFSTR path,void * data,DWORD size)484 static 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
SetReparseData(CFSTR path,bool isDir,const void * data,DWORD size)499 bool 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
DeleteReparseData(CFSTR path)530 bool 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
557 namespace NIO {
558
GetReparseData(CFSTR path,CByteBuffer & reparseData)559 bool 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
587 bool 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
SetSymLink(CFSTR from,CFSTR to)601 bool 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
SetSymLink_UString(CFSTR from,const UString & to)611 bool 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