master
cpp 686 lines 14.5 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 wslpath.c
8
9 Abstract:
10
11 This file contains the function definitions for wslpath.
12
13 --*/
14
15 #include "common.h"
16 #include <sys/mount.h>
17 #include <sys/utsname.h>
18 #include <ctype.h>
19 #include <locale.h>
20 #include <stdarg.h>
21 #include <signal.h>
22 #include <endian.h>
23 #include <lxbusapi.h>
24 #include <pwd.h>
25 #include "wslpath.h"
26
27 #include "util.h"
28 #include "CommandLine.h"
29
30 using namespace wsl::shared;
31
32 #define INVALID_USAGE() Die(Argv[0], EINVAL, true, NULL)
33
34 std::string AbsolutePath(char* Path, char* Cwd, size_t CwdSize, bool* Relative);
35
36 std::string AbsoluteWindowsPath(char* RelativePath, const char* Cwd);
37
38 int CollapsePath(char* Path, char Separator);
39
40 void Die(const char* Argv0, int Error, bool PrintUsage, const char* Message, ...);
41
42 std::string DosToCanonicalPath(char* Path, char* UnixCwd, size_t UnixCwdSize, bool* Relative);
43
44 std::string AbsolutePath(char* Path, char* Cwd, size_t CwdSize, bool* Relative)
45
46 /*++
47
48 Routine Description:
49
50 This routine converts a Unix path to an absolute path.
51
52 Arguments:
53
54 RelativePath - Supplies a pointer to the relative path.
55
56 Cwd - Supplies a buffer which receives the current working directory, only
57 if the path is relative.
58
59 CwdSize - Supplies the size of the Cwd buffer.
60
61 Relative - Supplies a pointer to receive whether the input path was
62 relative.
63
64 Return Value:
65
66 The absolute path as a string.
67 Returns an empty string on failure.
68
69 --*/
70
71 try
72 {
73 std::string NewPath{};
74 if (Path[0] != PATH_SEP)
75 {
76 if (getcwd(Cwd, CwdSize) == NULL)
77 {
78 return {};
79 }
80
81 NewPath += Cwd;
82 NewPath += PATH_SEP;
83 NewPath += Path;
84 *Relative = true;
85 }
86 else
87 {
88 NewPath = Path;
89 *Relative = false;
90 }
91
92 if (CollapsePath(NewPath.data(), PATH_SEP) < 0)
93 {
94 return {};
95 }
96
97 return NewPath;
98 }
99 catch (...)
100 {
101 LOG_CAUGHT_EXCEPTION();
102 return {};
103 }
104
105 std::string AbsoluteWindowsPath(char* RelativePath, const char* Cwd)
106
107 /*++
108
109 Routine Description:
110
111 This routine converts a relative Win32 path to an absolute path.
112
113 The input path must be relative.
114
115 Arguments:
116
117 RelativePath - Supplies a pointer to the relative path.
118
119 Cwd - Supplies a pointer to the Windows current working directory.
120
121 Return Value:
122
123 0 on success, <0 on failure.
124
125 --*/
126
127 try
128 {
129 std::string AbsolutePath{};
130 if (RelativePath[0] == PATH_SEP_NT)
131 {
132 //
133 // This path is relative to the drive letter root.
134 //
135
136 if (!isalpha(Cwd[0]) || Cwd[1] != ':')
137 {
138 return {};
139 }
140
141 AbsolutePath += Cwd[0];
142 AbsolutePath += Cwd[1];
143 }
144 else
145 {
146 AbsolutePath += Cwd;
147 AbsolutePath += PATH_SEP_NT;
148 }
149
150 AbsolutePath += RelativePath;
151 return AbsolutePath;
152 }
153 catch (...)
154 {
155 LOG_CAUGHT_EXCEPTION();
156 return {};
157 }
158
159 int CollapsePath(char* Path, char Separator)
160
161 /*++
162
163 Routine Description:
164
165 Collapses, where possible, relative path segments in a Path.
166
167 Arguments:
168
169 Path - Supplies the path to collapse.
170
171 Separator - The directory separator.
172
173 Return Value:
174
175 0 on success, <0 on failure.
176
177 --*/
178
179 {
180 char* LastSegment;
181 char* NextSeparator;
182 size_t PathLength;
183 size_t Remaining;
184 int RemoveSegmentCount;
185 char* Segment;
186 size_t SegmentLength;
187
188 PathLength = strlen(Path);
189 LastSegment = Path + PathLength;
190 RemoveSegmentCount = 0;
191 for (Remaining = PathLength; Remaining > 0;)
192 {
193 NextSeparator = static_cast<char*>(memrchr(Path, Separator, Remaining));
194 if (NextSeparator == nullptr)
195 {
196 break;
197 }
198
199 Segment = NextSeparator + 1;
200 SegmentLength = Remaining - (Segment - Path);
201 Remaining -= SegmentLength + 1;
202 if (SegmentLength == 2 && Segment[0] == '.' && Segment[1] == '.')
203 {
204 //
205 // Eliminate this and the next segment.
206 //
207
208 RemoveSegmentCount++;
209 }
210 else
211 {
212 if (RemoveSegmentCount == 0 && (SegmentLength == 0 || (SegmentLength == 1 && Segment[0] == '.')))
213 {
214 RemoveSegmentCount++;
215 }
216
217 if (RemoveSegmentCount > 0)
218 {
219 memmove(Segment, LastSegment, Path + PathLength - LastSegment + 1);
220 RemoveSegmentCount--;
221 }
222
223 LastSegment = Segment;
224 }
225 }
226
227 return RemoveSegmentCount == 0 ? 0 : -EINVAL;
228 }
229
230 void Die(const char* Argv0, int Error, bool PrintUsage, const char* Message, ...)
231
232 /*++
233
234 Routine Description:
235
236 This routine aborts program execution with an error message.
237
238 Arguments:
239
240 Argv0 - Supplies the name of the program.
241
242 Error - Supplies the error code or 0.
243
244 PrintUsage - Supplies a boolean specifying if usage should be printed.
245
246 Message - Supplies the message in printf format.
247
248 ... - Additional arguments.
249
250 Return Value:
251
252 None. Does not return.
253
254 --*/
255
256 {
257 va_list Args;
258
259 va_start(Args, Message);
260
261 fprintf(stderr, "%s: ", Argv0);
262 if (Message != nullptr)
263 {
264 vfprintf(stderr, Message, Args);
265 }
266
267 if (Error != 0)
268 {
269 if (Message != nullptr)
270 {
271 fputs(": ", stderr);
272 }
273
274 fputs(strerror(Error), stderr);
275 }
276
277 fputs("\n", stderr);
278 if (PrintUsage)
279 {
280 printf("%s\n", Localization::MessageWslPathUsage().c_str());
281 }
282
283 exit(1);
284 va_end(Args);
285 }
286
287 std::string DosToCanonicalPath(char* Path, char* UnixCwd, size_t UnixCwdSize, bool* Relative)
288
289 /*++
290
291 Routine Description:
292
293 This routine gets the canonical representation of a DOS path:
294 - The path is made absolute.
295 - All separators are changed to backslashes.
296 - Duplicate separators and .. components are compacted.
297 - The \\?\ prefix is stripped from long paths.
298
299 Arguments:
300
301 Path - Supplies the path to translate.
302
303 UnixCwd - Supplies a buffer which receives the Linux current working
304 directory, only if the path was relative.
305
306 UnixCwdSize - Supplies the size of the cwd buffer.
307
308 Relative - Supplies a pointer to receive whether the input path was
309 a relative path.
310
311 Return Value:
312
313 0 on success, -error for errors.
314
315 --*/
316
317 {
318 std::string AbsolutePath{};
319 size_t CollapseStartIndex;
320 char* SuffixString;
321 size_t StartIndex;
322 size_t PathLength;
323 int Result = -1;
324
325 *Relative = false;
326
327 //
328 // Convert relative paths to absolute paths.
329 //
330
331 if (!UtilIsAbsoluteWindowsPath(Path))
332 {
333 if (getcwd(UnixCwd, UnixCwdSize) == NULL)
334 {
335 return {};
336 }
337
338 const auto Cwd = UtilWinPathTranslate(UnixCwd, false);
339 if (Cwd.empty())
340 {
341 return {};
342 }
343
344 AbsolutePath = AbsoluteWindowsPath(Path, Cwd.c_str());
345 if (AbsolutePath.empty())
346 {
347 return {};
348 }
349
350 Path = AbsolutePath.data();
351 *Relative = true;
352 }
353
354 PathLength = strlen(Path);
355 if (PathLength < 2)
356 {
357 return {};
358 }
359
360 if (Path[1] == DRIVE_SEP_NT && Path[2] == PATH_SEP_NT)
361 {
362 //
363 // This is a drive letter path C:\foo -> \??\C:\foo
364 //
365
366 CollapseStartIndex = 0;
367 StartIndex = 0;
368 }
369 else if (Path[0] == PATH_SEP_NT && Path[1] == PATH_SEP_NT)
370 {
371 //
372 // This is either a long path or a UNC path. If it's a long path,
373 // strip the prefix.
374 //
375
376 if (Path[2] == '?' && Path[3] == PATH_SEP_NT)
377 {
378 StartIndex = 4;
379 CollapseStartIndex = 0;
380 }
381 else
382 {
383 StartIndex = 0;
384
385 //
386 // Make sure the starting \\ aren't collapsed
387 //
388
389 CollapseStartIndex = 2;
390 }
391 }
392 else
393 {
394 return {};
395 }
396
397 SuffixString = Path + StartIndex;
398 Result = CollapsePath(&SuffixString[CollapseStartIndex], PATH_SEP_NT);
399 if (Result < 0)
400 {
401 return {};
402 }
403
404 return std::string(SuffixString);
405 }
406
407 int WslPathEntry(int Argc, char* Argv[])
408
409 /*++
410
411 Routine Description:
412
413 This routine will output to stdout the NT path for a DrvFs path.
414
415 Arguments:
416
417 Argc - Supplies the argument count.
418
419 Argv - Supplies the command line arguments.
420
421 Return Value:
422
423 0 on success, 1 on failure.
424
425 --*/
426
427 {
428 int Flags = TRANSLATE_FLAG_RESOLVE_SYMLINKS;
429 std::optional<char> Mode;
430 const char* OriginalPath{};
431 std::string OutputPath;
432 int Result{};
433 char* SourcePath = nullptr;
434
435 //
436 // With the current version of musl, this has no useful effect but is also
437 // not harmful.
438 //
439
440 setlocale(LC_ALL, "");
441
442 ArgumentParser parser(Argc, Argv);
443
444 constexpr auto Usage = std::bind(Localization::MessageWslPathUsage, Localization::Options::Default);
445
446 parser.AddPositionalArgument(OriginalPath, 0);
447 parser.AddArgument(SetFlag<TRANSLATE_FLAG_ABSOLUTE, int>{Flags}, nullptr, TRANSLATE_MODE_ABSOLUTE);
448 parser.AddArgument(UniqueSetValue<char, TRANSLATE_MODE_UNIX>{Mode, Usage}, nullptr, TRANSLATE_MODE_UNIX);
449 parser.AddArgument(UniqueSetValue<char, TRANSLATE_MODE_WINDOWS>{Mode, Usage}, nullptr, TRANSLATE_MODE_WINDOWS);
450 parser.AddArgument(UniqueSetValue<char, TRANSLATE_MODE_MIXED>{Mode, Usage}, nullptr, TRANSLATE_MODE_MIXED);
451 parser.AddArgument(UniqueSetValue<char, TRANSLATE_MODE_HELP>{Mode, Usage}, "--help");
452
453 try
454 {
455 parser.Parse();
456 }
457 catch (const wil::ExceptionWithUserMessage& e)
458 {
459 fprintf(stderr, "%s\n", e.what());
460 return 1;
461 }
462
463 if (OriginalPath == nullptr || Mode == TRANSLATE_MODE_HELP)
464 {
465 INVALID_USAGE();
466 }
467
468 SourcePath = strdup(OriginalPath);
469 if (SourcePath == nullptr)
470 {
471 Die(Argv[0], errno, false, nullptr);
472 }
473
474 //
475 // Translate the path.
476 //
477
478 OutputPath = WslPathTranslate(SourcePath, Flags, Mode.value_or(TRANSLATE_MODE_UNIX));
479 if (OutputPath.empty())
480 {
481 Die(Argv[0], errno, false, "%s", OriginalPath);
482 }
483
484 //
485 // Print the translated path and a newline.
486 //
487
488 Result = printf("%s\n", OutputPath.empty() ? "." : OutputPath.c_str());
489 if (Result < 0)
490 {
491 Die(Argv[0], errno, false, nullptr);
492 }
493
494 //
495 // Don't bother cleaning up since the process is exiting.
496 //
497
498 return 0;
499 }
500
501 std::string WslPathTranslate(char* Path, int Flags, char Mode)
502
503 /*++
504
505 Routine Description:
506
507 This routine translates an absolute or relative NT or DrvFs path.
508
509 Arguments:
510
511 Path - Supplies the path to translate.
512
513 Flags - Supplies flags for the operation.
514
515 Mode - Supplies the translation mode.
516
517 Return Value:
518
519 The translated path.
520
521 --*/
522
523 {
524 bool Absolute;
525 std::string CanonicalPath{};
526 char* OutputCwd = nullptr;
527 size_t OutputCwdLength;
528 char* RealPath = nullptr;
529 bool Relative;
530 std::string TranslatedPath{};
531 char UnixCwd[PATH_MAX];
532 std::string WindowsCwd{};
533
534 //
535 // Validate input.
536 //
537
538 if (!Path || Flags & ~(TRANSLATE_FLAG_ABSOLUTE | TRANSLATE_FLAG_RESOLVE_SYMLINKS))
539 {
540 goto WslPathTranslateExit;
541 }
542
543 Absolute = ((Flags & TRANSLATE_FLAG_ABSOLUTE) != 0);
544
545 //
546 // Validate the translation mode.
547 //
548
549 switch (Mode)
550 {
551 case TRANSLATE_MODE_UNIX:
552 case TRANSLATE_MODE_WINDOWS:
553 case TRANSLATE_MODE_MIXED:
554 break;
555
556 default:
557 goto WslPathTranslateExit;
558 }
559
560 //
561 // Get the current working directory for relative path translations.
562 //
563
564 if (getcwd(UnixCwd, sizeof(UnixCwd)) == NULL)
565 {
566 goto WslPathTranslateExit;
567 }
568
569 //
570 // Get the canonical path.
571 //
572
573 if (Mode == TRANSLATE_MODE_UNIX)
574 {
575 UtilCanonicalisePathSeparator(Path, PATH_SEP_NT);
576 OutputCwd = UnixCwd;
577 CanonicalPath = DosToCanonicalPath(Path, UnixCwd, sizeof(UnixCwd), &Relative);
578 if (CanonicalPath.empty())
579 {
580 goto WslPathTranslateExit;
581 }
582 }
583 else
584 {
585 //
586 // If the resolve symlinks flag is specified, resolve any dot entries or
587 // symlinks in the path. For paths that do not exist, use the supplied path
588 // as-is.
589 //
590
591 if ((Flags & TRANSLATE_FLAG_RESOLVE_SYMLINKS) != 0)
592 {
593 RealPath = realpath(Path, NULL);
594 if (RealPath != nullptr)
595 {
596 //
597 // Preserve trailing slashes.
598 //
599
600 std::string_view PathView = Path;
601 if (!PathView.empty() && PathView.back() == PATH_SEP)
602 {
603 CanonicalPath = RealPath;
604 CanonicalPath += PATH_SEP;
605 Path = CanonicalPath.data();
606 }
607 else
608 {
609 Path = RealPath;
610 }
611 }
612 else if (errno != ENOENT)
613 {
614 goto WslPathTranslateExit;
615 }
616 }
617
618 CanonicalPath = AbsolutePath(Path, UnixCwd, sizeof(UnixCwd), &Relative);
619 if (CanonicalPath.empty())
620 {
621 goto WslPathTranslateExit;
622 }
623
624 //
625 // Translate the cwd if it will be needed to translate back to a
626 // relative path.
627 //
628
629 if (Relative && !Absolute)
630 {
631 WindowsCwd = UtilWinPathTranslate(UnixCwd, false);
632 OutputCwd = WindowsCwd.empty() ? NULL : WindowsCwd.data();
633 }
634 }
635
636 //
637 // Perform the translation
638 //
639
640 TranslatedPath = UtilWinPathTranslate(CanonicalPath.data(), Mode == TRANSLATE_MODE_UNIX);
641 if (TranslatedPath.empty())
642 {
643 goto WslPathTranslateExit;
644 }
645
646 //
647 // Convert the absolute path back into a relative one
648 // if it is a subpath of the current directory.
649 //
650
651 if (Relative && !Absolute && OutputCwd != nullptr)
652 {
653 OutputCwdLength = strlen(OutputCwd);
654 if (strncmp(TranslatedPath.c_str(), OutputCwd, OutputCwdLength) == 0)
655 {
656 switch (TranslatedPath[OutputCwdLength])
657 {
658 case PATH_SEP_NT:
659 case PATH_SEP:
660 if (OutputCwdLength + 1 == TranslatedPath.length())
661 {
662 // Special case for wslpath -u .
663 TranslatedPath = ".";
664 }
665 else
666 {
667 TranslatedPath = TranslatedPath.substr(OutputCwdLength + 1);
668 }
669 break;
670
671 case '\0':
672 TranslatedPath = TranslatedPath.substr(OutputCwdLength);
673 break;
674 }
675 }
676 }
677
678 if (Mode == TRANSLATE_MODE_MIXED)
679 {
680 UtilCanonicalisePathSeparator(TranslatedPath, PATH_SEP);
681 }
682
683 WslPathTranslateExit:
684 free(RealPath);
685 return TranslatedPath;
686 }