1 /**
2  * Encapsulate path and file names.
3  *
4  * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
5  * Authors:   Walter Bright, http://www.digitalmars.com
6  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:    $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/root/filename.d, root/_filename.d)
8  * Documentation:  https://dlang.org/phobos/dmd_root_filename.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/filename.d
10  */
11 
12 module dmd.root.filename;
13 
14 import core.stdc.ctype;
15 import core.stdc.errno;
16 import core.stdc.string;
17 import dmd.root.array;
18 import dmd.root.file;
19 import dmd.root.outbuffer;
20 import dmd.root.port;
21 import dmd.root.rmem;
22 import dmd.root.rootobject;
23 import dmd.root.string;
24 
25 version (Posix)
26 {
27     import core.sys.posix.stdlib;
28     import core.sys.posix.sys.stat;
29     import core.sys.posix.unistd : getcwd;
30 }
31 
32 version (Windows)
33 {
34     import core.sys.windows.winbase;
35     import core.sys.windows.windef;
36     import core.sys.windows.winnls;
37 
38     extern (Windows) DWORD GetFullPathNameW(LPCWSTR, DWORD, LPWSTR, LPWSTR*) nothrow @nogc;
39     extern (Windows) void SetLastError(DWORD) nothrow @nogc;
40     extern (C) char* getcwd(char* buffer, size_t maxlen) nothrow;
41 
42     // assume filenames encoded in system default Windows ANSI code page
43     private enum CodePage = CP_ACP;
44 }
45 
46 version (CRuntime_Glibc)
47 {
48     extern (C) char* canonicalize_file_name(const char*) nothrow;
49 }
50 
51 alias Strings = Array!(const(char)*);
52 
53 
54 // Check whether character is a directory separator
55 private bool isDirSeparator(char c) pure nothrow @nogc
56 {
57     version (Windows)
58     {
59         return c == '\\' || c == '/';
60     }
61     else version (Posix)
62     {
63         return c == '/';
64     }
65     else
66     {
67         assert(0);
68     }
69 }
70 
71 /***********************************************************
72  * Encapsulate path and file names.
73  */
74 struct FileName
75 {
76 nothrow:
77     private const(char)[] str;
78 
79     ///
80     extern (D) this(const(char)[] str) pure
81     {
82         this.str = str.xarraydup;
83     }
84 
85     /// Compare two name according to the platform's rules (case sensitive or not)
86     extern (C++) static bool equals(const(char)* name1, const(char)* name2) pure @nogc
87     {
88         return equals(name1.toDString, name2.toDString);
89     }
90 
91     /// Ditto
92     extern (D) static bool equals(const(char)[] name1, const(char)[] name2) pure @nogc
93     {
94         if (name1.length != name2.length)
95             return false;
96 
97         version (Windows)
98         {
99             return name1.ptr == name2.ptr ||
100                    Port.memicmp(name1.ptr, name2.ptr, name1.length) == 0;
101         }
102         else
103         {
104             return name1 == name2;
105         }
106     }
107 
108     /************************************
109      * Determine if path is absolute.
110      * Params:
111      *  name = path
112      * Returns:
113      *  true if absolute path name.
114      */
115     extern (C++) static bool absolute(const(char)* name) pure @nogc
116     {
117         return absolute(name.toDString);
118     }
119 
120     /// Ditto
121     extern (D) static bool absolute(const(char)[] name) pure @nogc
122     {
123         if (!name.length)
124             return false;
125 
126         version (Windows)
127         {
128             return isDirSeparator(name[0])
129                 || (name.length >= 2 && name[1] == ':');
130         }
131         else version (Posix)
132         {
133             return isDirSeparator(name[0]);
134         }
135         else
136         {
137             assert(0);
138         }
139     }
140 
141     unittest
142     {
143         assert(absolute("/"[]) == true);
144         assert(absolute(""[]) == false);
145 
146         version (Windows)
147         {
148             assert(absolute(r"\"[]) == true);
149             assert(absolute(r"\\"[]) == true);
150             assert(absolute(r"c:"[]) == true);
151         }
152     }
153 
154     /**
155     Return the given name as an absolute path
156 
157     Params:
158         name = path
159         base = the absolute base to prefix name with if it is relative
160 
161     Returns: name as an absolute path relative to base
162     */
163     extern (C++) static const(char)* toAbsolute(const(char)* name, const(char)* base = null)
164     {
165         const name_ = name.toDString();
166         const base_ = base ? base.toDString() : getcwd(null, 0).toDString();
167         return absolute(name_) ? name : combine(base_, name_).ptr;
168     }
169 
170     /********************************
171      * Determine file name extension as slice of input.
172      * Params:
173      *  str = file name
174      * Returns:
175      *  filename extension (read-only).
176      *  Points past '.' of extension.
177      *  If there isn't one, return null.
178      */
179     extern (C++) static const(char)* ext(const(char)* str) pure @nogc
180     {
181         return ext(str.toDString).ptr;
182     }
183 
184     /// Ditto
185     extern (D) static const(char)[] ext(const(char)[] str) nothrow pure @safe @nogc
186     {
187         foreach_reverse (idx, char e; str)
188         {
189             switch (e)
190             {
191             case '.':
192                 return str[idx + 1 .. $];
193             version (Posix)
194             {
195             case '/':
196                 return null;
197             }
198             version (Windows)
199             {
200             case '\\':
201             case ':':
202             case '/':
203                 return null;
204             }
205             default:
206                 continue;
207             }
208         }
209         return null;
210     }
211 
212     unittest
213     {
214         assert(ext("/foo/bar/dmd.conf"[]) == "conf");
215         assert(ext("object.o"[]) == "o");
216         assert(ext("/foo/bar/dmd"[]) == null);
217         assert(ext(".objdir.o/object"[]) == null);
218         assert(ext([]) == null);
219     }
220 
221     extern (C++) const(char)* ext() const pure @nogc
222     {
223         return ext(str).ptr;
224     }
225 
226     /********************************
227      * Return file name without extension.
228      *
229      * TODO:
230      * Once slice are used everywhere and `\0` is not assumed,
231      * this can be turned into a simple slicing.
232      *
233      * Params:
234      *  str = file name
235      *
236      * Returns:
237      *  mem.xmalloc'd filename with extension removed.
238      */
239     extern (C++) static const(char)* removeExt(const(char)* str)
240     {
241         return removeExt(str.toDString).ptr;
242     }
243 
244     /// Ditto
245     extern (D) static const(char)[] removeExt(const(char)[] str)
246     {
247         auto e = ext(str);
248         if (e.length)
249         {
250             const len = (str.length - e.length) - 1; // -1 for the dot
251             char* n = cast(char*)mem.xmalloc(len + 1);
252             memcpy(n, str.ptr, len);
253             n[len] = 0;
254             return n[0 .. len];
255         }
256         return mem.xstrdup(str.ptr)[0 .. str.length];
257     }
258 
259     unittest
260     {
261         assert(removeExt("/foo/bar/object.d"[]) == "/foo/bar/object");
262         assert(removeExt("/foo/bar/frontend.di"[]) == "/foo/bar/frontend");
263     }
264 
265     /********************************
266      * Return filename name excluding path (read-only).
267      */
268     extern (C++) static const(char)* name(const(char)* str) pure @nogc
269     {
270         return name(str.toDString).ptr;
271     }
272 
273     /// Ditto
274     extern (D) static const(char)[] name(const(char)[] str) pure @nogc
275     {
276         foreach_reverse (idx, char e; str)
277         {
278             switch (e)
279             {
280                 version (Posix)
281                 {
282                 case '/':
283                     return str[idx + 1 .. $];
284                 }
285                 version (Windows)
286                 {
287                 case '/':
288                 case '\\':
289                     return str[idx + 1 .. $];
290                 case ':':
291                     /* The ':' is a drive letter only if it is the second
292                      * character or the last character,
293                      * otherwise it is an ADS (Alternate Data Stream) separator.
294                      * Consider ADS separators as part of the file name.
295                      */
296                     if (idx == 1 || idx == str.length - 1)
297                         return str[idx + 1 .. $];
298                     break;
299                 }
300             default:
301                 break;
302             }
303         }
304         return str;
305     }
306 
307     extern (C++) const(char)* name() const pure @nogc
308     {
309         return name(str).ptr;
310     }
311 
312     unittest
313     {
314         assert(name("/foo/bar/object.d"[]) == "object.d");
315         assert(name("/foo/bar/frontend.di"[]) == "frontend.di");
316     }
317 
318     /**************************************
319      * Return path portion of str.
320      * returned string is newly allocated
321      * Path does not include trailing path separator.
322      */
323     extern (C++) static const(char)* path(const(char)* str)
324     {
325         return path(str.toDString).ptr;
326     }
327 
328     /// Ditto
329     extern (D) static const(char)[] path(const(char)[] str)
330     {
331         const n = name(str);
332         bool hasTrailingSlash;
333         if (n.length < str.length)
334         {
335             if (isDirSeparator(str[$ - n.length - 1]))
336                 hasTrailingSlash = true;
337         }
338         const pathlen = str.length - n.length - (hasTrailingSlash ? 1 : 0);
339         char* path = cast(char*)mem.xmalloc(pathlen + 1);
340         memcpy(path, str.ptr, pathlen);
341         path[pathlen] = 0;
342         return path[0 .. pathlen];
343     }
344 
345     unittest
346     {
347         assert(path("/foo/bar"[]) == "/foo");
348         assert(path("foo"[]) == "");
349     }
350 
351     /**************************************
352      * Replace filename portion of path.
353      */
354     extern (D) static const(char)[] replaceName(const(char)[] path, const(char)[] name)
355     {
356         if (absolute(name))
357             return name;
358         auto n = FileName.name(path);
359         if (n == path)
360             return name;
361         return combine(path[0 .. $ - n.length], name);
362     }
363 
364     /**
365        Combine a `path` and a file `name`
366 
367        Params:
368          path = Path to append to
369          name = Name to append to path
370 
371        Returns:
372          The `\0` terminated string which is the combination of `path` and `name`
373          and a valid path.
374     */
375     extern (C++) static const(char)* combine(const(char)* path, const(char)* name)
376     {
377         if (!path)
378             return name;
379         return combine(path.toDString, name.toDString).ptr;
380     }
381 
382     /// Ditto
383     extern(D) static const(char)[] combine(const(char)[] path, const(char)[] name)
384     {
385         return !path.length ? name : buildPath(path, name);
386     }
387 
388     unittest
389     {
390         version (Windows)
391             assert(combine("foo"[], "bar"[]) == "foo\\bar");
392         else
393             assert(combine("foo"[], "bar"[]) == "foo/bar");
394         assert(combine("foo/"[], "bar"[]) == "foo/bar");
395     }
396 
397     static const(char)[] buildPath(const(char)[][] fragments...)
398     {
399         size_t size;
400         foreach (f; fragments)
401             size += f.length ? f.length + 1 : 0;
402         if (size == 0)
403             size = 1;
404 
405         char* p = cast(char*) mem.xmalloc_noscan(size);
406         size_t length;
407         foreach (f; fragments)
408         {
409             if (!f.length)
410                 continue;
411 
412             p[length .. length + f.length] = f;
413             length += f.length;
414 
415             const last = p[length - 1];
416             version (Posix)
417             {
418                 if (!isDirSeparator(last))
419                     p[length++] = '/';
420             }
421             else version (Windows)
422             {
423                 if (!isDirSeparator(last) && last != ':')
424                     p[length++] = '\\';
425             }
426             else
427                 assert(0);
428         }
429 
430         // overwrite last slash with null terminator
431         p[length ? --length : 0] = 0;
432 
433         return p[0 .. length];
434     }
435 
436     unittest
437     {
438         assert(buildPath() == "");
439         assert(buildPath("foo") == "foo");
440         assert(buildPath("foo", null) == "foo");
441         assert(buildPath(null, "foo") == "foo");
442         version (Windows)
443             assert(buildPath("C:", r"a\", "bb/", "ccc", "d") == r"C:a\bb/ccc\d");
444         else
445             assert(buildPath("a/", "bb", "ccc") == "a/bb/ccc");
446     }
447 
448     // Split a path into an Array of paths
449     extern (C++) static Strings* splitPath(const(char)* path)
450     {
451         auto array = new Strings();
452         int sink(const(char)* p) nothrow
453         {
454             array.push(p);
455             return 0;
456         }
457         splitPath(&sink, path);
458         return array;
459     }
460 
461     /****
462      * Split path (such as that returned by `getenv("PATH")`) into pieces, each piece is mem.xmalloc'd
463      * Handle double quotes and ~.
464      * Pass the pieces to sink()
465      * Params:
466      *  sink = send the path pieces here, end when sink() returns !=0
467      *  path = the path to split up.
468      */
469     static void splitPath(int delegate(const(char)*) nothrow sink, const(char)* path)
470     {
471         if (!path)
472             return;
473 
474         auto p = path;
475         OutBuffer buf;
476         char c;
477         do
478         {
479             const(char)* home;
480             bool instring = false;
481             while (isspace(*p)) // skip leading whitespace
482                 ++p;
483             buf.reserve(8); // guess size of piece
484             for (;; ++p)
485             {
486                 c = *p;
487                 switch (c)
488                 {
489                     case '"':
490                         instring ^= false; // toggle inside/outside of string
491                         continue;
492 
493                     version (OSX)
494                     {
495                     case ',':
496                     }
497                     version (Windows)
498                     {
499                     case ';':
500                     }
501                     version (Posix)
502                     {
503                     case ':':
504                     }
505                         p++;    // ; cannot appear as part of a
506                         break;  // path, quotes won't protect it
507 
508                     case 0x1A:  // ^Z means end of file
509                     case 0:
510                         break;
511 
512                     case '\r':
513                         continue;  // ignore carriage returns
514 
515                     version (Posix)
516                     {
517                     case '~':
518                         if (!home)
519                             home = getenv("HOME");
520                         // Expand ~ only if it is prefixing the rest of the path.
521                         if (!buf.length && p[1] == '/' && home)
522                             buf.writestring(home);
523                         else
524                             buf.writeByte('~');
525                         continue;
526                     }
527 
528                     version (none)
529                     {
530                     case ' ':
531                     case '\t':         // tabs in filenames?
532                         if (!instring) // if not in string
533                             break;     // treat as end of path
534                     }
535                     default:
536                         buf.writeByte(c);
537                         continue;
538                 }
539                 break;
540             }
541             if (buf.length) // if path is not empty
542             {
543                 if (sink(buf.extractChars()))
544                     break;
545             }
546         } while (c);
547     }
548 
549     /**
550      * Add the extension `ext` to `name`, regardless of the content of `name`
551      *
552      * Params:
553      *   name = Path to append the extension to
554      *   ext  = Extension to add (should not include '.')
555      *
556      * Returns:
557      *   A newly allocated string (free with `FileName.free`)
558      */
559     extern(D) static char[] addExt(const(char)[] name, const(char)[] ext) pure
560     {
561         const len = name.length + ext.length + 2;
562         auto s = cast(char*)mem.xmalloc(len);
563         s[0 .. name.length] = name[];
564         s[name.length] = '.';
565         s[name.length + 1 .. len - 1] = ext[];
566         s[len - 1] = '\0';
567         return s[0 .. len - 1];
568     }
569 
570 
571     /***************************
572      * Free returned value with FileName::free()
573      */
574     extern (C++) static const(char)* defaultExt(const(char)* name, const(char)* ext)
575     {
576         return defaultExt(name.toDString, ext.toDString).ptr;
577     }
578 
579     /// Ditto
580     extern (D) static const(char)[] defaultExt(const(char)[] name, const(char)[] ext)
581     {
582         auto e = FileName.ext(name);
583         if (e.length) // it already has an extension
584             return name.xarraydup;
585         return addExt(name, ext);
586     }
587 
588     unittest
589     {
590         assert(defaultExt("/foo/object.d"[], "d") == "/foo/object.d");
591         assert(defaultExt("/foo/object"[], "d") == "/foo/object.d");
592         assert(defaultExt("/foo/bar.d"[], "o") == "/foo/bar.d");
593     }
594 
595     /***************************
596      * Free returned value with FileName::free()
597      */
598     extern (C++) static const(char)* forceExt(const(char)* name, const(char)* ext)
599     {
600         return forceExt(name.toDString, ext.toDString).ptr;
601     }
602 
603     /// Ditto
604     extern (D) static const(char)[] forceExt(const(char)[] name, const(char)[] ext)
605     {
606         if (auto e = FileName.ext(name))
607             return addExt(name[0 .. $ - e.length - 1], ext);
608         return defaultExt(name, ext); // doesn't have one
609     }
610 
611     unittest
612     {
613         assert(forceExt("/foo/object.d"[], "d") == "/foo/object.d");
614         assert(forceExt("/foo/object"[], "d") == "/foo/object.d");
615         assert(forceExt("/foo/bar.d"[], "o") == "/foo/bar.o");
616     }
617 
618     /// Returns:
619     ///   `true` if `name`'s extension is `ext`
620     extern (C++) static bool equalsExt(const(char)* name, const(char)* ext) pure @nogc
621     {
622         return equalsExt(name.toDString, ext.toDString);
623     }
624 
625     /// Ditto
626     extern (D) static bool equalsExt(const(char)[] name, const(char)[] ext) pure @nogc
627     {
628         auto e = FileName.ext(name);
629         if (!e.length && !ext.length)
630             return true;
631         if (!e.length || !ext.length)
632             return false;
633         return FileName.equals(e, ext);
634     }
635 
636     unittest
637     {
638         assert(!equalsExt("foo.bar"[], "d"));
639         assert(equalsExt("foo.bar"[], "bar"));
640         assert(equalsExt("object.d"[], "d"));
641         assert(!equalsExt("object"[], "d"));
642     }
643 
644     /******************************
645      * Return !=0 if extensions match.
646      */
647     extern (C++) bool equalsExt(const(char)* ext) const pure @nogc
648     {
649         return equalsExt(str, ext.toDString());
650     }
651 
652     /*************************************
653      * Search paths for file.
654      * Params:
655      *  path = array of path strings
656      *  name = file to look for
657      *  cwd = true means search current directory before searching path
658      * Returns:
659      *  if found, filename combined with path, otherwise null
660      */
661     extern (C++) static const(char)* searchPath(Strings* path, const(char)* name, bool cwd)
662     {
663         return searchPath(path, name.toDString, cwd).ptr;
664     }
665 
666     extern (D) static const(char)[] searchPath(Strings* path, const(char)[] name, bool cwd)
667     {
668         if (absolute(name))
669         {
670             return exists(name) ? name : null;
671         }
672         if (cwd)
673         {
674             if (exists(name))
675                 return name;
676         }
677         if (path)
678         {
679             foreach (p; *path)
680             {
681                 auto n = combine(p.toDString, name);
682                 if (exists(n))
683                     return n;
684                 //combine might return name
685                 if (n.ptr != name.ptr)
686                 {
687                     mem.xfree(cast(void*)n.ptr);
688                 }
689             }
690         }
691         return null;
692     }
693 
694     extern (D) static const(char)[] searchPath(const(char)* path, const(char)[] name, bool cwd)
695     {
696         if (absolute(name))
697         {
698             return exists(name) ? name : null;
699         }
700         if (cwd)
701         {
702             if (exists(name))
703                 return name;
704         }
705         if (path && *path)
706         {
707             const(char)[] result;
708 
709             int sink(const(char)* p) nothrow
710             {
711                 auto n = combine(p.toDString, name);
712                 mem.xfree(cast(void*)p);
713                 if (exists(n))
714                 {
715                     result = n;
716                     return 1;   // done with splitPath() call
717                 }
718                 return 0;
719             }
720 
721             splitPath(&sink, path);
722             return result;
723         }
724         return null;
725     }
726 
727     /************************************
728      * Determine if path contains reserved character.
729      * Params:
730      *  name = path
731      * Returns:
732      *  index of the first reserved character in path if found, size_t.max otherwise
733      */
734     extern (D) static size_t findReservedChar(const(char)* name) pure @nogc
735     {
736         version (Windows)
737         {
738             size_t idx = 0;
739             // According to https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
740             // the following characters are not allowed in path: < > : " | ? *
741             for (const(char)* p = name; *p; p++, idx++)
742             {
743                 char c = *p;
744                 if (c == '<' || c == '>' || c == ':' || c == '"' || c == '|' || c == '?' || c == '*')
745                 {
746                     return idx;
747                 }
748             }
749             return size_t.max;
750         }
751         else
752         {
753             return size_t.max;
754         }
755     }
756     unittest
757     {
758         assert(findReservedChar(r"") == size_t.max);
759         assert(findReservedChar(r" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-_=+()") == size_t.max);
760 
761         version (Windows)
762         {
763             assert(findReservedChar(` < `) == 1);
764             assert(findReservedChar(` >`) == 1);
765             assert(findReservedChar(`: `) == 0);
766             assert(findReservedChar(`"`) == 0);
767             assert(findReservedChar(`|`) == 0);
768             assert(findReservedChar(`?`) == 0);
769             assert(findReservedChar(`*`) == 0);
770         }
771         else
772         {
773             assert(findReservedChar(`<>:"|?*`) == size_t.max);
774         }
775     }
776 
777     /************************************
778      * Determine if path has a reference to parent directory.
779      * Params:
780      *  name = path
781      * Returns:
782      *  true if path contains '..' reference to parent directory
783      */
784     extern (D) static bool refersToParentDir(const(char)* name) pure @nogc
785     {
786         if (name[0] == '.' && name[1] == '.' && (!name[2] || isDirSeparator(name[2])))
787         {
788             return true;
789         }
790 
791         for (const(char)* p = name; *p; p++)
792         {
793             char c = *p;
794             if (isDirSeparator(c) && p[1] == '.' && p[2] == '.' && (!p[3] || isDirSeparator(p[3])))
795             {
796                 return true;
797             }
798         }
799         return false;
800     }
801     unittest
802     {
803         assert(!refersToParentDir(r""));
804         assert(!refersToParentDir(r"foo"));
805         assert(!refersToParentDir(r"foo.."));
806         assert(!refersToParentDir(r"foo..boo"));
807         assert(!refersToParentDir(r"foo/..boo"));
808         assert(!refersToParentDir(r"foo../boo"));
809         assert(refersToParentDir(r".."));
810         assert(refersToParentDir(r"../"));
811         assert(refersToParentDir(r"foo/.."));
812         assert(refersToParentDir(r"foo/../"));
813         assert(refersToParentDir(r"foo/../../boo"));
814 
815         version (Windows)
816         {
817             // Backslash as directory separator
818             assert(!refersToParentDir(r"foo\..boo"));
819             assert(!refersToParentDir(r"foo..\boo"));
820             assert(refersToParentDir(r"..\"));
821             assert(refersToParentDir(r"foo\.."));
822             assert(refersToParentDir(r"foo\..\"));
823             assert(refersToParentDir(r"foo\..\..\boo"));
824         }
825     }
826 
827 
828     /**
829        Check if the file the `path` points to exists
830 
831        Returns:
832          0 if it does not exists
833          1 if it exists and is not a directory
834          2 if it exists and is a directory
835      */
836     extern (C++) static int exists(const(char)* name)
837     {
838         return exists(name.toDString);
839     }
840 
841     /// Ditto
842     extern (D) static int exists(const(char)[] name)
843     {
844         if (!name.length)
845             return 0;
846         version (Posix)
847         {
848             stat_t st;
849             if (name.toCStringThen!((v) => stat(v.ptr, &st)) < 0)
850                 return 0;
851             if (S_ISDIR(st.st_mode))
852                 return 2;
853             return 1;
854         }
855         else version (Windows)
856         {
857             return name.toWStringzThen!((wname)
858             {
859                 const dw = GetFileAttributesW(&wname[0]);
860                 if (dw == -1)
861                     return 0;
862                 else if (dw & FILE_ATTRIBUTE_DIRECTORY)
863                     return 2;
864                 else
865                     return 1;
866             });
867         }
868         else
869         {
870             assert(0);
871         }
872     }
873 
874     /**
875        Ensure that the provided path exists
876 
877        Accepts a path to either a file or a directory.
878        In the former case, the basepath (path to the containing directory)
879        will be checked for existence, and created if it does not exists.
880        In the later case, the directory pointed to will be checked for existence
881        and created if needed.
882 
883        Params:
884          path = a path to a file or a directory
885 
886        Returns:
887          `true` if the directory exists or was successfully created
888      */
889     extern (D) static bool ensurePathExists(const(char)[] path)
890     {
891         //printf("FileName::ensurePathExists(%s)\n", path ? path : "");
892         if (!path.length)
893             return true;
894         if (exists(path))
895             return true;
896 
897         // We were provided with a file name
898         // We need to call ourselves recursively to ensure parent dir exist
899         const char[] p = FileName.path(path);
900         if (p.length)
901         {
902             version (Windows)
903             {
904                 // Note: Windows filename comparison should be case-insensitive,
905                 // however p is a subslice of path so we don't need it
906                 if (path.length == p.length ||
907                     (path.length > 2 && path[1] == ':' && path[2 .. $] == p))
908                 {
909                     mem.xfree(cast(void*)p.ptr);
910                     return true;
911                 }
912             }
913             const r = ensurePathExists(p);
914             mem.xfree(cast(void*)p);
915 
916             if (!r)
917                 return r;
918         }
919 
920         version (Windows)
921             const r = _mkdir(path);
922         version (Posix)
923         {
924             errno = 0;
925             const r = path.toCStringThen!((pathCS) => mkdir(pathCS.ptr, (7 << 6) | (7 << 3) | 7));
926         }
927 
928         if (r == 0)
929             return true;
930 
931         // Don't error out if another instance of dmd just created
932         // this directory
933         version (Windows)
934         {
935             import core.sys.windows.winerror : ERROR_ALREADY_EXISTS;
936             if (GetLastError() == ERROR_ALREADY_EXISTS)
937                 return true;
938         }
939         version (Posix)
940         {
941             if (errno == EEXIST)
942                 return true;
943         }
944 
945         return false;
946     }
947 
948     ///ditto
949     extern (C++) static bool ensurePathExists(const(char)* path)
950     {
951         return ensurePathExists(path.toDString);
952     }
953 
954     /******************************************
955      * Return canonical version of name.
956      * This code is high risk.
957      */
958     extern (C++) static const(char)* canonicalName(const(char)* name)
959     {
960         return canonicalName(name.toDString).ptr;
961     }
962 
963     /// Ditto
964     extern (D) static const(char)[] canonicalName(const(char)[] name)
965     {
966         version (Posix)
967         {
968             import core.stdc.limits;      // PATH_MAX
969             import core.sys.posix.unistd; // _PC_PATH_MAX
970 
971             // Older versions of druntime don't have PATH_MAX defined.
972             // i.e: dmd __VERSION__ < 2085, gdc __VERSION__ < 2076.
973             static if (!__traits(compiles, PATH_MAX))
974             {
975                 version (DragonFlyBSD)
976                     enum PATH_MAX = 1024;
977                 else version (FreeBSD)
978                     enum PATH_MAX = 1024;
979                 else version (linux)
980                     enum PATH_MAX = 4096;
981                 else version (NetBSD)
982                     enum PATH_MAX = 1024;
983                 else version (OpenBSD)
984                     enum PATH_MAX = 1024;
985                 else version (OSX)
986                     enum PATH_MAX = 1024;
987                 else version (Solaris)
988                     enum PATH_MAX = 1024;
989             }
990 
991             // Have realpath(), passing a NULL destination pointer may return an
992             // internally malloc'd buffer, however it is implementation defined
993             // as to what happens, so cannot rely on it.
994             static if (__traits(compiles, PATH_MAX))
995             {
996                 // Have compile time limit on filesystem path, use it with realpath.
997                 char[PATH_MAX] buf = void;
998                 auto path = name.toCStringThen!((n) => realpath(n.ptr, buf.ptr));
999                 if (path !is null)
1000                     return xarraydup(path.toDString);
1001             }
1002             else static if (__traits(compiles, canonicalize_file_name))
1003             {
1004                 // Have canonicalize_file_name, which malloc's memory.
1005                 // We need a dmd.root.rmem allocation though.
1006                 auto path = name.toCStringThen!((n) => canonicalize_file_name(n.ptr));
1007                 scope(exit) .free(path.ptr);
1008                 if (path !is null)
1009                     return xarraydup(path.toDString);
1010             }
1011             else static if (__traits(compiles, _PC_PATH_MAX))
1012             {
1013                 // Panic! Query the OS for the buffer limit.
1014                 auto path_max = pathconf("/", _PC_PATH_MAX);
1015                 if (path_max > 0)
1016                 {
1017                     char *buf = cast(char*)mem.xmalloc(path_max);
1018                     scope(exit) mem.xfree(buf);
1019                     auto path = name.toCStringThen!((n) => realpath(n.ptr, buf));
1020                     if (path !is null)
1021                         return xarraydup(path.toDString);
1022                 }
1023             }
1024             // Give up trying to support this platform, just duplicate the filename
1025             // unless there is nothing to copy from.
1026             if (!name.length)
1027                 return null;
1028             return xarraydup(name);
1029         }
1030         else version (Windows)
1031         {
1032             // Convert to wstring first since otherwise the Win32 APIs have a character limit
1033             return name.toWStringzThen!((wname)
1034             {
1035                 /* Apparently, there is no good way to do this on Windows.
1036                  * GetFullPathName isn't it, but use it anyway.
1037                  */
1038                 // First find out how long the buffer has to be, incl. terminating null.
1039                 const capacity = GetFullPathNameW(&wname[0], 0, null, null);
1040                 if (!capacity) return null;
1041                 auto buffer = cast(wchar*) mem.xmalloc_noscan(capacity * wchar.sizeof);
1042                 scope(exit) mem.xfree(buffer);
1043 
1044                 // Actually get the full path name. If the buffer is large enough,
1045                 // the returned length does NOT include the terminating null...
1046                 const length = GetFullPathNameW(&wname[0], capacity, buffer, null /*filePart*/);
1047                 assert(length == capacity - 1);
1048 
1049                 return toNarrowStringz(buffer[0 .. length]);
1050             });
1051         }
1052         else
1053         {
1054             assert(0);
1055         }
1056     }
1057 
1058     unittest
1059     {
1060         string filename = "foo.bar";
1061         const path = canonicalName(filename);
1062         scope(exit) free(path.ptr);
1063         assert(path.length >= filename.length);
1064         assert(path[$ - filename.length .. $] == filename);
1065     }
1066 
1067     /********************************
1068      * Free memory allocated by FileName routines
1069      */
1070     extern (C++) static void free(const(char)* str) pure
1071     {
1072         if (str)
1073         {
1074             assert(str[0] != cast(char)0xAB);
1075             memset(cast(void*)str, 0xAB, strlen(str) + 1); // stomp
1076         }
1077         mem.xfree(cast(void*)str);
1078     }
1079 
1080     extern (C++) const(char)* toChars() const pure nothrow @nogc @trusted
1081     {
1082         // Since we can return an empty slice (but '\0' terminated),
1083         // we don't do bounds check (as `&str[0]` does)
1084         return str.ptr;
1085     }
1086 
1087     const(char)[] toString() const pure nothrow @nogc @trusted
1088     {
1089         return str;
1090     }
1091 
1092     bool opCast(T)() const pure nothrow @nogc @safe
1093     if (is(T == bool))
1094     {
1095         return str.ptr !is null;
1096     }
1097 }
1098 
1099 version(Windows)
1100 {
1101     /****************************************************************
1102      * The code before used the POSIX function `mkdir` on Windows. That
1103      * function is now deprecated and fails with long paths, so instead
1104      * we use the newer `CreateDirectoryW`.
1105      *
1106      * `CreateDirectoryW` is the unicode version of the generic macro
1107      * `CreateDirectory`.  `CreateDirectoryA` has a file path
1108      *  limitation of 248 characters, `mkdir` fails with less and might
1109      *  fail due to the number of consecutive `..`s in the
1110      *  path. `CreateDirectoryW` also normally has a 248 character
1111      * limit, unless the path is absolute and starts with `\\?\`. Note
1112      * that this is different from starting with the almost identical
1113      * `\\?`.
1114      *
1115      * Params:
1116      *  path = The path to create.
1117      *
1118      * Returns:
1119      *  0 on success, 1 on failure.
1120      *
1121      * References:
1122      *  https://msdn.microsoft.com/en-us/library/windows/desktop/aa363855(v=vs.85).aspx
1123      */
1124     private int _mkdir(const(char)[] path) nothrow
1125     {
1126         const createRet = path.extendedPathThen!(
1127             p => CreateDirectoryW(&p[0], null /*securityAttributes*/));
1128         // different conventions for CreateDirectory and mkdir
1129         return createRet == 0 ? 1 : 0;
1130     }
1131 
1132     /**************************************
1133      * Converts a path to one suitable to be passed to Win32 API
1134      * functions that can deal with paths longer than 248
1135      * characters then calls the supplied function on it.
1136      *
1137      * Params:
1138      *  path = The Path to call F on.
1139      *
1140      * Returns:
1141      *  The result of calling F on path.
1142      *
1143      * References:
1144      *  https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
1145      */
1146     package auto extendedPathThen(alias F)(const(char)[] path)
1147     {
1148         if (!path.length)
1149             return F((wchar[]).init);
1150         return path.toWStringzThen!((wpath)
1151         {
1152             // GetFullPathNameW expects a sized buffer to store the result in. Since we don't
1153             // know how large it has to be, we pass in null and get the needed buffer length
1154             // as the return code.
1155             const pathLength = GetFullPathNameW(&wpath[0],
1156                                                 0 /*length8*/,
1157                                                 null /*output buffer*/,
1158                                                 null /*filePartBuffer*/);
1159             if (pathLength == 0)
1160             {
1161                 return F((wchar[]).init);
1162             }
1163 
1164             // wpath is the UTF16 version of path, but to be able to use
1165             // extended paths, we need to prefix with `\\?\` and the absolute
1166             // path.
1167             static immutable prefix = `\\?\`w;
1168 
1169             // prefix only needed for long names and non-UNC names
1170             const needsPrefix = pathLength >= MAX_PATH && (wpath[0] != '\\' || wpath[1] != '\\');
1171             const prefixLength = needsPrefix ? prefix.length : 0;
1172 
1173             // +1 for the null terminator
1174             const bufferLength = pathLength + prefixLength + 1;
1175 
1176             wchar[1024] absBuf = void;
1177             wchar[] absPath = bufferLength > absBuf.length
1178                 ? new wchar[bufferLength] : absBuf[0 .. bufferLength];
1179 
1180             absPath[0 .. prefixLength] = prefix[0 .. prefixLength];
1181 
1182             const absPathRet = GetFullPathNameW(&wpath[0],
1183                 cast(uint)(absPath.length - prefixLength - 1),
1184                 &absPath[prefixLength],
1185                 null /*filePartBuffer*/);
1186 
1187             if (absPathRet == 0 || absPathRet > absPath.length - prefixLength)
1188             {
1189                 return F((wchar[]).init);
1190             }
1191 
1192             absPath[$ - 1] = '\0';
1193             // Strip null terminator from the slice
1194             return F(absPath[0 .. $ - 1]);
1195         });
1196     }
1197 
1198     /**********************************
1199      * Converts a UTF-16 string to a (null-terminated) narrow string.
1200      * Returns:
1201      *  If `buffer` is specified and the result fits, a slice of that buffer,
1202      *  otherwise a new buffer which can be released via `mem.xfree()`.
1203      *  Nulls are propagated, i.e., if `wide` is null, the returned slice is
1204      *  null too.
1205      */
1206     char[] toNarrowStringz(const(wchar)[] wide, char[] buffer = null) nothrow
1207     {
1208         if (wide is null)
1209             return null;
1210 
1211         const requiredLength = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, buffer.ptr, cast(int) buffer.length, null, null);
1212         if (requiredLength < buffer.length)
1213         {
1214             buffer[requiredLength] = 0;
1215             return buffer[0 .. requiredLength];
1216         }
1217 
1218         char* newBuffer = cast(char*) mem.xmalloc_noscan(requiredLength + 1);
1219         const length = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, newBuffer, requiredLength, null, null);
1220         assert(length == requiredLength);
1221         newBuffer[length] = 0;
1222         return newBuffer[0 .. length];
1223     }
1224 
1225     /**********************************
1226      * Converts a narrow string to a (null-terminated) UTF-16 string.
1227      * Returns:
1228      *  If `buffer` is specified and the result fits, a slice of that buffer,
1229      *  otherwise a new buffer which can be released via `mem.xfree()`.
1230      *  Nulls are propagated, i.e., if `narrow` is null, the returned slice is
1231      *  null too.
1232      */
1233     wchar[] toWStringz(const(char)[] narrow, wchar[] buffer = null) nothrow
1234     {
1235         if (narrow is null)
1236             return null;
1237 
1238         const requiredLength = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, cast(int) buffer.length);
1239         if (requiredLength < buffer.length)
1240         {
1241             buffer[requiredLength] = 0;
1242             return buffer[0 .. requiredLength];
1243         }
1244 
1245         wchar* newBuffer = cast(wchar*) mem.xmalloc_noscan((requiredLength + 1) * wchar.sizeof);
1246         const length = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, newBuffer, requiredLength);
1247         assert(length == requiredLength);
1248         newBuffer[length] = 0;
1249         return newBuffer[0 .. length];
1250     }
1251 
1252     /**********************************
1253      * Converts a slice of UTF-8 characters to an array of wchar that's null
1254      * terminated so it can be passed to Win32 APIs then calls the supplied
1255      * function on it.
1256      *
1257      * Params:
1258      *  str = The string to convert.
1259      *
1260      * Returns:
1261      *  The result of calling F on the UTF16 version of str.
1262      */
1263     private auto toWStringzThen(alias F)(const(char)[] str) nothrow
1264     {
1265         if (!str.length) return F(""w.ptr);
1266 
1267         wchar[1024] buf = void;
1268         wchar[] wide = toWStringz(str, buf);
1269         scope(exit) wide.ptr != buf.ptr && mem.xfree(wide.ptr);
1270 
1271         return F(wide);
1272     }
1273 }