d: Merge dmd, druntime d8e3976a58, phobos 7a6e95688
[official-gcc.git] / gcc / d / dmd / root / filename.d
blob5b0bba42fc21fb7297d2df6983d19c1fffc0dd4e
1 /**
2 * Encapsulate path and file names.
4 * Copyright: Copyright (C) 1999-2024 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;
18 import dmd.common.file;
19 import dmd.common.outbuffer;
21 import dmd.root.array;
22 import dmd.root.file;
23 import dmd.root.port;
24 import dmd.root.rmem;
25 import dmd.root.string;
27 version (Posix)
29 import core.sys.posix.stdlib;
30 import core.sys.posix.sys.stat;
31 import core.sys.posix.unistd : getcwd;
34 version (Windows)
36 import core.sys.windows.winbase;
37 import core.sys.windows.windef;
38 import core.sys.windows.winnls;
40 import dmd.common.smallbuffer : extendedPathThen;
42 extern (Windows) DWORD GetFullPathNameW(LPCWSTR, DWORD, LPWSTR, LPWSTR*) nothrow @nogc;
43 extern (Windows) void SetLastError(DWORD) nothrow @nogc;
44 extern (C) char* getcwd(char* buffer, size_t maxlen) nothrow;
47 version (CRuntime_Glibc)
49 extern (C) char* canonicalize_file_name(const char*) nothrow;
52 alias Strings = Array!(const(char)*);
55 // Check whether character is a directory separator
56 bool isDirSeparator(char c) pure nothrow @nogc @safe
58 version (Windows)
60 return c == '\\' || c == '/';
62 else version (Posix)
64 return c == '/';
66 else
68 assert(0);
72 /***********************************************************
73 * Encapsulate path and file names.
75 struct FileName
77 nothrow:
78 private const(char)[] str;
80 ///
81 extern (D) this(const(char)[] str) pure
83 this.str = str.xarraydup;
86 ///
87 extern (C++) static FileName create(const(char)* name) pure
89 return FileName(name.toDString);
92 /// Compare two name according to the platform's rules (case sensitive or not)
93 extern (C++) static bool equals(const(char)* name1, const(char)* name2) pure @nogc
95 return equals(name1.toDString, name2.toDString);
98 /// Ditto
99 extern (D) static bool equals(const(char)[] name1, const(char)[] name2) pure @nogc
101 if (name1.length != name2.length)
102 return false;
104 version (Windows)
106 return name1.ptr == name2.ptr ||
107 Port.memicmp(name1.ptr, name2.ptr, name1.length) == 0;
109 else
111 return name1 == name2;
115 /************************************
116 * Determine if path is absolute.
117 * Params:
118 * name = path
119 * Returns:
120 * true if absolute path name.
122 extern (C++) static bool absolute(const(char)* name) pure @nogc
124 return absolute(name.toDString);
127 /// Ditto
128 extern (D) static bool absolute(const(char)[] name) pure @nogc @safe
130 if (!name.length)
131 return false;
133 version (Windows)
135 return isDirSeparator(name[0])
136 || (name.length >= 2 && name[1] == ':');
138 else version (Posix)
140 return isDirSeparator(name[0]);
142 else
144 assert(0);
148 unittest
150 assert(absolute("/"[]) == true);
151 assert(absolute(""[]) == false);
153 version (Windows)
155 assert(absolute(r"\"[]) == true);
156 assert(absolute(r"\\"[]) == true);
157 assert(absolute(r"c:"[]) == true);
162 Return the given name as an absolute path
164 Params:
165 name = path
166 base = the absolute base to prefix name with if it is relative
168 Returns: name as an absolute path relative to base
170 extern (C++) static const(char)* toAbsolute(const(char)* name, const(char)* base = null)
172 const name_ = name.toDString();
173 const base_ = base ? base.toDString() : getcwd(null, 0).toDString();
174 return absolute(name_) ? name : combine(base_, name_).ptr;
177 /********************************
178 * Determine file name extension as slice of input.
179 * Params:
180 * str = file name
181 * Returns:
182 * filename extension (read-only).
183 * Points past '.' of extension.
184 * If there isn't one, return null.
186 extern (C++) static const(char)* ext(const(char)* str) pure @nogc
188 return ext(str.toDString).ptr;
191 /// Ditto
192 extern (D) static const(char)[] ext(const(char)[] str) nothrow pure @safe @nogc
194 foreach_reverse (idx, char e; str)
196 switch (e)
198 case '.':
199 return str[idx + 1 .. $];
200 version (Posix)
202 case '/':
203 return null;
205 version (Windows)
207 case '\\':
208 case ':':
209 case '/':
210 return null;
212 default:
213 continue;
216 return null;
219 unittest
221 assert(ext("/foo/bar/dmd.conf"[]) == "conf");
222 assert(ext("object.o"[]) == "o");
223 assert(ext("/foo/bar/dmd"[]) == null);
224 assert(ext(".objdir.o/object"[]) == null);
225 assert(ext([]) == null);
228 extern (C++) const(char)* ext() const pure @nogc
230 return ext(str).ptr;
233 /********************************
234 * Return file name without extension.
236 * TODO:
237 * Once slice are used everywhere and `\0` is not assumed,
238 * this can be turned into a simple slicing.
240 * Params:
241 * str = file name
243 * Returns:
244 * mem.xmalloc'd filename with extension removed.
246 extern (C++) static const(char)* removeExt(const(char)* str)
248 return removeExt(str.toDString).ptr;
251 /// Ditto
252 extern (D) static const(char)[] removeExt(const(char)[] str)
254 auto e = ext(str);
255 if (e.length)
257 const len = (str.length - e.length) - 1; // -1 for the dot
258 char* n = cast(char*)mem.xmalloc(len + 1);
259 memcpy(n, str.ptr, len);
260 n[len] = 0;
261 return n[0 .. len];
263 return mem.xstrdup(str.ptr)[0 .. str.length];
266 unittest
268 assert(removeExt("/foo/bar/object.d"[]) == "/foo/bar/object");
269 assert(removeExt("/foo/bar/frontend.di"[]) == "/foo/bar/frontend");
272 /********************************
273 * Return filename name excluding path (read-only).
275 extern (C++) static const(char)* name(const(char)* str) pure @nogc
277 return name(str.toDString).ptr;
280 /// Ditto
281 extern (D) static const(char)[] name(const(char)[] str) pure @nogc @safe
283 foreach_reverse (idx, char e; str)
285 switch (e)
287 version (Posix)
289 case '/':
290 return str[idx + 1 .. $];
292 version (Windows)
294 case '/':
295 case '\\':
296 return str[idx + 1 .. $];
297 case ':':
298 /* The ':' is a drive letter only if it is the second
299 * character or the last character,
300 * otherwise it is an ADS (Alternate Data Stream) separator.
301 * Consider ADS separators as part of the file name.
303 if (idx == 1 || idx == str.length - 1)
304 return str[idx + 1 .. $];
305 break;
307 default:
308 break;
311 return str;
314 extern (C++) const(char)* name() const pure @nogc
316 return name(str).ptr;
319 unittest
321 assert(name("/foo/bar/object.d"[]) == "object.d");
322 assert(name("/foo/bar/frontend.di"[]) == "frontend.di");
325 /**************************************
326 * Return path portion of str.
327 * returned string is newly allocated
328 * Path does not include trailing path separator.
330 extern (C++) static const(char)* path(const(char)* str)
332 return path(str.toDString).ptr;
335 /// Ditto
336 extern (D) static const(char)[] path(const(char)[] str)
338 const n = name(str);
339 bool hasTrailingSlash;
340 if (n.length < str.length)
342 if (isDirSeparator(str[$ - n.length - 1]))
343 hasTrailingSlash = true;
345 const pathlen = str.length - n.length - (hasTrailingSlash ? 1 : 0);
346 char* path = cast(char*)mem.xmalloc(pathlen + 1);
347 memcpy(path, str.ptr, pathlen);
348 path[pathlen] = 0;
349 return path[0 .. pathlen];
352 unittest
354 assert(path("/foo/bar"[]) == "/foo");
355 assert(path("foo"[]) == "");
358 /**************************************
359 * Replace filename portion of path.
361 extern (D) static const(char)[] replaceName(const(char)[] path, const(char)[] name)
363 if (absolute(name))
364 return name;
365 auto n = FileName.name(path);
366 if (n == path)
367 return name;
368 return combine(path[0 .. $ - n.length], name);
372 Combine a `path` and a file `name`
374 Params:
375 path = Path to append to
376 name = Name to append to path
378 Returns:
379 The `\0` terminated string which is the combination of `path` and `name`
380 and a valid path.
382 extern (C++) static const(char)* combine(const(char)* path, const(char)* name)
384 if (!path)
385 return name;
386 return combine(path.toDString, name.toDString).ptr;
389 /// Ditto
390 extern(D) static const(char)[] combine(const(char)[] path, const(char)[] name)
392 return !path.length ? name : buildPath(path, name);
395 unittest
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");
404 static const(char)[] buildPath(const(char)[][] fragments...)
406 size_t size;
407 foreach (f; fragments)
408 size += f.length ? f.length + 1 : 0;
409 if (size == 0)
410 size = 1;
412 char* p = cast(char*) mem.xmalloc_noscan(size);
413 size_t length;
414 foreach (f; fragments)
416 if (!f.length)
417 continue;
419 p[length .. length + f.length] = f;
420 length += f.length;
422 const last = p[length - 1];
423 version (Posix)
425 if (!isDirSeparator(last))
426 p[length++] = '/';
428 else version (Windows)
430 if (!isDirSeparator(last) && last != ':')
431 p[length++] = '\\';
433 else
434 assert(0);
437 // overwrite last slash with null terminator
438 p[length ? --length : 0] = 0;
440 return p[0 .. length];
443 unittest
445 assert(buildPath() == "");
446 assert(buildPath("foo") == "foo");
447 assert(buildPath("foo", null) == "foo");
448 assert(buildPath(null, "foo") == "foo");
449 version (Windows)
450 assert(buildPath("C:", r"a\", "bb/", "ccc", "d") == r"C:a\bb/ccc\d");
451 else
452 assert(buildPath("a/", "bb", "ccc") == "a/bb/ccc");
455 // Split a path into an Array of paths
456 extern (C++) static Strings* splitPath(const(char)* path)
458 auto array = new Strings();
459 int sink(const(char)* p) nothrow
461 array.push(p);
462 return 0;
464 splitPath(&sink, path);
465 return array;
468 /****
469 * Split path (such as that returned by `getenv("PATH")`) into pieces, each piece is mem.xmalloc'd
470 * Handle double quotes and ~.
471 * Pass the pieces to sink()
472 * Params:
473 * sink = send the path pieces here, end when sink() returns !=0
474 * path = the path to split up.
476 static void splitPath(int delegate(const(char)*) nothrow sink, const(char)* path)
478 if (!path)
479 return;
481 auto p = path;
482 OutBuffer buf;
483 char c;
486 const(char)* home;
487 bool instring = false;
488 while (isspace(*p)) // skip leading whitespace
489 ++p;
490 buf.reserve(8); // guess size of piece
491 for (;; ++p)
493 c = *p;
494 switch (c)
496 case '"':
497 instring ^= false; // toggle inside/outside of string
498 continue;
500 version (OSX)
502 case ',':
504 version (Windows)
506 case ';':
508 version (Posix)
510 case ':':
512 p++; // ; cannot appear as part of a
513 break; // path, quotes won't protect it
515 case 0x1A: // ^Z means end of file
516 case 0:
517 break;
519 case '\r':
520 continue; // ignore carriage returns
522 version (Posix)
524 case '~':
525 if (!home)
526 home = getenv("HOME");
527 // Expand ~ only if it is prefixing the rest of the path.
528 if (!buf.length && p[1] == '/' && home)
529 buf.writestring(home);
530 else
531 buf.writeByte('~');
532 continue;
535 version (none)
537 case ' ':
538 case '\t': // tabs in filenames?
539 if (!instring) // if not in string
540 break; // treat as end of path
542 default:
543 buf.writeByte(c);
544 continue;
546 break;
548 if (buf.length) // if path is not empty
550 if (sink(buf.extractChars()))
551 break;
553 } while (c);
557 * Add the extension `ext` to `name`, regardless of the content of `name`
559 * Params:
560 * name = Path to append the extension to
561 * ext = Extension to add (should not include '.')
563 * Returns:
564 * A newly allocated string (free with `FileName.free`)
566 extern(D) static char[] addExt(const(char)[] name, const(char)[] ext) pure
568 const len = name.length + ext.length + 2;
569 auto s = cast(char*)mem.xmalloc(len);
570 s[0 .. name.length] = name[];
571 s[name.length] = '.';
572 s[name.length + 1 .. len - 1] = ext[];
573 s[len - 1] = '\0';
574 return s[0 .. len - 1];
578 /***************************
579 * Free returned value with FileName::free()
581 extern (C++) static const(char)* defaultExt(const(char)* name, const(char)* ext)
583 return defaultExt(name.toDString, ext.toDString).ptr;
586 /// Ditto
587 extern (D) static const(char)[] defaultExt(const(char)[] name, const(char)[] ext)
589 auto e = FileName.ext(name);
590 if (e.length) // it already has an extension
591 return name.xarraydup;
592 return addExt(name, ext);
595 unittest
597 assert(defaultExt("/foo/object.d"[], "d") == "/foo/object.d");
598 assert(defaultExt("/foo/object"[], "d") == "/foo/object.d");
599 assert(defaultExt("/foo/bar.d"[], "o") == "/foo/bar.d");
602 /***************************
603 * Free returned value with FileName::free()
605 extern (C++) static const(char)* forceExt(const(char)* name, const(char)* ext)
607 return forceExt(name.toDString, ext.toDString).ptr;
610 /// Ditto
611 extern (D) static const(char)[] forceExt(const(char)[] name, const(char)[] ext)
613 if (auto e = FileName.ext(name))
614 return addExt(name[0 .. $ - e.length - 1], ext);
615 return defaultExt(name, ext); // doesn't have one
618 unittest
620 assert(forceExt("/foo/object.d"[], "d") == "/foo/object.d");
621 assert(forceExt("/foo/object"[], "d") == "/foo/object.d");
622 assert(forceExt("/foo/bar.d"[], "o") == "/foo/bar.o");
625 /// Returns:
626 /// `true` if `name`'s extension is `ext`
627 extern (C++) static bool equalsExt(const(char)* name, const(char)* ext) pure @nogc
629 return equalsExt(name.toDString, ext.toDString);
632 /// Ditto
633 extern (D) static bool equalsExt(const(char)[] name, const(char)[] ext) pure @nogc
635 auto e = FileName.ext(name);
636 if (!e.length && !ext.length)
637 return true;
638 if (!e.length || !ext.length)
639 return false;
640 return FileName.equals(e, ext);
643 unittest
645 assert(!equalsExt("foo.bar"[], "d"));
646 assert(equalsExt("foo.bar"[], "bar"));
647 assert(equalsExt("object.d"[], "d"));
648 assert(!equalsExt("object"[], "d"));
651 /******************************
652 * Return !=0 if extensions match.
654 extern (C++) bool equalsExt(const(char)* ext) const pure @nogc
656 return equalsExt(str, ext.toDString());
659 /*************************************
660 * Search paths for file.
661 * Params:
662 * path = array of path strings
663 * name = file to look for
664 * cwd = true means search current directory before searching path
665 * Returns:
666 * if found, filename combined with path, otherwise null
668 extern (C++) static const(char)* searchPath(Strings* path, const(char)* name, bool cwd)
670 return searchPath(path, name.toDString, cwd).ptr;
673 extern (D) static const(char)[] searchPath(Strings* path, const(char)[] name, bool cwd)
675 if (absolute(name))
677 return exists(name) ? name : null;
679 if (cwd)
681 if (exists(name))
682 return name;
684 if (path)
686 foreach (p; *path)
688 auto n = combine(p.toDString, name);
689 if (exists(n))
690 return n;
691 //combine might return name
692 if (n.ptr != name.ptr)
694 mem.xfree(cast(void*)n.ptr);
698 return null;
701 extern (D) static const(char)[] searchPath(const(char)* path, const(char)[] name, bool cwd)
703 if (absolute(name))
705 return exists(name) ? name : null;
707 if (cwd)
709 if (exists(name))
710 return name;
712 if (path && *path)
714 const(char)[] result;
716 int sink(const(char)* p) nothrow
718 auto n = combine(p.toDString, name);
719 mem.xfree(cast(void*)p);
720 if (exists(n))
722 result = n;
723 return 1; // done with splitPath() call
725 return 0;
728 splitPath(&sink, path);
729 return result;
731 return null;
734 /************************************
735 * Determine if path contains reserved character.
736 * Params:
737 * name = path
738 * Returns:
739 * index of the first reserved character in path if found, size_t.max otherwise
741 extern (D) static size_t findReservedChar(const(char)[] name) pure @nogc @safe
743 version (Windows)
745 // According to https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
746 // the following characters are not allowed in path: < > : " | ? *
747 foreach (idx; 0 .. name.length)
749 char c = name[idx];
750 if (c == '<' || c == '>' || c == ':' || c == '"' || c == '|' || c == '?' || c == '*')
752 return idx;
755 return size_t.max;
757 else
759 return size_t.max;
762 unittest
764 assert(findReservedChar(r"") == size_t.max);
765 assert(findReservedChar(r" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-_=+()") == size_t.max);
767 version (Windows)
769 assert(findReservedChar(` < `) == 1);
770 assert(findReservedChar(` >`) == 1);
771 assert(findReservedChar(`: `) == 0);
772 assert(findReservedChar(`"`) == 0);
773 assert(findReservedChar(`|`) == 0);
774 assert(findReservedChar(`?`) == 0);
775 assert(findReservedChar(`*`) == 0);
777 else
779 assert(findReservedChar(`<>:"|?*`) == size_t.max);
783 /************************************
784 * Determine if path has a reference to parent directory.
785 * Params:
786 * name = path
787 * Returns:
788 * true if path contains '..' reference to parent directory
790 extern (D) static bool refersToParentDir(const(char)[] name) pure @nogc @safe
792 size_t s = 0;
793 foreach (i; 0 .. name.length)
795 if (isDirSeparator(name[i]))
797 if (name[s..i] == "..")
798 return true;
799 s = i + 1;
802 if (name[s..$] == "..")
803 return true;
805 return false;
807 unittest
809 assert(!refersToParentDir(r""));
810 assert(!refersToParentDir(r"foo"));
811 assert(!refersToParentDir(r"foo.."));
812 assert(!refersToParentDir(r"foo..boo"));
813 assert(!refersToParentDir(r"foo/..boo"));
814 assert(!refersToParentDir(r"foo../boo"));
815 assert(refersToParentDir(r".."));
816 assert(refersToParentDir(r"../"));
817 assert(refersToParentDir(r"foo/.."));
818 assert(refersToParentDir(r"foo/../"));
819 assert(refersToParentDir(r"foo/../../boo"));
821 version (Windows)
823 // Backslash as directory separator
824 assert(!refersToParentDir(r"foo\..boo"));
825 assert(!refersToParentDir(r"foo..\boo"));
826 assert(refersToParentDir(r"..\"));
827 assert(refersToParentDir(r"foo\.."));
828 assert(refersToParentDir(r"foo\..\"));
829 assert(refersToParentDir(r"foo\..\..\boo"));
835 Check if the file the `path` points to exists
837 Returns:
838 0 if it does not exists
839 1 if it exists and is not a directory
840 2 if it exists and is a directory
842 extern (C++) static int exists(const(char)* name)
844 return exists(name.toDString);
847 /// Ditto
848 extern (D) static int exists(const(char)[] name)
850 if (!name.length)
851 return 0;
852 version (Posix)
854 stat_t st;
855 if (name.toCStringThen!((v) => stat(v.ptr, &st)) < 0)
856 return 0;
857 if (S_ISDIR(st.st_mode))
858 return 2;
859 return 1;
861 else version (Windows)
863 return name.extendedPathThen!((wname)
865 const dw = GetFileAttributesW(&wname[0]);
866 if (dw == -1)
867 return 0;
868 else if (dw & FILE_ATTRIBUTE_DIRECTORY)
869 return 2;
870 else
871 return 1;
874 else
876 assert(0);
881 Ensure that the provided path exists
883 Accepts a path to either a file or a directory.
884 In the former case, the basepath (path to the containing directory)
885 will be checked for existence, and created if it does not exists.
886 In the later case, the directory pointed to will be checked for existence
887 and created if needed.
889 Params:
890 path = a path to a file or a directory
892 Returns:
893 `true` if the directory exists or was successfully created
895 extern (D) static bool ensurePathExists(const(char)[] path)
897 //printf("FileName::ensurePathExists(%s)\n", path ? path : "");
898 if (!path.length)
899 return true;
900 if (exists(path))
901 return true;
903 // We were provided with a file name
904 // We need to call ourselves recursively to ensure parent dir exist
905 const char[] p = FileName.path(path);
906 if (p.length)
908 version (Windows)
910 // Note: Windows filename comparison should be case-insensitive,
911 // however p is a subslice of path so we don't need it
912 if (path.length == p.length ||
913 (path.length > 2 && path[1] == ':' && path[2 .. $] == p))
915 mem.xfree(cast(void*)p.ptr);
916 return true;
919 const r = ensurePathExists(p);
920 mem.xfree(cast(void*)p);
922 if (!r)
923 return r;
926 version (Windows)
927 const r = _mkdir(path);
928 version (Posix)
930 errno = 0;
931 const r = path.toCStringThen!((pathCS) => mkdir(pathCS.ptr, (7 << 6) | (7 << 3) | 7));
934 if (r == 0)
935 return true;
937 // Don't error out if another instance of dmd just created
938 // this directory
939 version (Windows)
941 import core.sys.windows.winerror : ERROR_ALREADY_EXISTS;
942 if (GetLastError() == ERROR_ALREADY_EXISTS)
943 return true;
945 version (Posix)
947 if (errno == EEXIST)
948 return true;
951 return false;
954 ///ditto
955 extern (C++) static bool ensurePathExists(const(char)* path)
957 return ensurePathExists(path.toDString);
960 /******************************************
961 * Return canonical version of name.
962 * This code is high risk.
964 extern (C++) static const(char)* canonicalName(const(char)* name)
966 return canonicalName(name.toDString).ptr;
969 /// Ditto
970 extern (D) static const(char)[] canonicalName(const(char)[] name)
972 version (Posix)
974 import core.stdc.limits; // PATH_MAX
975 import core.sys.posix.unistd; // _PC_PATH_MAX
977 // Older versions of druntime don't have PATH_MAX defined.
978 // i.e: dmd __VERSION__ < 2085, gdc __VERSION__ < 2076.
979 static if (!__traits(compiles, PATH_MAX))
981 version (DragonFlyBSD)
982 enum PATH_MAX = 1024;
983 else version (FreeBSD)
984 enum PATH_MAX = 1024;
985 else version (linux)
986 enum PATH_MAX = 4096;
987 else version (NetBSD)
988 enum PATH_MAX = 1024;
989 else version (OpenBSD)
990 enum PATH_MAX = 1024;
991 else version (OSX)
992 enum PATH_MAX = 1024;
993 else version (Solaris)
994 enum PATH_MAX = 1024;
997 // Have realpath(), passing a NULL destination pointer may return an
998 // internally malloc'd buffer, however it is implementation defined
999 // as to what happens, so cannot rely on it.
1000 static if (__traits(compiles, PATH_MAX))
1002 // Have compile time limit on filesystem path, use it with realpath.
1003 char[PATH_MAX] buf = void;
1004 auto path = name.toCStringThen!((n) => realpath(n.ptr, buf.ptr));
1005 if (path !is null)
1006 return xarraydup(path.toDString);
1008 else static if (__traits(compiles, canonicalize_file_name))
1010 // Have canonicalize_file_name, which malloc's memory.
1011 // We need a dmd.root.rmem allocation though.
1012 auto path = name.toCStringThen!((n) => canonicalize_file_name(n.ptr));
1013 scope(exit) .free(path);
1014 if (path !is null)
1015 return xarraydup(path.toDString);
1017 else static if (__traits(compiles, _PC_PATH_MAX))
1019 // Panic! Query the OS for the buffer limit.
1020 auto path_max = pathconf("/", _PC_PATH_MAX);
1021 if (path_max > 0)
1023 char *buf = cast(char*)mem.xmalloc(path_max);
1024 scope(exit) mem.xfree(buf);
1025 auto path = name.toCStringThen!((n) => realpath(n.ptr, buf));
1026 if (path !is null)
1027 return xarraydup(path.toDString);
1030 // Give up trying to support this platform, just duplicate the filename
1031 // unless there is nothing to copy from.
1032 if (!name.length)
1033 return null;
1034 return xarraydup(name);
1036 else version (Windows)
1038 // Convert to wstring first since otherwise the Win32 APIs have a character limit
1039 return name.toWStringzThen!((wname)
1041 /* Apparently, there is no good way to do this on Windows.
1042 * GetFullPathName isn't it, but use it anyway.
1044 // First find out how long the buffer has to be, incl. terminating null.
1045 const capacity = GetFullPathNameW(&wname[0], 0, null, null);
1046 if (!capacity) return null;
1047 auto buffer = cast(wchar*) mem.xmalloc_noscan(capacity * wchar.sizeof);
1048 scope(exit) mem.xfree(buffer);
1050 // Actually get the full path name. If the buffer is large enough,
1051 // the returned length does NOT include the terminating null...
1052 const length = GetFullPathNameW(&wname[0], capacity, buffer, null /*filePart*/);
1053 assert(length == capacity - 1);
1055 return toNarrowStringz(buffer[0 .. length]);
1058 else
1060 assert(0);
1064 unittest
1066 string filename = "foo.bar";
1067 const path = canonicalName(filename);
1068 scope(exit) free(path.ptr);
1069 assert(path.length >= filename.length);
1070 assert(path[$ - filename.length .. $] == filename);
1073 /********************************
1074 * Free memory allocated by FileName routines
1076 extern (C++) static void free(const(char)* str) pure
1078 if (str)
1080 assert(str[0] != cast(char)0xAB);
1081 memset(cast(void*)str, 0xAB, strlen(str) + 1); // stomp
1083 mem.xfree(cast(void*)str);
1086 extern (C++) const(char)* toChars() const pure nothrow @nogc @trusted
1088 // Since we can return an empty slice (but '\0' terminated),
1089 // we don't do bounds check (as `&str[0]` does)
1090 return str.ptr;
1093 const(char)[] toString() const pure nothrow @nogc @trusted
1095 return str;
1098 bool opCast(T)() const pure nothrow @nogc @safe
1099 if (is(T == bool))
1101 return str.ptr !is null;
1105 version(Windows)
1107 /****************************************************************
1108 * The code before used the POSIX function `mkdir` on Windows. That
1109 * function is now deprecated and fails with long paths, so instead
1110 * we use the newer `CreateDirectoryW`.
1112 * `CreateDirectoryW` is the unicode version of the generic macro
1113 * `CreateDirectory`. `CreateDirectoryA` has a file path
1114 * limitation of 248 characters, `mkdir` fails with less and might
1115 * fail due to the number of consecutive `..`s in the
1116 * path. `CreateDirectoryW` also normally has a 248 character
1117 * limit, unless the path is absolute and starts with `\\?\`. Note
1118 * that this is different from starting with the almost identical
1119 * `\\?`.
1121 * Params:
1122 * path = The path to create.
1124 * Returns:
1125 * 0 on success, 1 on failure.
1127 * References:
1128 * https://msdn.microsoft.com/en-us/library/windows/desktop/aa363855(v=vs.85).aspx
1130 private int _mkdir(const(char)[] path) nothrow
1132 const createRet = path.extendedPathThen!(
1133 p => CreateDirectoryW(&p[0], null /*securityAttributes*/));
1134 // different conventions for CreateDirectory and mkdir
1135 return createRet == 0 ? 1 : 0;
1138 /**********************************
1139 * Converts a UTF-16 string to a (null-terminated) narrow string.
1140 * Returns:
1141 * If `buffer` is specified and the result fits, a slice of that buffer,
1142 * otherwise a new buffer which can be released via `mem.xfree()`.
1143 * Nulls are propagated, i.e., if `wide` is null, the returned slice is
1144 * null too.
1146 char[] toNarrowStringz(const(wchar)[] wide, char[] buffer = null) nothrow
1148 import dmd.common.file : CodePage;
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.smallbuffer : 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);