xref: /aosp_15_r20/external/lzma/CPP/Windows/FileLink.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
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   if (Get16(p + 6) != 0) // padding
253     return false;
254   unsigned len = Get16(p + 4);
255   p += 8;
256   size -= 8;
257   if (len != size)
258   // if (len > size)
259     return false;
260   /*
261   if ((type & kReparseFlags_Alias) == 0 ||
262       (type & kReparseFlags_Microsoft) == 0 ||
263       (type & 0xFFFF) != 3)
264   */
265 
266 
267   HeaderError = false;
268 
269   if (   Tag != Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT
270       && Tag != Z7_WIN_IO_REPARSE_TAG_SYMLINK
271       && Tag != Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK)
272   {
273     // for unsupported reparse points
274     ErrorCode = (DWORD)ERROR_REPARSE_TAG_INVALID; // ERROR_REPARSE_TAG_MISMATCH
275     // errorCode = ERROR_REPARSE_TAG_MISMATCH; // ERROR_REPARSE_TAG_INVALID
276     return false;
277   }
278 
279   TagIsUnknown = false;
280 
281   if (Tag == Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK)
282   {
283     if (len < 4)
284       return false;
285     Flags = Get32(p); // maybe it's not Flags
286     if (Flags != Z7_WIN_LX_SYMLINK_FLAG)
287       return false;
288     len -= 4;
289     p += 4;
290     char *s = WslName.GetBuf(len);
291     unsigned i;
292     for (i = 0; i < len; i++)
293     {
294       char c = (char)p[i];
295       s[i] = c;
296       if (c == 0)
297         break;
298     }
299     WslName.ReleaseBuf_SetEnd(i);
300     MinorError = (i != len);
301     ErrorCode = 0;
302     return true;
303   }
304 
305   if (len < 8)
306     return false;
307   unsigned subOffs = Get16(p);
308   unsigned subLen = Get16(p + 2);
309   unsigned printOffs = Get16(p + 4);
310   unsigned printLen = Get16(p + 6);
311   len -= 8;
312   p += 8;
313 
314   Flags = 0;
315   if (Tag == Z7_WIN_IO_REPARSE_TAG_SYMLINK)
316   {
317     if (len < 4)
318       return false;
319     Flags = Get32(p);
320     len -= 4;
321     p += 4;
322   }
323 
324   if ((subOffs & 1) != 0 || subOffs > len || len - subOffs < subLen)
325     return false;
326   if ((printOffs & 1) != 0 || printOffs > len || len - printOffs < printLen)
327     return false;
328   GetString(p + subOffs, subLen >> 1, SubsName);
329   GetString(p + printOffs, printLen >> 1, PrintName);
330 
331   ErrorCode = 0;
332   return true;
333 }
334 
335 
Parse(const Byte * p,size_t size)336 bool CReparseShortInfo::Parse(const Byte *p, size_t size)
337 {
338   const Byte *start = p;
339   Offset= 0;
340   Size = 0;
341   if (size < 8)
342     return false;
343   UInt32 Tag = Get32(p);
344   UInt32 len = Get16(p + 4);
345   if (len + 8 > size)
346     return false;
347   /*
348   if ((type & kReparseFlags_Alias) == 0 ||
349       (type & kReparseFlags_Microsoft) == 0 ||
350       (type & 0xFFFF) != 3)
351   */
352   if (Tag != Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT &&
353       Tag != Z7_WIN_IO_REPARSE_TAG_SYMLINK)
354     // return true;
355     return false;
356 
357   if (Get16(p + 6) != 0) // padding
358     return false;
359 
360   p += 8;
361   size -= 8;
362 
363   if (len != size) // do we need that check?
364     return false;
365 
366   if (len < 8)
367     return false;
368   unsigned subOffs = Get16(p);
369   unsigned subLen = Get16(p + 2);
370   unsigned printOffs = Get16(p + 4);
371   unsigned printLen = Get16(p + 6);
372   len -= 8;
373   p += 8;
374 
375   // UInt32 Flags = 0;
376   if (Tag == Z7_WIN_IO_REPARSE_TAG_SYMLINK)
377   {
378     if (len < 4)
379       return false;
380     // Flags = Get32(p);
381     len -= 4;
382     p += 4;
383   }
384 
385   if ((subOffs & 1) != 0 || subOffs > len || len - subOffs < subLen)
386     return false;
387   if ((printOffs & 1) != 0 || printOffs > len || len - printOffs < printLen)
388     return false;
389 
390   Offset = (unsigned)(p - start) + subOffs;
391   Size = subLen;
392   return true;
393 }
394 
IsOkNamePair() const395 bool CReparseAttr::IsOkNamePair() const
396 {
397   if (IsLinkPrefix(SubsName))
398   {
399     if (!IsDrivePath(SubsName.Ptr(k_LinkPrefix_Size)))
400       return PrintName.IsEmpty();
401     if (wcscmp(SubsName.Ptr(k_LinkPrefix_Size), PrintName) == 0)
402       return true;
403   }
404   return wcscmp(SubsName, PrintName) == 0;
405 }
406 
407 /*
408 bool CReparseAttr::IsVolume() const
409 {
410   if (!IsLinkPrefix(SubsName))
411     return false;
412   return IsVolumeName(SubsName.Ptr(k_LinkPrefix_Size));
413 }
414 */
415 
GetPath() const416 UString CReparseAttr::GetPath() const
417 {
418   if (IsSymLink_WSL())
419   {
420     UString u;
421     // if (CheckUTF8(attr.WslName)
422     if (!ConvertUTF8ToUnicode(WslName, u))
423       MultiByteToUnicodeString2(u, WslName);
424     return u;
425   }
426 
427   UString s (SubsName);
428   if (IsLinkPrefix(s))
429   {
430     s.ReplaceOneCharAtPos(1, '\\'); // we normalize prefix from "\??\" to "\\?\"
431     if (IsDrivePath(s.Ptr(k_LinkPrefix_Size)))
432       s.DeleteFrontal(k_LinkPrefix_Size);
433   }
434   return s;
435 }
436 
437 #ifdef Z7_DEVICE_FILE
438 
439 namespace NSystem
440 {
441 bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize);
442 }
443 #endif // Z7_DEVICE_FILE
444 
445 #if defined(_WIN32) && !defined(UNDER_CE)
446 
447 namespace NIO {
448 
GetReparseData(CFSTR path,CByteBuffer & reparseData,BY_HANDLE_FILE_INFORMATION * fileInfo)449 bool GetReparseData(CFSTR path, CByteBuffer &reparseData, BY_HANDLE_FILE_INFORMATION *fileInfo)
450 {
451   reparseData.Free();
452   CInFile file;
453   if (!file.OpenReparse(path))
454     return false;
455 
456   if (fileInfo)
457     file.GetFileInformation(fileInfo);
458 
459   const unsigned kBufSize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
460   CByteArr buf(kBufSize);
461   DWORD returnedSize;
462   if (!file.DeviceIoControlOut(my_FSCTL_GET_REPARSE_POINT, buf, kBufSize, &returnedSize))
463     return false;
464   reparseData.CopyFrom(buf, returnedSize);
465   return true;
466 }
467 
CreatePrefixDirOfFile(CFSTR path)468 static bool CreatePrefixDirOfFile(CFSTR path)
469 {
470   FString path2 (path);
471   int pos = path2.ReverseFind_PathSepar();
472   if (pos < 0)
473     return true;
474   #ifdef _WIN32
475   if (pos == 2 && path2[1] == L':')
476     return true; // we don't create Disk folder;
477   #endif
478   path2.DeleteFrom((unsigned)pos);
479   return NDir::CreateComplexDir(path2);
480 }
481 
482 
OutIoReparseData(DWORD controlCode,CFSTR path,void * data,DWORD size)483 static bool OutIoReparseData(DWORD controlCode, CFSTR path, void *data, DWORD size)
484 {
485   COutFile file;
486   if (!file.Open(path,
487       FILE_SHARE_WRITE,
488       OPEN_EXISTING,
489       FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS))
490     return false;
491 
492   DWORD returnedSize;
493   return file.DeviceIoControl(controlCode, data, size, NULL, 0, &returnedSize);
494 }
495 
496 
497 // If there is Reparse data already, it still writes new Reparse data
SetReparseData(CFSTR path,bool isDir,const void * data,DWORD size)498 bool SetReparseData(CFSTR path, bool isDir, const void *data, DWORD size)
499 {
500   NFile::NFind::CFileInfo fi;
501   if (fi.Find(path))
502   {
503     if (fi.IsDir() != isDir)
504     {
505       ::SetLastError(ERROR_DIRECTORY);
506       return false;
507     }
508   }
509   else
510   {
511     if (isDir)
512     {
513       if (!NDir::CreateComplexDir(path))
514         return false;
515     }
516     else
517     {
518       CreatePrefixDirOfFile(path);
519       COutFile file;
520       if (!file.Create_NEW(path))
521         return false;
522     }
523   }
524 
525   return OutIoReparseData(my_FSCTL_SET_REPARSE_POINT, path, (void *)(const Byte *)(data), size);
526 }
527 
528 
DeleteReparseData(CFSTR path)529 bool DeleteReparseData(CFSTR path)
530 {
531   CByteBuffer reparseData;
532   if (!GetReparseData(path, reparseData, NULL))
533     return false;
534   /* MSDN: The tag specified in the ReparseTag member of this structure
535      must match the tag of the reparse point to be deleted,
536      and the ReparseDataLength member must be zero */
537   #define my_REPARSE_DATA_BUFFER_HEADER_SIZE 8
538   if (reparseData.Size() < my_REPARSE_DATA_BUFFER_HEADER_SIZE)
539   {
540     SetLastError(ERROR_INVALID_REPARSE_DATA);
541     return false;
542   }
543   BYTE buf[my_REPARSE_DATA_BUFFER_HEADER_SIZE];
544   memset(buf, 0, sizeof(buf));
545   memcpy(buf, reparseData, 4); // tag
546   return OutIoReparseData(my_FSCTL_DELETE_REPARSE_POINT, path, buf, sizeof(buf));
547 }
548 
549 }
550 
551 #endif //  defined(_WIN32) && !defined(UNDER_CE)
552 
553 
554 #ifndef _WIN32
555 
556 namespace NIO {
557 
GetReparseData(CFSTR path,CByteBuffer & reparseData)558 bool GetReparseData(CFSTR path, CByteBuffer &reparseData)
559 {
560   reparseData.Free();
561 
562   #define MAX_PATHNAME_LEN 1024
563   char buf[MAX_PATHNAME_LEN + 2];
564   const size_t request = sizeof(buf) - 1;
565 
566   // printf("\nreadlink() path = %s \n", path);
567   const ssize_t size = readlink(path, buf, request);
568   // there is no tail zero
569 
570   if (size < 0)
571     return false;
572   if ((size_t)size >= request)
573   {
574     SetLastError(EINVAL); // check it: ENAMETOOLONG
575     return false;
576   }
577 
578   // printf("\nreadlink() res = %s size = %d \n", buf, (int)size);
579   reparseData.CopyFrom((const Byte *)buf, (size_t)size);
580   return true;
581 }
582 
583 
584 /*
585 // If there is Reparse data already, it still writes new Reparse data
586 bool SetReparseData(CFSTR path, bool isDir, const void *data, DWORD size)
587 {
588   // AString s;
589   // s.SetFrom_CalcLen(data, size);
590   // return (symlink(s, path) == 0);
591   UNUSED_VAR(path)
592   UNUSED_VAR(isDir)
593   UNUSED_VAR(data)
594   UNUSED_VAR(size)
595   SetLastError(ENOSYS);
596   return false;
597 }
598 */
599 
SetSymLink(CFSTR from,CFSTR to)600 bool SetSymLink(CFSTR from, CFSTR to)
601 {
602   // printf("\nsymlink() %s -> %s\n", from, to);
603   int ir;
604   // ir = unlink(path);
605   // if (ir == 0)
606   ir = symlink(to, from);
607   return (ir == 0);
608 }
609 
SetSymLink_UString(CFSTR from,const UString & to)610 bool SetSymLink_UString(CFSTR from, const UString &to)
611 {
612   AString utf;
613   ConvertUnicodeToUTF8(to, utf);
614   return SetSymLink(from, utf);
615 }
616 
617 }
618 
619 #endif // !_WIN32
620 
621 }}
622