xref: /third_party/lzma/CPP/Windows/FileLink.cpp (revision 370b324c)
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