d: Merge upstream dmd, druntime 4ca4140e58, phobos 454dff14d.
[official-gcc.git] / gcc / d / dmd / root / filename.d
blob387361568e07576bb57510ef7caa8cb6e6cfe88c
1 /**
2 * Encapsulate path and file names.
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
12 module dmd.root.filename;
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;
26 version (Posix)
28 import core.sys.posix.stdlib;
29 import core.sys.posix.sys.stat;
30 import core.sys.posix.unistd : getcwd;
33 version (Windows)
35 import core.sys.windows.winbase;
36 import core.sys.windows.windef;
37 import core.sys.windows.winnls;
39 import dmd.common.string : extendedPathThen;
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;
45 // assume filenames encoded in system default Windows ANSI code page
46 private enum CodePage = CP_ACP;
49 version (CRuntime_Glibc)
51 extern (C) char* canonicalize_file_name(const char*) nothrow;
54 alias Strings = Array!(const(char)*);
57 // Check whether character is a directory separator
58 bool isDirSeparator(char c) pure nothrow @nogc @safe
60 version (Windows)
62 return c == '\\' || c == '/';
64 else version (Posix)
66 return c == '/';
68 else
70 assert(0);
74 /***********************************************************
75 * Encapsulate path and file names.
77 struct FileName
79 nothrow:
80 private const(char)[] str;
82 ///
83 extern (D) this(const(char)[] str) pure
85 this.str = str.xarraydup;
88 ///
89 extern (C++) static FileName create(const(char)* name) pure
91 return FileName(name.toDString);
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
97 return equals(name1.toDString, name2.toDString);
100 /// Ditto
101 extern (D) static bool equals(const(char)[] name1, const(char)[] name2) pure @nogc
103 if (name1.length != name2.length)
104 return false;
106 version (Windows)
108 return name1.ptr == name2.ptr ||
109 Port.memicmp(name1.ptr, name2.ptr, name1.length) == 0;
111 else
113 return name1 == name2;
117 /************************************
118 * Determine if path is absolute.
119 * Params:
120 * name = path
121 * Returns:
122 * true if absolute path name.
124 extern (C++) static bool absolute(const(char)* name) pure @nogc
126 return absolute(name.toDString);
129 /// Ditto
130 extern (D) static bool absolute(const(char)[] name) pure @nogc
132 if (!name.length)
133 return false;
135 version (Windows)
137 return isDirSeparator(name[0])
138 || (name.length >= 2 && name[1] == ':');
140 else version (Posix)
142 return isDirSeparator(name[0]);
144 else
146 assert(0);
150 unittest
152 assert(absolute("/"[]) == true);
153 assert(absolute(""[]) == false);
155 version (Windows)
157 assert(absolute(r"\"[]) == true);
158 assert(absolute(r"\\"[]) == true);
159 assert(absolute(r"c:"[]) == true);
164 Return the given name as an absolute path
166 Params:
167 name = path
168 base = the absolute base to prefix name with if it is relative
170 Returns: name as an absolute path relative to base
172 extern (C++) static const(char)* toAbsolute(const(char)* name, const(char)* base = null)
174 const name_ = name.toDString();
175 const base_ = base ? base.toDString() : getcwd(null, 0).toDString();
176 return absolute(name_) ? name : combine(base_, name_).ptr;
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.
188 extern (C++) static const(char)* ext(const(char)* str) pure @nogc
190 return ext(str.toDString).ptr;
193 /// Ditto
194 extern (D) static const(char)[] ext(const(char)[] str) nothrow pure @safe @nogc
196 foreach_reverse (idx, char e; str)
198 switch (e)
200 case '.':
201 return str[idx + 1 .. $];
202 version (Posix)
204 case '/':
205 return null;
207 version (Windows)
209 case '\\':
210 case ':':
211 case '/':
212 return null;
214 default:
215 continue;
218 return null;
221 unittest
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);
230 extern (C++) const(char)* ext() const pure @nogc
232 return ext(str).ptr;
235 /********************************
236 * Return file name without extension.
238 * TODO:
239 * Once slice are used everywhere and `\0` is not assumed,
240 * this can be turned into a simple slicing.
242 * Params:
243 * str = file name
245 * Returns:
246 * mem.xmalloc'd filename with extension removed.
248 extern (C++) static const(char)* removeExt(const(char)* str)
250 return removeExt(str.toDString).ptr;
253 /// Ditto
254 extern (D) static const(char)[] removeExt(const(char)[] str)
256 auto e = ext(str);
257 if (e.length)
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];
265 return mem.xstrdup(str.ptr)[0 .. str.length];
268 unittest
270 assert(removeExt("/foo/bar/object.d"[]) == "/foo/bar/object");
271 assert(removeExt("/foo/bar/frontend.di"[]) == "/foo/bar/frontend");
274 /********************************
275 * Return filename name excluding path (read-only).
277 extern (C++) static const(char)* name(const(char)* str) pure @nogc
279 return name(str.toDString).ptr;
282 /// Ditto
283 extern (D) static const(char)[] name(const(char)[] str) pure @nogc
285 foreach_reverse (idx, char e; str)
287 switch (e)
289 version (Posix)
291 case '/':
292 return str[idx + 1 .. $];
294 version (Windows)
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.
305 if (idx == 1 || idx == str.length - 1)
306 return str[idx + 1 .. $];
307 break;
309 default:
310 break;
313 return str;
316 extern (C++) const(char)* name() const pure @nogc
318 return name(str).ptr;
321 unittest
323 assert(name("/foo/bar/object.d"[]) == "object.d");
324 assert(name("/foo/bar/frontend.di"[]) == "frontend.di");
327 /**************************************
328 * Return path portion of str.
329 * returned string is newly allocated
330 * Path does not include trailing path separator.
332 extern (C++) static const(char)* path(const(char)* str)
334 return path(str.toDString).ptr;
337 /// Ditto
338 extern (D) static const(char)[] path(const(char)[] str)
340 const n = name(str);
341 bool hasTrailingSlash;
342 if (n.length < str.length)
344 if (isDirSeparator(str[$ - n.length - 1]))
345 hasTrailingSlash = true;
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];
354 unittest
356 assert(path("/foo/bar"[]) == "/foo");
357 assert(path("foo"[]) == "");
360 /**************************************
361 * Replace filename portion of path.
363 extern (D) static const(char)[] replaceName(const(char)[] path, const(char)[] name)
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);
374 Combine a `path` and a file `name`
376 Params:
377 path = Path to append to
378 name = Name to append to path
380 Returns:
381 The `\0` terminated string which is the combination of `path` and `name`
382 and a valid path.
384 extern (C++) static const(char)* combine(const(char)* path, const(char)* name)
386 if (!path)
387 return name;
388 return combine(path.toDString, name.toDString).ptr;
391 /// Ditto
392 extern(D) static const(char)[] combine(const(char)[] path, const(char)[] name)
394 return !path.length ? name : buildPath(path, name);
397 unittest
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");
406 static const(char)[] buildPath(const(char)[][] fragments...)
408 size_t size;
409 foreach (f; fragments)
410 size += f.length ? f.length + 1 : 0;
411 if (size == 0)
412 size = 1;
414 char* p = cast(char*) mem.xmalloc_noscan(size);
415 size_t length;
416 foreach (f; fragments)
418 if (!f.length)
419 continue;
421 p[length .. length + f.length] = f;
422 length += f.length;
424 const last = p[length - 1];
425 version (Posix)
427 if (!isDirSeparator(last))
428 p[length++] = '/';
430 else version (Windows)
432 if (!isDirSeparator(last) && last != ':')
433 p[length++] = '\\';
435 else
436 assert(0);
439 // overwrite last slash with null terminator
440 p[length ? --length : 0] = 0;
442 return p[0 .. length];
445 unittest
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");
457 // Split a path into an Array of paths
458 extern (C++) static Strings* splitPath(const(char)* path)
460 auto array = new Strings();
461 int sink(const(char)* p) nothrow
463 array.push(p);
464 return 0;
466 splitPath(&sink, path);
467 return array;
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.
478 static void splitPath(int delegate(const(char)*) nothrow sink, const(char)* path)
480 if (!path)
481 return;
483 auto p = path;
484 OutBuffer buf;
485 char c;
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)
495 c = *p;
496 switch (c)
498 case '"':
499 instring ^= false; // toggle inside/outside of string
500 continue;
502 version (OSX)
504 case ',':
506 version (Windows)
508 case ';':
510 version (Posix)
512 case ':':
514 p++; // ; cannot appear as part of a
515 break; // path, quotes won't protect it
517 case 0x1A: // ^Z means end of file
518 case 0:
519 break;
521 case '\r':
522 continue; // ignore carriage returns
524 version (Posix)
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;
537 version (none)
539 case ' ':
540 case '\t': // tabs in filenames?
541 if (!instring) // if not in string
542 break; // treat as end of path
544 default:
545 buf.writeByte(c);
546 continue;
548 break;
550 if (buf.length) // if path is not empty
552 if (sink(buf.extractChars()))
553 break;
555 } while (c);
559 * Add the extension `ext` to `name`, regardless of the content of `name`
561 * Params:
562 * name = Path to append the extension to
563 * ext = Extension to add (should not include '.')
565 * Returns:
566 * A newly allocated string (free with `FileName.free`)
568 extern(D) static char[] addExt(const(char)[] name, const(char)[] ext) pure
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];
580 /***************************
581 * Free returned value with FileName::free()
583 extern (C++) static const(char)* defaultExt(const(char)* name, const(char)* ext)
585 return defaultExt(name.toDString, ext.toDString).ptr;
588 /// Ditto
589 extern (D) static const(char)[] defaultExt(const(char)[] name, const(char)[] ext)
591 auto e = FileName.ext(name);
592 if (e.length) // it already has an extension
593 return name.xarraydup;
594 return addExt(name, ext);
597 unittest
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");
604 /***************************
605 * Free returned value with FileName::free()
607 extern (C++) static const(char)* forceExt(const(char)* name, const(char)* ext)
609 return forceExt(name.toDString, ext.toDString).ptr;
612 /// Ditto
613 extern (D) static const(char)[] forceExt(const(char)[] name, const(char)[] ext)
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
620 unittest
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");
627 /// Returns:
628 /// `true` if `name`'s extension is `ext`
629 extern (C++) static bool equalsExt(const(char)* name, const(char)* ext) pure @nogc
631 return equalsExt(name.toDString, ext.toDString);
634 /// Ditto
635 extern (D) static bool equalsExt(const(char)[] name, const(char)[] ext) pure @nogc
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);
645 unittest
647 assert(!equalsExt("foo.bar"[], "d"));
648 assert(equalsExt("foo.bar"[], "bar"));
649 assert(equalsExt("object.d"[], "d"));
650 assert(!equalsExt("object"[], "d"));
653 /******************************
654 * Return !=0 if extensions match.
656 extern (C++) bool equalsExt(const(char)* ext) const pure @nogc
658 return equalsExt(str, ext.toDString());
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
670 extern (C++) static const(char)* searchPath(Strings* path, const(char)* name, bool cwd)
672 return searchPath(path, name.toDString, cwd).ptr;
675 extern (D) static const(char)[] searchPath(Strings* path, const(char)[] name, bool cwd)
677 if (absolute(name))
679 return exists(name) ? name : null;
681 if (cwd)
683 if (exists(name))
684 return name;
686 if (path)
688 foreach (p; *path)
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)
696 mem.xfree(cast(void*)n.ptr);
700 return null;
703 extern (D) static const(char)[] searchPath(const(char)* path, const(char)[] name, bool cwd)
705 if (absolute(name))
707 return exists(name) ? name : null;
709 if (cwd)
711 if (exists(name))
712 return name;
714 if (path && *path)
716 const(char)[] result;
718 int sink(const(char)* p) nothrow
720 auto n = combine(p.toDString, name);
721 mem.xfree(cast(void*)p);
722 if (exists(n))
724 result = n;
725 return 1; // done with splitPath() call
727 return 0;
730 splitPath(&sink, path);
731 return result;
733 return null;
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
743 extern (D) static size_t findReservedChar(const(char)[] name) pure @nogc @safe
745 version (Windows)
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)
751 char c = name[idx];
752 if (c == '<' || c == '>' || c == ':' || c == '"' || c == '|' || c == '?' || c == '*')
754 return idx;
757 return size_t.max;
759 else
761 return size_t.max;
764 unittest
766 assert(findReservedChar(r"") == size_t.max);
767 assert(findReservedChar(r" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-_=+()") == size_t.max);
769 version (Windows)
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);
779 else
781 assert(findReservedChar(`<>:"|?*`) == size_t.max);
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
792 extern (D) static bool refersToParentDir(const(char)[] name) pure @nogc @safe
794 size_t s = 0;
795 foreach (i; 0 .. name.length)
797 if (isDirSeparator(name[i]))
799 if (name[s..i] == "..")
800 return true;
801 s = i + 1;
804 if (name[s..$] == "..")
805 return true;
807 return false;
809 unittest
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"));
823 version (Windows)
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"));
837 Check if the file the `path` points to exists
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
844 extern (C++) static int exists(const(char)* name)
846 return exists(name.toDString);
849 /// Ditto
850 extern (D) static int exists(const(char)[] name)
852 if (!name.length)
853 return 0;
854 version (Posix)
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;
863 else version (Windows)
865 return name.extendedPathThen!((wname)
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;
876 else
878 assert(0);
883 Ensure that the provided path exists
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.
891 Params:
892 path = a path to a file or a directory
894 Returns:
895 `true` if the directory exists or was successfully created
897 extern (D) static bool ensurePathExists(const(char)[] path)
899 //printf("FileName::ensurePathExists(%s)\n", path ? path : "");
900 if (!path.length)
901 return true;
902 if (exists(path))
903 return true;
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)
910 version (Windows)
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))
917 mem.xfree(cast(void*)p.ptr);
918 return true;
921 const r = ensurePathExists(p);
922 mem.xfree(cast(void*)p);
924 if (!r)
925 return r;
928 version (Windows)
929 const r = _mkdir(path);
930 version (Posix)
932 errno = 0;
933 const r = path.toCStringThen!((pathCS) => mkdir(pathCS.ptr, (7 << 6) | (7 << 3) | 7));
936 if (r == 0)
937 return true;
939 // Don't error out if another instance of dmd just created
940 // this directory
941 version (Windows)
943 import core.sys.windows.winerror : ERROR_ALREADY_EXISTS;
944 if (GetLastError() == ERROR_ALREADY_EXISTS)
945 return true;
947 version (Posix)
949 if (errno == EEXIST)
950 return true;
953 return false;
956 ///ditto
957 extern (C++) static bool ensurePathExists(const(char)* path)
959 return ensurePathExists(path.toDString);
962 /******************************************
963 * Return canonical version of name.
964 * This code is high risk.
966 extern (C++) static const(char)* canonicalName(const(char)* name)
968 return canonicalName(name.toDString).ptr;
971 /// Ditto
972 extern (D) static const(char)[] canonicalName(const(char)[] name)
974 version (Posix)
976 import core.stdc.limits; // PATH_MAX
977 import core.sys.posix.unistd; // _PC_PATH_MAX
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))
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;
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))
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);
1010 else static if (__traits(compiles, canonicalize_file_name))
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);
1019 else static if (__traits(compiles, _PC_PATH_MAX))
1021 // Panic! Query the OS for the buffer limit.
1022 auto path_max = pathconf("/", _PC_PATH_MAX);
1023 if (path_max > 0)
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);
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);
1038 else version (Windows)
1040 // Convert to wstring first since otherwise the Win32 APIs have a character limit
1041 return name.toWStringzThen!((wname)
1043 /* Apparently, there is no good way to do this on Windows.
1044 * GetFullPathName isn't it, but use it anyway.
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);
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);
1057 return toNarrowStringz(buffer[0 .. length]);
1060 else
1062 assert(0);
1066 unittest
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);
1075 /********************************
1076 * Free memory allocated by FileName routines
1078 extern (C++) static void free(const(char)* str) pure
1080 if (str)
1082 assert(str[0] != cast(char)0xAB);
1083 memset(cast(void*)str, 0xAB, strlen(str) + 1); // stomp
1085 mem.xfree(cast(void*)str);
1088 extern (C++) const(char)* toChars() const pure nothrow @nogc @trusted
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;
1095 const(char)[] toString() const pure nothrow @nogc @trusted
1097 return str;
1100 bool opCast(T)() const pure nothrow @nogc @safe
1101 if (is(T == bool))
1103 return str.ptr !is null;
1107 version(Windows)
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`.
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 * `\\?`.
1123 * Params:
1124 * path = The path to create.
1126 * Returns:
1127 * 0 on success, 1 on failure.
1129 * References:
1130 * https://msdn.microsoft.com/en-us/library/windows/desktop/aa363855(v=vs.85).aspx
1132 private int _mkdir(const(char)[] path) nothrow
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;
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.
1148 char[] toNarrowStringz(const(wchar)[] wide, char[] buffer = null) nothrow
1150 if (wide is null)
1151 return null;
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)
1156 buffer[requiredLength] = 0;
1157 return buffer[0 .. requiredLength];
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];
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.
1172 * Params:
1173 * str = The string to convert.
1175 * Returns:
1176 * The result of calling F on the UTF16 version of str.
1178 private auto toWStringzThen(alias F)(const(char)[] str) nothrow
1180 import dmd.common.string : SmallBuffer, toWStringz;
1182 if (!str.length) return F(""w.ptr);
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);
1189 return F(wide);