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