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