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
;
19 import dmd
.common
.outbuffer
;
20 import dmd
.common
.file
;
23 import dmd
.root
.rootobject
;
24 import dmd
.root
.string
;
28 import core
.sys
.posix
.stdlib
;
29 import core
.sys
.posix
.sys
.stat
;
30 import core
.sys
.posix
.unistd
: getcwd
;
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
62 return c
== '\\' || c
== '/';
74 /***********************************************************
75 * Encapsulate path and file names.
80 private const(char)[] str;
83 extern (D
) this(const(char)[] str) pure
85 this.str = str.xarraydup
;
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
);
101 extern (D
) static bool equals(const(char)[] name1
, const(char)[] name2
) pure @nogc
103 if (name1
.length
!= name2
.length
)
108 return name1
.ptr
== name2
.ptr ||
109 Port
.memicmp(name1
.ptr
, name2
.ptr
, name1
.length
) == 0;
113 return name1
== name2
;
117 /************************************
118 * Determine if path is absolute.
122 * true if absolute path name.
124 extern (C
++) static bool absolute(const(char)* name
) pure @nogc
126 return absolute(name
.toDString
);
130 extern (D
) static bool absolute(const(char)[] name
) pure @nogc
137 return isDirSeparator(name
[0])
138 ||
(name
.length
>= 2 && name
[1] == ':');
142 return isDirSeparator(name
[0]);
152 assert(absolute("/"[]) == true);
153 assert(absolute(""[]) == false);
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
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.
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
;
194 extern (D
) static const(char)[] ext(const(char)[] str) nothrow pure @safe @nogc
196 foreach_reverse (idx
, char e
; str)
201 return str[idx
+ 1 .. $];
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
235 /********************************
236 * Return file name without extension.
239 * Once slice are used everywhere and `\0` is not assumed,
240 * this can be turned into a simple slicing.
246 * mem.xmalloc'd filename with extension removed.
248 extern (C
++) static const(char)* removeExt(const(char)* str)
250 return removeExt(str.toDString
).ptr
;
254 extern (D
) static const(char)[] removeExt(const(char)[] str)
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
);
265 return mem
.xstrdup(str.ptr
)[0 .. str.length
];
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
;
283 extern (D
) static const(char)[] name(const(char)[] str) pure @nogc
285 foreach_reverse (idx
, char e
; str)
292 return str[idx
+ 1 .. $];
298 return str[idx
+ 1 .. $];
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 .. $];
316 extern (C
++) const(char)* name() const pure @nogc
318 return name(str).ptr
;
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
;
338 extern (D
) static const(char)[] path(const(char)[] 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
);
351 return path
[0 .. pathlen
];
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
)
367 auto n
= FileName
.name(path
);
370 return combine(path
[0 .. $ - n
.length
], name
);
374 Combine a `path` and a file `name`
377 path = Path to append to
378 name = Name to append to path
381 The `\0` terminated string which is the combination of `path` and `name`
384 extern (C
++) static const(char)* combine(const(char)* path
, const(char)* name
)
388 return combine(path
.toDString
, name
.toDString
).ptr
;
392 extern(D
) static const(char)[] combine(const(char)[] path
, const(char)[] name
)
394 return !path
.length ? name
: buildPath(path
, name
);
400 assert(combine("foo"[], "bar"[]) == "foo\\bar");
402 assert(combine("foo"[], "bar"[]) == "foo/bar");
403 assert(combine("foo/"[], "bar"[]) == "foo/bar");
406 static const(char)[] buildPath(const(char)[][] fragments
...)
409 foreach (f
; fragments
)
410 size
+= f
.length ? f
.length
+ 1 : 0;
414 char* p
= cast(char*) mem
.xmalloc_noscan(size
);
416 foreach (f
; fragments
)
421 p
[length
.. length
+ f
.length
] = f
;
424 const last
= p
[length
- 1];
427 if (!isDirSeparator(last
))
430 else version (Windows
)
432 if (!isDirSeparator(last
) && last
!= ':')
439 // overwrite last slash with null terminator
440 p
[length ?
--length
: 0] = 0;
442 return p
[0 .. length
];
447 assert(buildPath() == "");
448 assert(buildPath("foo") == "foo");
449 assert(buildPath("foo", null) == "foo");
450 assert(buildPath(null, "foo") == "foo");
452 assert(buildPath("C:", r
"a\", "bb/", "ccc", "d") == r
"C:a\bb/ccc\d");
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
466 splitPath(&sink
, path
);
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()
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
)
489 bool instring
= false;
490 while (isspace(*p
)) // skip leading whitespace
492 buf
.reserve(8); // guess size of piece
499 instring ^
= false; // toggle inside/outside of string
514 p
++; // ; cannot appear as part of a
515 break; // path, quotes won't protect it
517 case 0x1A: // ^Z means end of file
522 continue; // ignore carriage returns
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
);
540 case '\t': // tabs in filenames?
541 if (!instring
) // if not in string
542 break; // treat as end of path
550 if (buf
.length
) // if path is not empty
552 if (sink(buf
.extractChars()))
559 * Add the extension `ext` to `name`, regardless of the content of `name`
562 * name = Path to append the extension to
563 * ext = Extension to add (should not include '.')
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
[];
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
;
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
);
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
;
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
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");
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
);
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
)
640 if (!e
.length ||
!ext
.length
)
642 return FileName
.equals(e
, ext
);
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.
664 * path = array of path strings
665 * name = file to look for
666 * cwd = true means search current directory before searching path
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)
679 return exists(name
) ? name
: null;
690 auto n
= combine(p
.toDString
, name
);
693 //combine might return name
694 if (n
.ptr
!= name
.ptr
)
696 mem
.xfree(cast(void*)n
.ptr
);
703 extern (D
) static const(char)[] searchPath(const(char)* path
, const(char)[] name
, bool cwd)
707 return exists(name
) ? name
: null;
716 const(char)[] result
;
718 int sink(const(char)* p
) nothrow
720 auto n
= combine(p
.toDString
, name
);
721 mem
.xfree(cast(void*)p
);
725 return 1; // done with splitPath() call
730 splitPath(&sink
, path
);
736 /************************************
737 * Determine if path contains reserved character.
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
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
)
752 if (c
== '<' || c
== '>' || c
== ':' || c
== '"' || c
== '|' || c
== '?' || c
== '*')
766 assert(findReservedChar(r
"") == size_t
.max
);
767 assert(findReservedChar(r
" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-_=+()") == size_t
.max
);
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);
781 assert(findReservedChar(`<>:"|?*`) == size_t
.max
);
785 /************************************
786 * Determine if path has a reference to parent directory.
790 * true if path contains '..' reference to parent directory
792 extern (D
) static bool refersToParentDir(const(char)[] name
) pure @nogc @safe
795 foreach (i
; 0 .. name
.length
)
797 if (isDirSeparator(name
[i
]))
799 if (name
[s
..i
] == "..")
804 if (name
[s
..$] == "..")
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"));
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
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
);
850 extern (D
) static int exists(const(char)[] name
)
857 if (name
.toCStringThen
!((v
) => stat(v
.ptr
, &st
)) < 0)
859 if (S_ISDIR(st
.st_mode
))
863 else version (Windows
)
865 return name
.extendedPathThen
!((wname
)
867 const dw = GetFileAttributesW(&wname
[0]);
870 else if (dw & FILE_ATTRIBUTE_DIRECTORY
)
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.
892 path = a path to a file or a directory
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 : "");
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
);
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
);
921 const r
= ensurePathExists(p
);
922 mem
.xfree(cast(void*)p
);
929 const r
= _mkdir(path
);
933 const r
= path
.toCStringThen
!((pathCS
) => mkdir(pathCS
.ptr
, (7 << 6) |
(7 << 3) |
7));
939 // Don't error out if another instance of dmd just created
943 import core
.sys
.windows
.winerror
: ERROR_ALREADY_EXISTS
;
944 if (GetLastError() == ERROR_ALREADY_EXISTS
)
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
;
972 extern (D
) static const(char)[] canonicalName(const(char)[] name
)
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;
988 enum PATH_MAX
= 4096;
989 else version (NetBSD
)
990 enum PATH_MAX
= 1024;
991 else version (OpenBSD
)
992 enum PATH_MAX
= 1024;
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
));
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
);
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
);
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
));
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.
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
]);
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
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)
1095 const(char)[] toString() const pure nothrow @nogc @trusted
1100 bool opCast(T
)() const pure nothrow @nogc @safe
1103 return str.ptr
!is null;
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
1124 * path = The path to create.
1127 * 0 on success, 1 on failure.
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.
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
1148 char[] toNarrowStringz(const(wchar)[] wide
, char[] buffer
= null) nothrow
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
1173 * str = The string to convert.
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
);