d: Merge dmd. druntime e770945277, phobos 6d6e0b9b9
[official-gcc.git] / gcc / d / dmd / root / filename.d
blob41c2050057d32932d2a55266e178f3b554845608
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(const ref Strings path, const char* name, bool cwd)
670 return searchPath(path[], name.toDString, cwd).ptr;
673 extern (D) static const(char)[] searchPath(const char*[] 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 foreach (p; path)
686 auto n = combine(p.toDString, name);
687 if (exists(n))
688 return n;
689 //combine might return name
690 if (n.ptr != name.ptr)
692 mem.xfree(cast(void*)n.ptr);
695 return null;
698 extern (D) static const(char)[] searchPath(const char* path, const char[] name, bool cwd)
700 if (absolute(name))
702 return exists(name) ? name : null;
704 if (cwd)
706 if (exists(name))
707 return name;
709 if (path && *path)
711 const(char)[] result;
713 int sink(const(char)* p) nothrow
715 auto n = combine(p.toDString, name);
716 mem.xfree(cast(void*)p);
717 if (exists(n))
719 result = n;
720 return 1; // done with splitPath() call
722 return 0;
725 splitPath(&sink, path);
726 return result;
728 return null;
731 /************************************
732 * Determine if path contains reserved character.
733 * Params:
734 * name = path
735 * Returns:
736 * index of the first reserved character in path if found, size_t.max otherwise
738 extern (D) static size_t findReservedChar(const char[] name) pure @nogc @safe
740 version (Windows)
742 // According to https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
743 // the following characters are not allowed in path: < > : " | ? *
744 foreach (idx; 0 .. name.length)
746 char c = name[idx];
747 if (c == '<' || c == '>' || c == ':' || c == '"' || c == '|' || c == '?' || c == '*')
749 return idx;
752 return size_t.max;
754 else
756 return size_t.max;
759 unittest
761 assert(findReservedChar(r"") == size_t.max);
762 assert(findReservedChar(r" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-_=+()") == size_t.max);
764 version (Windows)
766 assert(findReservedChar(` < `) == 1);
767 assert(findReservedChar(` >`) == 1);
768 assert(findReservedChar(`: `) == 0);
769 assert(findReservedChar(`"`) == 0);
770 assert(findReservedChar(`|`) == 0);
771 assert(findReservedChar(`?`) == 0);
772 assert(findReservedChar(`*`) == 0);
774 else
776 assert(findReservedChar(`<>:"|?*`) == size_t.max);
780 /************************************
781 * Determine if path has a reference to parent directory.
782 * Params:
783 * name = path
784 * Returns:
785 * true if path contains '..' reference to parent directory
787 extern (D) static bool refersToParentDir(const char[] name) pure @nogc @safe
789 size_t s = 0;
790 foreach (i; 0 .. name.length)
792 if (isDirSeparator(name[i]))
794 if (name[s..i] == "..")
795 return true;
796 s = i + 1;
799 if (name[s..$] == "..")
800 return true;
802 return false;
804 unittest
806 assert(!refersToParentDir(r""));
807 assert(!refersToParentDir(r"foo"));
808 assert(!refersToParentDir(r"foo.."));
809 assert(!refersToParentDir(r"foo..boo"));
810 assert(!refersToParentDir(r"foo/..boo"));
811 assert(!refersToParentDir(r"foo../boo"));
812 assert(refersToParentDir(r".."));
813 assert(refersToParentDir(r"../"));
814 assert(refersToParentDir(r"foo/.."));
815 assert(refersToParentDir(r"foo/../"));
816 assert(refersToParentDir(r"foo/../../boo"));
818 version (Windows)
820 // Backslash as directory separator
821 assert(!refersToParentDir(r"foo\..boo"));
822 assert(!refersToParentDir(r"foo..\boo"));
823 assert(refersToParentDir(r"..\"));
824 assert(refersToParentDir(r"foo\.."));
825 assert(refersToParentDir(r"foo\..\"));
826 assert(refersToParentDir(r"foo\..\..\boo"));
832 Check if the file the `path` points to exists
834 Returns:
835 0 if it does not exists
836 1 if it exists and is not a directory
837 2 if it exists and is a directory
839 extern (C++) static int exists(const(char)* name)
841 return exists(name.toDString);
844 /// Ditto
845 extern (D) static int exists(const char[] name)
847 if (!name.length)
848 return 0;
849 version (Posix)
851 stat_t st;
852 if (name.toCStringThen!((v) => stat(v.ptr, &st)) < 0)
853 return 0;
854 if (S_ISDIR(st.st_mode))
855 return 2;
856 return 1;
858 else version (Windows)
860 return name.extendedPathThen!((wname)
862 const dw = GetFileAttributesW(&wname[0]);
863 if (dw == -1)
864 return 0;
865 else if (dw & FILE_ATTRIBUTE_DIRECTORY)
866 return 2;
867 else
868 return 1;
871 else
873 assert(0);
878 Ensure that the provided path exists
880 Accepts a path to either a file or a directory.
881 In the former case, the basepath (path to the containing directory)
882 will be checked for existence, and created if it does not exists.
883 In the later case, the directory pointed to will be checked for existence
884 and created if needed.
886 Params:
887 path = a path to a file or a directory
889 Returns:
890 `true` if the directory exists or was successfully created
892 extern (D) static bool ensurePathExists(const char[] path)
894 //printf("FileName::ensurePathExists(%s)\n", path ? path : "");
895 if (!path.length)
896 return true;
897 if (exists(path))
898 return true;
900 // We were provided with a file name
901 // We need to call ourselves recursively to ensure parent dir exist
902 const char[] p = FileName.path(path);
903 if (p.length)
905 version (Windows)
907 // Note: Windows filename comparison should be case-insensitive,
908 // however p is a subslice of path so we don't need it
909 if (path.length == p.length ||
910 (path.length > 2 && path[1] == ':' && path[2 .. $] == p))
912 mem.xfree(cast(void*)p.ptr);
913 return true;
916 const r = ensurePathExists(p);
917 mem.xfree(cast(void*)p);
919 if (!r)
920 return r;
923 version (Windows)
924 const r = _mkdir(path);
925 version (Posix)
927 errno = 0;
928 const r = path.toCStringThen!((pathCS) => mkdir(pathCS.ptr, (7 << 6) | (7 << 3) | 7));
931 if (r == 0)
932 return true;
934 // Don't error out if another instance of dmd just created
935 // this directory
936 version (Windows)
938 import core.sys.windows.winerror : ERROR_ALREADY_EXISTS;
939 if (GetLastError() == ERROR_ALREADY_EXISTS)
940 return true;
942 version (Posix)
944 if (errno == EEXIST)
945 return true;
948 return false;
951 ///ditto
952 extern (C++) static bool ensurePathExists(const(char)* path)
954 return ensurePathExists(path.toDString);
957 /******************************************
958 * Return canonical version of name.
959 * This code is high risk.
961 extern (C++) static const(char)* canonicalName(const(char)* name)
963 return canonicalName(name.toDString).ptr;
966 /// Ditto
967 extern (D) static const(char)[] canonicalName(const char[] name)
969 version (Posix)
971 import core.stdc.limits; // PATH_MAX
972 import core.sys.posix.unistd; // _PC_PATH_MAX
974 // Older versions of druntime don't have PATH_MAX defined.
975 // i.e: dmd __VERSION__ < 2085, gdc __VERSION__ < 2076.
976 static if (!__traits(compiles, PATH_MAX))
978 version (DragonFlyBSD)
979 enum PATH_MAX = 1024;
980 else version (FreeBSD)
981 enum PATH_MAX = 1024;
982 else version (linux)
983 enum PATH_MAX = 4096;
984 else version (NetBSD)
985 enum PATH_MAX = 1024;
986 else version (OpenBSD)
987 enum PATH_MAX = 1024;
988 else version (OSX)
989 enum PATH_MAX = 1024;
990 else version (Solaris)
991 enum PATH_MAX = 1024;
994 // Have realpath(), passing a NULL destination pointer may return an
995 // internally malloc'd buffer, however it is implementation defined
996 // as to what happens, so cannot rely on it.
997 static if (__traits(compiles, PATH_MAX))
999 // Have compile time limit on filesystem path, use it with realpath.
1000 char[PATH_MAX] buf = void;
1001 auto path = name.toCStringThen!((n) => realpath(n.ptr, buf.ptr));
1002 if (path !is null)
1003 return xarraydup(path.toDString);
1005 else static if (__traits(compiles, canonicalize_file_name))
1007 // Have canonicalize_file_name, which malloc's memory.
1008 // We need a dmd.root.rmem allocation though.
1009 auto path = name.toCStringThen!((n) => canonicalize_file_name(n.ptr));
1010 scope(exit) .free(path);
1011 if (path !is null)
1012 return xarraydup(path.toDString);
1014 else static if (__traits(compiles, _PC_PATH_MAX))
1016 // Panic! Query the OS for the buffer limit.
1017 auto path_max = pathconf("/", _PC_PATH_MAX);
1018 if (path_max > 0)
1020 char *buf = cast(char*)mem.xmalloc(path_max);
1021 scope(exit) mem.xfree(buf);
1022 auto path = name.toCStringThen!((n) => realpath(n.ptr, buf));
1023 if (path !is null)
1024 return xarraydup(path.toDString);
1027 // Give up trying to support this platform, just duplicate the filename
1028 // unless there is nothing to copy from.
1029 if (!name.length)
1030 return null;
1031 return xarraydup(name);
1033 else version (Windows)
1035 // Convert to wstring first since otherwise the Win32 APIs have a character limit
1036 return name.toWStringzThen!((wname)
1038 /* Apparently, there is no good way to do this on Windows.
1039 * GetFullPathName isn't it, but use it anyway.
1041 // First find out how long the buffer has to be, incl. terminating null.
1042 const capacity = GetFullPathNameW(&wname[0], 0, null, null);
1043 if (!capacity) return null;
1044 auto buffer = cast(wchar*) mem.xmalloc_noscan(capacity * wchar.sizeof);
1045 scope(exit) mem.xfree(buffer);
1047 // Actually get the full path name. If the buffer is large enough,
1048 // the returned length does NOT include the terminating null...
1049 const length = GetFullPathNameW(&wname[0], capacity, buffer, null /*filePart*/);
1050 assert(length == capacity - 1);
1052 return toNarrowStringz(buffer[0 .. length]);
1055 else
1057 assert(0);
1061 unittest
1063 string filename = "foo.bar";
1064 const path = canonicalName(filename);
1065 scope(exit) free(path.ptr);
1066 assert(path.length >= filename.length);
1067 assert(path[$ - filename.length .. $] == filename);
1070 /********************************
1071 * Free memory allocated by FileName routines
1073 extern (C++) static void free(const(char)* str) pure
1075 if (str)
1077 assert(str[0] != cast(char)0xAB);
1078 memset(cast(void*)str, 0xAB, strlen(str) + 1); // stomp
1080 mem.xfree(cast(void*)str);
1083 extern (C++) const(char)* toChars() const pure nothrow @nogc @trusted
1085 // Since we can return an empty slice (but '\0' terminated),
1086 // we don't do bounds check (as `&str[0]` does)
1087 return str.ptr;
1090 const(char)[] toString() const pure nothrow @nogc @trusted
1092 return str;
1095 bool opCast(T)() const pure nothrow @nogc @safe
1096 if (is(T == bool))
1098 return str.ptr !is null;
1102 version(Windows)
1104 /****************************************************************
1105 * The code before used the POSIX function `mkdir` on Windows. That
1106 * function is now deprecated and fails with long paths, so instead
1107 * we use the newer `CreateDirectoryW`.
1109 * `CreateDirectoryW` is the unicode version of the generic macro
1110 * `CreateDirectory`. `CreateDirectoryA` has a file path
1111 * limitation of 248 characters, `mkdir` fails with less and might
1112 * fail due to the number of consecutive `..`s in the
1113 * path. `CreateDirectoryW` also normally has a 248 character
1114 * limit, unless the path is absolute and starts with `\\?\`. Note
1115 * that this is different from starting with the almost identical
1116 * `\\?`.
1118 * Params:
1119 * path = The path to create.
1121 * Returns:
1122 * 0 on success, 1 on failure.
1124 * References:
1125 * https://msdn.microsoft.com/en-us/library/windows/desktop/aa363855(v=vs.85).aspx
1127 private int _mkdir(const char[] path) nothrow
1129 const createRet = path.extendedPathThen!(
1130 p => CreateDirectoryW(&p[0], null /*securityAttributes*/));
1131 // different conventions for CreateDirectory and mkdir
1132 return createRet == 0 ? 1 : 0;
1135 /**********************************
1136 * Converts a UTF-16 string to a (null-terminated) narrow string.
1137 * Returns:
1138 * If `buffer` is specified and the result fits, a slice of that buffer,
1139 * otherwise a new buffer which can be released via `mem.xfree()`.
1140 * Nulls are propagated, i.e., if `wide` is null, the returned slice is
1141 * null too.
1143 char[] toNarrowStringz(const(wchar)[] wide, char[] buffer = null) nothrow
1145 import dmd.common.file : CodePage;
1147 if (wide is null)
1148 return null;
1150 const requiredLength = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, buffer.ptr, cast(int) buffer.length, null, null);
1151 if (requiredLength < buffer.length)
1153 buffer[requiredLength] = 0;
1154 return buffer[0 .. requiredLength];
1157 char* newBuffer = cast(char*) mem.xmalloc_noscan(requiredLength + 1);
1158 const length = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, newBuffer, requiredLength, null, null);
1159 assert(length == requiredLength);
1160 newBuffer[length] = 0;
1161 return newBuffer[0 .. length];
1164 /**********************************
1165 * Converts a slice of UTF-8 characters to an array of wchar that's null
1166 * terminated so it can be passed to Win32 APIs then calls the supplied
1167 * function on it.
1169 * Params:
1170 * str = The string to convert.
1172 * Returns:
1173 * The result of calling F on the UTF16 version of str.
1175 private auto toWStringzThen(alias F)(const char[] str) nothrow
1177 import dmd.common.smallbuffer : SmallBuffer, toWStringz;
1179 if (!str.length) return F(""w.ptr);
1181 wchar[1024] support = void;
1182 auto buf = SmallBuffer!wchar(support.length, support);
1183 wchar[] wide = toWStringz(str, buf);
1184 scope(exit) wide.ptr != buf.ptr && mem.xfree(wide.ptr);
1186 return F(wide);