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