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