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