aboutsummaryrefslogtreecommitdiff
path: root/libphobos/src/std/file.d
diff options
context:
space:
mode:
Diffstat (limited to 'libphobos/src/std/file.d')
-rw-r--r--libphobos/src/std/file.d4325
1 files changed, 4325 insertions, 0 deletions
diff --git a/libphobos/src/std/file.d b/libphobos/src/std/file.d
new file mode 100644
index 0000000..6b12c04
--- /dev/null
+++ b/libphobos/src/std/file.d
@@ -0,0 +1,4325 @@
+// Written in the D programming language.
+
+/**
+Utilities for manipulating files and scanning directories. Functions
+in this module handle files as a unit, e.g., read or write one _file
+at a time. For opening files and manipulating them via handles refer
+to module $(MREF std, stdio).
+
+$(SCRIPT inhibitQuickIndex = 1;)
+$(BOOKTABLE,
+$(TR $(TH Category) $(TH Functions))
+$(TR $(TD General) $(TD
+ $(LREF exists)
+ $(LREF isDir)
+ $(LREF isFile)
+ $(LREF isSymlink)
+ $(LREF rename)
+ $(LREF thisExePath)
+))
+$(TR $(TD Directories) $(TD
+ $(LREF chdir)
+ $(LREF dirEntries)
+ $(LREF getcwd)
+ $(LREF mkdir)
+ $(LREF mkdirRecurse)
+ $(LREF rmdir)
+ $(LREF rmdirRecurse)
+ $(LREF tempDir)
+))
+$(TR $(TD Files) $(TD
+ $(LREF append)
+ $(LREF copy)
+ $(LREF read)
+ $(LREF readText)
+ $(LREF remove)
+ $(LREF slurp)
+ $(LREF write)
+))
+$(TR $(TD Symlinks) $(TD
+ $(LREF symlink)
+ $(LREF readLink)
+))
+$(TR $(TD Attributes) $(TD
+ $(LREF attrIsDir)
+ $(LREF attrIsFile)
+ $(LREF attrIsSymlink)
+ $(LREF getAttributes)
+ $(LREF getLinkAttributes)
+ $(LREF getSize)
+ $(LREF setAttributes)
+))
+$(TR $(TD Timestamp) $(TD
+ $(LREF getTimes)
+ $(LREF getTimesWin)
+ $(LREF setTimes)
+ $(LREF timeLastModified)
+))
+$(TR $(TD Other) $(TD
+ $(LREF DirEntry)
+ $(LREF FileException)
+ $(LREF PreserveAttributes)
+ $(LREF SpanMode)
+))
+)
+
+
+Copyright: Copyright Digital Mars 2007 - 2011.
+See_Also: The $(HTTP ddili.org/ders/d.en/files.html, official tutorial) for an
+introduction to working with files in D, module
+$(MREF std, stdio) for opening files and manipulating them via handles,
+and module $(MREF std, path) for manipulating path strings.
+
+License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
+Authors: $(HTTP digitalmars.com, Walter Bright),
+ $(HTTP erdani.org, Andrei Alexandrescu),
+ Jonathan M Davis
+Source: $(PHOBOSSRC std/_file.d)
+ */
+module std.file;
+
+import core.stdc.errno, core.stdc.stdlib, core.stdc.string;
+import core.time : abs, dur, hnsecs, seconds;
+
+import std.datetime.date : DateTime;
+import std.datetime.systime : Clock, SysTime, unixTimeToStdTime;
+import std.internal.cstring;
+import std.meta;
+import std.range.primitives;
+import std.traits;
+import std.typecons;
+
+version (Windows)
+{
+ import core.sys.windows.windows, std.windows.syserror;
+}
+else version (Posix)
+{
+ import core.sys.posix.dirent, core.sys.posix.fcntl, core.sys.posix.sys.stat,
+ core.sys.posix.sys.time, core.sys.posix.unistd, core.sys.posix.utime;
+}
+else
+ static assert(false, "Module " ~ .stringof ~ " not implemented for this OS.");
+
+// Character type used for operating system filesystem APIs
+version (Windows)
+{
+ private alias FSChar = wchar;
+}
+else version (Posix)
+{
+ private alias FSChar = char;
+}
+else
+ static assert(0);
+
+// Purposefully not documented. Use at your own risk
+@property string deleteme() @safe
+{
+ import std.conv : to;
+ import std.path : buildPath;
+ import std.process : thisProcessID;
+
+ static _deleteme = "deleteme.dmd.unittest.pid";
+ static _first = true;
+
+ if (_first)
+ {
+ _deleteme = buildPath(tempDir(), _deleteme) ~ to!string(thisProcessID);
+ _first = false;
+ }
+
+ return _deleteme;
+}
+
+version (unittest) private struct TestAliasedString
+{
+ string get() @safe @nogc pure nothrow { return _s; }
+ alias get this;
+ @disable this(this);
+ string _s;
+}
+
+version (Android)
+{
+ package enum system_directory = "/system/etc";
+ package enum system_file = "/system/etc/hosts";
+}
+else version (Posix)
+{
+ package enum system_directory = "/usr/include";
+ package enum system_file = "/usr/include/assert.h";
+}
+
+
+/++
+ Exception thrown for file I/O errors.
+ +/
+class FileException : Exception
+{
+ import std.conv : text, to;
+
+ /++
+ OS error code.
+ +/
+ immutable uint errno;
+
+ /++
+ Constructor which takes an error message.
+
+ Params:
+ name = Name of file for which the error occurred.
+ msg = Message describing the error.
+ file = The _file where the error occurred.
+ line = The _line where the error occurred.
+ +/
+ this(in char[] name, in char[] msg, string file = __FILE__, size_t line = __LINE__) @safe pure
+ {
+ if (msg.empty)
+ super(name.idup, file, line);
+ else
+ super(text(name, ": ", msg), file, line);
+
+ errno = 0;
+ }
+
+ /++
+ Constructor which takes the error number ($(LUCKY GetLastError)
+ in Windows, $(D_PARAM errno) in Posix).
+
+ Params:
+ name = Name of file for which the error occurred.
+ errno = The error number.
+ file = The _file where the error occurred.
+ Defaults to $(D __FILE__).
+ line = The _line where the error occurred.
+ Defaults to $(D __LINE__).
+ +/
+ version (Windows) this(in char[] name,
+ uint errno = .GetLastError(),
+ string file = __FILE__,
+ size_t line = __LINE__) @safe
+ {
+ this(name, sysErrorString(errno), file, line);
+ this.errno = errno;
+ }
+ else version (Posix) this(in char[] name,
+ uint errno = .errno,
+ string file = __FILE__,
+ size_t line = __LINE__) @trusted
+ {
+ import std.exception : errnoString;
+ this(name, errnoString(errno), file, line);
+ this.errno = errno;
+ }
+}
+
+private T cenforce(T)(T condition, lazy const(char)[] name, string file = __FILE__, size_t line = __LINE__)
+{
+ if (condition)
+ return condition;
+ version (Windows)
+ {
+ throw new FileException(name, .GetLastError(), file, line);
+ }
+ else version (Posix)
+ {
+ throw new FileException(name, .errno, file, line);
+ }
+}
+
+version (Windows)
+@trusted
+private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez,
+ string file = __FILE__, size_t line = __LINE__)
+{
+ if (condition)
+ return condition;
+ if (!name)
+ {
+ import core.stdc.wchar_ : wcslen;
+ import std.conv : to;
+
+ auto len = namez ? wcslen(namez) : 0;
+ name = to!string(namez[0 .. len]);
+ }
+ throw new FileException(name, .GetLastError(), file, line);
+}
+
+version (Posix)
+@trusted
+private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez,
+ string file = __FILE__, size_t line = __LINE__)
+{
+ if (condition)
+ return condition;
+ if (!name)
+ {
+ import core.stdc.string : strlen;
+
+ auto len = namez ? strlen(namez) : 0;
+ name = namez[0 .. len].idup;
+ }
+ throw new FileException(name, .errno, file, line);
+}
+
+@safe unittest
+{
+ // issue 17102
+ try
+ {
+ cenforce(false, null, null,
+ __FILE__, __LINE__);
+ }
+ catch (FileException) {}
+}
+
+/* **********************************
+ * Basic File operations.
+ */
+
+/********************************************
+Read entire contents of file $(D name) and returns it as an untyped
+array. If the file size is larger than $(D upTo), only $(D upTo)
+bytes are _read.
+
+Params:
+ name = string or range of characters representing the file _name
+ upTo = if present, the maximum number of bytes to _read
+
+Returns: Untyped array of bytes _read.
+
+Throws: $(LREF FileException) on error.
+ */
+
+void[] read(R)(R name, size_t upTo = size_t.max)
+if (isInputRange!R && isSomeChar!(ElementEncodingType!R) && !isInfinite!R &&
+ !isConvertibleToString!R)
+{
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ return readImpl(name, name.tempCString!FSChar(), upTo);
+ else
+ return readImpl(null, name.tempCString!FSChar(), upTo);
+}
+
+///
+@safe unittest
+{
+ import std.utf : byChar;
+ scope(exit)
+ {
+ assert(exists(deleteme));
+ remove(deleteme);
+ }
+
+ write(deleteme, "1234"); // deleteme is the name of a temporary file
+ assert(read(deleteme, 2) == "12");
+ assert(read(deleteme.byChar) == "1234");
+ assert((cast(const(ubyte)[])read(deleteme)).length == 4);
+}
+
+/// ditto
+void[] read(R)(auto ref R name, size_t upTo = size_t.max)
+if (isConvertibleToString!R)
+{
+ return read!(StringTypeOf!R)(name, upTo);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, read(TestAliasedString(null))));
+}
+
+version (Posix) private void[] readImpl(const(char)[] name, const(FSChar)* namez, size_t upTo = size_t.max) @trusted
+{
+ import core.memory : GC;
+ import std.algorithm.comparison : min;
+ import std.array : uninitializedArray;
+ import std.conv : to;
+
+ // A few internal configuration parameters {
+ enum size_t
+ minInitialAlloc = 1024 * 4,
+ maxInitialAlloc = size_t.max / 2,
+ sizeIncrement = 1024 * 16,
+ maxSlackMemoryAllowed = 1024;
+ // }
+
+ immutable fd = core.sys.posix.fcntl.open(namez,
+ core.sys.posix.fcntl.O_RDONLY);
+ cenforce(fd != -1, name);
+ scope(exit) core.sys.posix.unistd.close(fd);
+
+ stat_t statbuf = void;
+ cenforce(fstat(fd, &statbuf) == 0, name, namez);
+
+ immutable initialAlloc = min(upTo, to!size_t(statbuf.st_size
+ ? min(statbuf.st_size + 1, maxInitialAlloc)
+ : minInitialAlloc));
+ void[] result = uninitializedArray!(ubyte[])(initialAlloc);
+ scope(failure) GC.free(result.ptr);
+ size_t size = 0;
+
+ for (;;)
+ {
+ immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size,
+ min(result.length, upTo) - size);
+ cenforce(actual != -1, name, namez);
+ if (actual == 0) break;
+ size += actual;
+ if (size >= upTo) break;
+ if (size < result.length) continue;
+ immutable newAlloc = size + sizeIncrement;
+ result = GC.realloc(result.ptr, newAlloc, GC.BlkAttr.NO_SCAN)[0 .. newAlloc];
+ }
+
+ return result.length - size >= maxSlackMemoryAllowed
+ ? GC.realloc(result.ptr, size, GC.BlkAttr.NO_SCAN)[0 .. size]
+ : result[0 .. size];
+}
+
+
+version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* namez, size_t upTo = size_t.max) @safe
+{
+ import core.memory : GC;
+ import std.algorithm.comparison : min;
+ import std.array : uninitializedArray;
+ static trustedCreateFileW(const(wchar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode,
+ SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition,
+ DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted
+ {
+ return CreateFileW(namez, dwDesiredAccess, dwShareMode,
+ lpSecurityAttributes, dwCreationDisposition,
+ dwFlagsAndAttributes, hTemplateFile);
+
+ }
+ static trustedCloseHandle(HANDLE hObject) @trusted
+ {
+ return CloseHandle(hObject);
+ }
+ static trustedGetFileSize(HANDLE hFile, out ulong fileSize) @trusted
+ {
+ DWORD sizeHigh;
+ DWORD sizeLow = GetFileSize(hFile, &sizeHigh);
+ const bool result = sizeLow != INVALID_FILE_SIZE;
+ if (result)
+ fileSize = makeUlong(sizeLow, sizeHigh);
+ return result;
+ }
+ static trustedReadFile(HANDLE hFile, void *lpBuffer, ulong nNumberOfBytesToRead) @trusted
+ {
+ // Read by chunks of size < 4GB (Windows API limit)
+ ulong totalNumRead = 0;
+ while (totalNumRead != nNumberOfBytesToRead)
+ {
+ const uint chunkSize = min(nNumberOfBytesToRead - totalNumRead, 0xffff_0000);
+ DWORD numRead = void;
+ const result = ReadFile(hFile, lpBuffer + totalNumRead, chunkSize, &numRead, null);
+ if (result == 0 || numRead != chunkSize)
+ return false;
+ totalNumRead += chunkSize;
+ }
+ return true;
+ }
+
+ alias defaults =
+ AliasSeq!(GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, (SECURITY_ATTRIBUTES*).init,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
+ HANDLE.init);
+ auto h = trustedCreateFileW(namez, defaults);
+
+ cenforce(h != INVALID_HANDLE_VALUE, name, namez);
+ scope(exit) cenforce(trustedCloseHandle(h), name, namez);
+ ulong fileSize = void;
+ cenforce(trustedGetFileSize(h, fileSize), name, namez);
+ size_t size = min(upTo, fileSize);
+ auto buf = uninitializedArray!(ubyte[])(size);
+
+ scope(failure)
+ {
+ () @trusted { GC.free(buf.ptr); } ();
+ }
+
+ if (size)
+ cenforce(trustedReadFile(h, &buf[0], size), name, namez);
+ return buf[0 .. size];
+}
+
+version (linux) @safe unittest
+{
+ // A file with "zero" length that doesn't have 0 length at all
+ auto s = std.file.readText("/proc/sys/kernel/osrelease");
+ assert(s.length > 0);
+ //writefln("'%s'", s);
+}
+
+@safe unittest
+{
+ scope(exit) if (exists(deleteme)) remove(deleteme);
+ import std.stdio;
+ auto f = File(deleteme, "w");
+ f.write("abcd"); f.flush();
+ assert(read(deleteme) == "abcd");
+}
+
+/********************************************
+Read and validates (using $(REF validate, std,utf)) a text file. $(D S)
+can be a type of array of characters of any width and constancy. No
+width conversion is performed; if the width of the characters in file
+$(D name) is different from the width of elements of $(D S),
+validation will fail.
+
+Params:
+ name = string or range of characters representing the file _name
+
+Returns: Array of characters read.
+
+Throws: $(D FileException) on file error, $(D UTFException) on UTF
+decoding error.
+ */
+
+S readText(S = string, R)(R name)
+if (isSomeString!S &&
+ (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
+ !isConvertibleToString!R)
+{
+ import std.utf : validate;
+ static auto trustedCast(void[] buf) @trusted { return cast(S) buf; }
+ auto result = trustedCast(read(name));
+ validate(result);
+ return result;
+}
+
+///
+@safe unittest
+{
+ import std.exception : enforce;
+ write(deleteme, "abc"); // deleteme is the name of a temporary file
+ scope(exit) remove(deleteme);
+ string content = readText(deleteme);
+ enforce(content == "abc");
+}
+
+/// ditto
+S readText(S = string, R)(auto ref R name)
+if (isConvertibleToString!R)
+{
+ return readText!(S, StringTypeOf!R)(name);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, readText(TestAliasedString(null))));
+}
+
+/*********************************************
+Write $(D buffer) to file $(D name).
+
+Creates the file if it does not already exist.
+
+Params:
+ name = string or range of characters representing the file _name
+ buffer = data to be written to file
+
+Throws: $(D FileException) on error.
+
+See_also: $(REF toFile, std,stdio)
+ */
+void write(R)(R name, const void[] buffer)
+if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
+ !isConvertibleToString!R)
+{
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ writeImpl(name, name.tempCString!FSChar(), buffer, false);
+ else
+ writeImpl(null, name.tempCString!FSChar(), buffer, false);
+}
+
+///
+@system unittest
+{
+ scope(exit)
+ {
+ assert(exists(deleteme));
+ remove(deleteme);
+ }
+
+ int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
+ write(deleteme, a); // deleteme is the name of a temporary file
+ assert(cast(int[]) read(deleteme) == a);
+}
+
+/// ditto
+void write(R)(auto ref R name, const void[] buffer)
+if (isConvertibleToString!R)
+{
+ write!(StringTypeOf!R)(name, buffer);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, write(TestAliasedString(null), null)));
+}
+
+/*********************************************
+Appends $(D buffer) to file $(D name).
+
+Creates the file if it does not already exist.
+
+Params:
+ name = string or range of characters representing the file _name
+ buffer = data to be appended to file
+
+Throws: $(D FileException) on error.
+ */
+void append(R)(R name, const void[] buffer)
+if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
+ !isConvertibleToString!R)
+{
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ writeImpl(name, name.tempCString!FSChar(), buffer, true);
+ else
+ writeImpl(null, name.tempCString!FSChar(), buffer, true);
+}
+
+///
+@system unittest
+{
+ scope(exit)
+ {
+ assert(exists(deleteme));
+ remove(deleteme);
+ }
+
+ int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
+ write(deleteme, a); // deleteme is the name of a temporary file
+ int[] b = [ 13, 21 ];
+ append(deleteme, b);
+ assert(cast(int[]) read(deleteme) == a ~ b);
+}
+
+/// ditto
+void append(R)(auto ref R name, const void[] buffer)
+if (isConvertibleToString!R)
+{
+ append!(StringTypeOf!R)(name, buffer);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, append(TestAliasedString("foo"), [0, 1, 2, 3])));
+}
+
+// Posix implementation helper for write and append
+
+version (Posix) private void writeImpl(const(char)[] name, const(FSChar)* namez,
+ in void[] buffer, bool append) @trusted
+{
+ import std.conv : octal;
+
+ // append or write
+ auto mode = append ? O_CREAT | O_WRONLY | O_APPEND
+ : O_CREAT | O_WRONLY | O_TRUNC;
+
+ immutable fd = core.sys.posix.fcntl.open(namez, mode, octal!666);
+ cenforce(fd != -1, name, namez);
+ {
+ scope(failure) core.sys.posix.unistd.close(fd);
+
+ immutable size = buffer.length;
+ size_t sum, cnt = void;
+ while (sum != size)
+ {
+ cnt = (size - sum < 2^^30) ? (size - sum) : 2^^30;
+ const numwritten = core.sys.posix.unistd.write(fd, buffer.ptr + sum, cnt);
+ if (numwritten != cnt)
+ break;
+ sum += numwritten;
+ }
+ cenforce(sum == size, name, namez);
+ }
+ cenforce(core.sys.posix.unistd.close(fd) == 0, name, namez);
+}
+
+// Windows implementation helper for write and append
+
+version (Windows) private void writeImpl(const(char)[] name, const(FSChar)* namez,
+ in void[] buffer, bool append) @trusted
+{
+ HANDLE h;
+ if (append)
+ {
+ alias defaults =
+ AliasSeq!(GENERIC_WRITE, 0, null, OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
+ HANDLE.init);
+
+ h = CreateFileW(namez, defaults);
+ cenforce(h != INVALID_HANDLE_VALUE, name, namez);
+ cenforce(SetFilePointer(h, 0, null, FILE_END) != INVALID_SET_FILE_POINTER,
+ name, namez);
+ }
+ else // write
+ {
+ alias defaults =
+ AliasSeq!(GENERIC_WRITE, 0, null, CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
+ HANDLE.init);
+
+ h = CreateFileW(namez, defaults);
+ cenforce(h != INVALID_HANDLE_VALUE, name, namez);
+ }
+ immutable size = buffer.length;
+ size_t sum, cnt = void;
+ DWORD numwritten = void;
+ while (sum != size)
+ {
+ cnt = (size - sum < 2^^30) ? (size - sum) : 2^^30;
+ WriteFile(h, buffer.ptr + sum, cast(uint) cnt, &numwritten, null);
+ if (numwritten != cnt)
+ break;
+ sum += numwritten;
+ }
+ cenforce(sum == size && CloseHandle(h), name, namez);
+}
+
+/***************************************************
+ * Rename file $(D from) _to $(D to).
+ * If the target file exists, it is overwritten.
+ * Params:
+ * from = string or range of characters representing the existing file name
+ * to = string or range of characters representing the target file name
+ * Throws: $(D FileException) on error.
+ */
+void rename(RF, RT)(RF from, RT to)
+if ((isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) || isSomeString!RF)
+ && !isConvertibleToString!RF &&
+ (isInputRange!RT && !isInfinite!RT && isSomeChar!(ElementEncodingType!RT) || isSomeString!RT)
+ && !isConvertibleToString!RT)
+{
+ // Place outside of @trusted block
+ auto fromz = from.tempCString!FSChar();
+ auto toz = to.tempCString!FSChar();
+
+ static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char))
+ alias f = from;
+ else
+ enum string f = null;
+
+ static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char))
+ alias t = to;
+ else
+ enum string t = null;
+
+ renameImpl(f, t, fromz, toz);
+}
+
+/// ditto
+void rename(RF, RT)(auto ref RF from, auto ref RT to)
+if (isConvertibleToString!RF || isConvertibleToString!RT)
+{
+ import std.meta : staticMap;
+ alias Types = staticMap!(convertToString, RF, RT);
+ rename!Types(from, to);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, rename(TestAliasedString(null), TestAliasedString(null))));
+ static assert(__traits(compiles, rename("", TestAliasedString(null))));
+ static assert(__traits(compiles, rename(TestAliasedString(null), "")));
+ import std.utf : byChar;
+ static assert(__traits(compiles, rename(TestAliasedString(null), "".byChar)));
+}
+
+private void renameImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz) @trusted
+{
+ version (Windows)
+ {
+ import std.exception : enforce;
+
+ const result = MoveFileExW(fromz, toz, MOVEFILE_REPLACE_EXISTING);
+ if (!result)
+ {
+ import core.stdc.wchar_ : wcslen;
+ import std.conv : to, text;
+
+ if (!f)
+ f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]);
+
+ if (!t)
+ t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
+
+ enforce(false,
+ new FileException(
+ text("Attempting to rename file ", f, " to ", t)));
+ }
+ }
+ else version (Posix)
+ {
+ static import core.stdc.stdio;
+
+ cenforce(core.stdc.stdio.rename(fromz, toz) == 0, t, toz);
+ }
+}
+
+@safe unittest
+{
+ import std.utf : byWchar;
+
+ auto t1 = deleteme, t2 = deleteme~"2";
+ scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
+ write(t1, "1");
+ rename(t1, t2);
+ assert(readText(t2) == "1");
+ write(t1, "2");
+ rename(t1, t2.byWchar);
+ assert(readText(t2) == "2");
+}
+
+
+/***************************************************
+Delete file $(D name).
+
+Params:
+ name = string or range of characters representing the file _name
+
+Throws: $(D FileException) on error.
+ */
+void remove(R)(R name)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ removeImpl(name, name.tempCString!FSChar());
+ else
+ removeImpl(null, name.tempCString!FSChar());
+}
+
+/// ditto
+void remove(R)(auto ref R name)
+if (isConvertibleToString!R)
+{
+ remove!(StringTypeOf!R)(name);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, remove(TestAliasedString("foo"))));
+}
+
+private void removeImpl(const(char)[] name, const(FSChar)* namez) @trusted
+{
+ version (Windows)
+ {
+ cenforce(DeleteFileW(namez), name, namez);
+ }
+ else version (Posix)
+ {
+ static import core.stdc.stdio;
+
+ if (!name)
+ {
+ import core.stdc.string : strlen;
+ auto len = strlen(namez);
+ name = namez[0 .. len];
+ }
+ cenforce(core.stdc.stdio.remove(namez) == 0,
+ "Failed to remove file " ~ name);
+ }
+}
+
+version (Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(R)(R name)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R))
+{
+ auto namez = name.tempCString!FSChar();
+
+ WIN32_FILE_ATTRIBUTE_DATA fad = void;
+
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ {
+ static void getFA(const(char)[] name, const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
+ {
+ import std.exception : enforce;
+ enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad),
+ new FileException(name.idup));
+ }
+ getFA(name, namez, fad);
+ }
+ else
+ {
+ static void getFA(const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
+ {
+ import core.stdc.wchar_ : wcslen;
+ import std.conv : to;
+ import std.exception : enforce;
+
+ enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad),
+ new FileException(namez[0 .. wcslen(namez)].to!string));
+ }
+ getFA(namez, fad);
+ }
+ return fad;
+}
+
+version (Windows) private ulong makeUlong(DWORD dwLow, DWORD dwHigh) @safe pure nothrow @nogc
+{
+ ULARGE_INTEGER li;
+ li.LowPart = dwLow;
+ li.HighPart = dwHigh;
+ return li.QuadPart;
+}
+
+/***************************************************
+Get size of file $(D name) in bytes.
+
+Params:
+ name = string or range of characters representing the file _name
+
+Throws: $(D FileException) on error (e.g., file not found).
+ */
+ulong getSize(R)(R name)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ version (Windows)
+ {
+ with (getFileAttributesWin(name))
+ return makeUlong(nFileSizeLow, nFileSizeHigh);
+ }
+ else version (Posix)
+ {
+ auto namez = name.tempCString();
+
+ static trustedStat(const(FSChar)* namez, out stat_t buf) @trusted
+ {
+ return stat(namez, &buf);
+ }
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias names = name;
+ else
+ string names = null;
+ stat_t statbuf = void;
+ cenforce(trustedStat(namez, statbuf) == 0, names, namez);
+ return statbuf.st_size;
+ }
+}
+
+/// ditto
+ulong getSize(R)(auto ref R name)
+if (isConvertibleToString!R)
+{
+ return getSize!(StringTypeOf!R)(name);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, getSize(TestAliasedString("foo"))));
+}
+
+@safe unittest
+{
+ // create a file of size 1
+ write(deleteme, "a");
+ scope(exit) { assert(exists(deleteme)); remove(deleteme); }
+ assert(getSize(deleteme) == 1);
+ // create a file of size 3
+ write(deleteme, "abc");
+ import std.utf : byChar;
+ assert(getSize(deleteme.byChar) == 3);
+}
+
+
+// Reads a time field from a stat_t with full precision.
+version (Posix)
+private SysTime statTimeToStdTime(char which)(ref stat_t statbuf)
+{
+ auto unixTime = mixin(`statbuf.st_` ~ which ~ `time`);
+ long stdTime = unixTimeToStdTime(unixTime);
+
+ static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `tim`))))
+ stdTime += mixin(`statbuf.st_` ~ which ~ `tim.tv_nsec`) / 100;
+ else
+ static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `timensec`))))
+ stdTime += mixin(`statbuf.st_` ~ which ~ `timensec`) / 100;
+ else
+ static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `time_nsec`))))
+ stdTime += mixin(`statbuf.st_` ~ which ~ `time_nsec`) / 100;
+ else
+ static if (is(typeof(mixin(`statbuf.__st_` ~ which ~ `timensec`))))
+ stdTime += mixin(`statbuf.__st_` ~ which ~ `timensec`) / 100;
+
+ return SysTime(stdTime);
+}
+
+/++
+ Get the access and modified times of file or folder $(D name).
+
+ Params:
+ name = File/Folder _name to get times for.
+ accessTime = Time the file/folder was last accessed.
+ modificationTime = Time the file/folder was last modified.
+
+ Throws:
+ $(D FileException) on error.
+ +/
+void getTimes(R)(R name,
+ out SysTime accessTime,
+ out SysTime modificationTime)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ version (Windows)
+ {
+ import std.datetime.systime : FILETIMEToSysTime;
+
+ with (getFileAttributesWin(name))
+ {
+ accessTime = FILETIMEToSysTime(&ftLastAccessTime);
+ modificationTime = FILETIMEToSysTime(&ftLastWriteTime);
+ }
+ }
+ else version (Posix)
+ {
+ auto namez = name.tempCString();
+
+ static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
+ {
+ return stat(namez, &buf);
+ }
+ stat_t statbuf = void;
+
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias names = name;
+ else
+ string names = null;
+ cenforce(trustedStat(namez, statbuf) == 0, names, namez);
+
+ accessTime = statTimeToStdTime!'a'(statbuf);
+ modificationTime = statTimeToStdTime!'m'(statbuf);
+ }
+}
+
+/// ditto
+void getTimes(R)(auto ref R name,
+ out SysTime accessTime,
+ out SysTime modificationTime)
+if (isConvertibleToString!R)
+{
+ return getTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
+}
+
+@safe unittest
+{
+ SysTime atime, mtime;
+ static assert(__traits(compiles, getTimes(TestAliasedString("foo"), atime, mtime)));
+}
+
+@system unittest
+{
+ import std.stdio : writefln;
+
+ auto currTime = Clock.currTime();
+
+ write(deleteme, "a");
+ scope(exit) { assert(exists(deleteme)); remove(deleteme); }
+
+ SysTime accessTime1 = void;
+ SysTime modificationTime1 = void;
+
+ getTimes(deleteme, accessTime1, modificationTime1);
+
+ enum leeway = dur!"seconds"(5);
+
+ {
+ auto diffa = accessTime1 - currTime;
+ auto diffm = modificationTime1 - currTime;
+ scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime1, modificationTime1, currTime, diffa, diffm);
+
+ assert(abs(diffa) <= leeway);
+ assert(abs(diffm) <= leeway);
+ }
+
+ version (fullFileTests)
+ {
+ import core.thread;
+ enum sleepTime = dur!"seconds"(2);
+ Thread.sleep(sleepTime);
+
+ currTime = Clock.currTime();
+ write(deleteme, "b");
+
+ SysTime accessTime2 = void;
+ SysTime modificationTime2 = void;
+
+ getTimes(deleteme, accessTime2, modificationTime2);
+
+ {
+ auto diffa = accessTime2 - currTime;
+ auto diffm = modificationTime2 - currTime;
+ scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime2, modificationTime2, currTime, diffa, diffm);
+
+ //There is no guarantee that the access time will be updated.
+ assert(abs(diffa) <= leeway + sleepTime);
+ assert(abs(diffm) <= leeway);
+ }
+
+ assert(accessTime1 <= accessTime2);
+ assert(modificationTime1 <= modificationTime2);
+ }
+}
+
+
+version (StdDdoc)
+{
+ /++
+ $(BLUE This function is Windows-Only.)
+
+ Get creation/access/modified times of file $(D name).
+
+ This is the same as $(D getTimes) except that it also gives you the file
+ creation time - which isn't possible on Posix systems.
+
+ Params:
+ name = File _name to get times for.
+ fileCreationTime = Time the file was created.
+ fileAccessTime = Time the file was last accessed.
+ fileModificationTime = Time the file was last modified.
+
+ Throws:
+ $(D FileException) on error.
+ +/
+ void getTimesWin(R)(R name,
+ out SysTime fileCreationTime,
+ out SysTime fileAccessTime,
+ out SysTime fileModificationTime)
+ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R);
+}
+else version (Windows)
+{
+ void getTimesWin(R)(R name,
+ out SysTime fileCreationTime,
+ out SysTime fileAccessTime,
+ out SysTime fileModificationTime)
+ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+ {
+ import std.datetime.systime : FILETIMEToSysTime;
+
+ with (getFileAttributesWin(name))
+ {
+ fileCreationTime = FILETIMEToSysTime(&ftCreationTime);
+ fileAccessTime = FILETIMEToSysTime(&ftLastAccessTime);
+ fileModificationTime = FILETIMEToSysTime(&ftLastWriteTime);
+ }
+ }
+
+ void getTimesWin(R)(auto ref R name,
+ out SysTime fileCreationTime,
+ out SysTime fileAccessTime,
+ out SysTime fileModificationTime)
+ if (isConvertibleToString!R)
+ {
+ getTimesWin!(StringTypeOf!R)(name, fileCreationTime, fileAccessTime, fileModificationTime);
+ }
+}
+
+version (Windows) @system unittest
+{
+ import std.stdio : writefln;
+ auto currTime = Clock.currTime();
+
+ write(deleteme, "a");
+ scope(exit) { assert(exists(deleteme)); remove(deleteme); }
+
+ SysTime creationTime1 = void;
+ SysTime accessTime1 = void;
+ SysTime modificationTime1 = void;
+
+ getTimesWin(deleteme, creationTime1, accessTime1, modificationTime1);
+
+ enum leeway = dur!"seconds"(5);
+
+ {
+ auto diffc = creationTime1 - currTime;
+ auto diffa = accessTime1 - currTime;
+ auto diffm = modificationTime1 - currTime;
+ scope(failure)
+ {
+ writefln("[%s] [%s] [%s] [%s] [%s] [%s] [%s]",
+ creationTime1, accessTime1, modificationTime1, currTime, diffc, diffa, diffm);
+ }
+
+ // Deleting and recreating a file doesn't seem to always reset the "file creation time"
+ //assert(abs(diffc) <= leeway);
+ assert(abs(diffa) <= leeway);
+ assert(abs(diffm) <= leeway);
+ }
+
+ version (fullFileTests)
+ {
+ import core.thread;
+ Thread.sleep(dur!"seconds"(2));
+
+ currTime = Clock.currTime();
+ write(deleteme, "b");
+
+ SysTime creationTime2 = void;
+ SysTime accessTime2 = void;
+ SysTime modificationTime2 = void;
+
+ getTimesWin(deleteme, creationTime2, accessTime2, modificationTime2);
+
+ {
+ auto diffa = accessTime2 - currTime;
+ auto diffm = modificationTime2 - currTime;
+ scope(failure)
+ {
+ writefln("[%s] [%s] [%s] [%s] [%s]",
+ accessTime2, modificationTime2, currTime, diffa, diffm);
+ }
+
+ assert(abs(diffa) <= leeway);
+ assert(abs(diffm) <= leeway);
+ }
+
+ assert(creationTime1 == creationTime2);
+ assert(accessTime1 <= accessTime2);
+ assert(modificationTime1 <= modificationTime2);
+ }
+
+ {
+ SysTime ctime, atime, mtime;
+ static assert(__traits(compiles, getTimesWin(TestAliasedString("foo"), ctime, atime, mtime)));
+ }
+}
+
+
+/++
+ Set access/modified times of file or folder $(D name).
+
+ Params:
+ name = File/Folder _name to get times for.
+ accessTime = Time the file/folder was last accessed.
+ modificationTime = Time the file/folder was last modified.
+
+ Throws:
+ $(D FileException) on error.
+ +/
+void setTimes(R)(R name,
+ SysTime accessTime,
+ SysTime modificationTime)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ version (Windows)
+ {
+ import std.datetime.systime : SysTimeToFILETIME;
+
+ auto namez = name.tempCString!FSChar();
+ static auto trustedCreateFileW(const(FSChar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode,
+ SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition,
+ DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted
+ {
+ return CreateFileW(namez, dwDesiredAccess, dwShareMode,
+ lpSecurityAttributes, dwCreationDisposition,
+ dwFlagsAndAttributes, hTemplateFile);
+
+ }
+ static auto trustedCloseHandle(HANDLE hObject) @trusted
+ {
+ return CloseHandle(hObject);
+ }
+ static auto trustedSetFileTime(HANDLE hFile, in FILETIME *lpCreationTime,
+ in ref FILETIME lpLastAccessTime, in ref FILETIME lpLastWriteTime) @trusted
+ {
+ return SetFileTime(hFile, lpCreationTime, &lpLastAccessTime, &lpLastWriteTime);
+ }
+
+ const ta = SysTimeToFILETIME(accessTime);
+ const tm = SysTimeToFILETIME(modificationTime);
+ alias defaults =
+ AliasSeq!(GENERIC_WRITE,
+ 0,
+ null,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL |
+ FILE_ATTRIBUTE_DIRECTORY |
+ FILE_FLAG_BACKUP_SEMANTICS,
+ HANDLE.init);
+ auto h = trustedCreateFileW(namez, defaults);
+
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias names = name;
+ else
+ string names = null;
+ cenforce(h != INVALID_HANDLE_VALUE, names, namez);
+
+ scope(exit)
+ cenforce(trustedCloseHandle(h), names, namez);
+
+ cenforce(trustedSetFileTime(h, null, ta, tm), names, namez);
+ }
+ else version (Posix)
+ {
+ auto namez = name.tempCString!FSChar();
+ static if (is(typeof(&utimensat)))
+ {
+ static auto trustedUtimensat(int fd, const(FSChar)* namez, const ref timespec[2] times, int flags) @trusted
+ {
+ return utimensat(fd, namez, times, flags);
+ }
+ timespec[2] t = void;
+
+ t[0] = accessTime.toTimeSpec();
+ t[1] = modificationTime.toTimeSpec();
+
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias names = name;
+ else
+ string names = null;
+ cenforce(trustedUtimensat(AT_FDCWD, namez, t, 0) == 0, names, namez);
+ }
+ else
+ {
+ static auto trustedUtimes(const(FSChar)* namez, const ref timeval[2] times) @trusted
+ {
+ return utimes(namez, times);
+ }
+ timeval[2] t = void;
+
+ t[0] = accessTime.toTimeVal();
+ t[1] = modificationTime.toTimeVal();
+
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias names = name;
+ else
+ string names = null;
+ cenforce(trustedUtimes(namez, t) == 0, names, namez);
+ }
+ }
+}
+
+/// ditto
+void setTimes(R)(auto ref R name,
+ SysTime accessTime,
+ SysTime modificationTime)
+if (isConvertibleToString!R)
+{
+ setTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
+}
+
+@safe unittest
+{
+ if (false) // Test instatiation
+ setTimes(TestAliasedString("foo"), SysTime.init, SysTime.init);
+}
+
+@system unittest
+{
+ import std.stdio : File;
+ string newdir = deleteme ~ r".dir";
+ string dir = newdir ~ r"/a/b/c";
+ string file = dir ~ "/file";
+
+ if (!exists(dir)) mkdirRecurse(dir);
+ { auto f = File(file, "w"); }
+
+ void testTimes(int hnsecValue)
+ {
+ foreach (path; [file, dir]) // test file and dir
+ {
+ SysTime atime = SysTime(DateTime(2010, 10, 4, 0, 0, 30), hnsecs(hnsecValue));
+ SysTime mtime = SysTime(DateTime(2011, 10, 4, 0, 0, 30), hnsecs(hnsecValue));
+ setTimes(path, atime, mtime);
+
+ SysTime atime_res;
+ SysTime mtime_res;
+ getTimes(path, atime_res, mtime_res);
+ assert(atime == atime_res);
+ assert(mtime == mtime_res);
+ }
+ }
+
+ testTimes(0);
+ version (linux)
+ testTimes(123_456_7);
+
+ rmdirRecurse(newdir);
+}
+
+/++
+ Returns the time that the given file was last modified.
+
+ Throws:
+ $(D FileException) if the given file does not exist.
++/
+SysTime timeLastModified(R)(R name)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ version (Windows)
+ {
+ SysTime dummy;
+ SysTime ftm;
+
+ getTimesWin(name, dummy, dummy, ftm);
+
+ return ftm;
+ }
+ else version (Posix)
+ {
+ auto namez = name.tempCString!FSChar();
+ static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
+ {
+ return stat(namez, &buf);
+ }
+ stat_t statbuf = void;
+
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias names = name;
+ else
+ string names = null;
+ cenforce(trustedStat(namez, statbuf) == 0, names, namez);
+
+ return statTimeToStdTime!'m'(statbuf);
+ }
+}
+
+/// ditto
+SysTime timeLastModified(R)(auto ref R name)
+if (isConvertibleToString!R)
+{
+ return timeLastModified!(StringTypeOf!R)(name);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, timeLastModified(TestAliasedString("foo"))));
+}
+
+/++
+ Returns the time that the given file was last modified. If the
+ file does not exist, returns $(D returnIfMissing).
+
+ A frequent usage pattern occurs in build automation tools such as
+ $(HTTP gnu.org/software/make, make) or $(HTTP
+ en.wikipedia.org/wiki/Apache_Ant, ant). To check whether file $(D
+ target) must be rebuilt from file $(D source) (i.e., $(D target) is
+ older than $(D source) or does not exist), use the comparison
+ below. The code throws a $(D FileException) if $(D source) does not
+ exist (as it should). On the other hand, the $(D SysTime.min) default
+ makes a non-existing $(D target) seem infinitely old so the test
+ correctly prompts building it.
+
+ Params:
+ name = The _name of the file to get the modification time for.
+ returnIfMissing = The time to return if the given file does not exist.
+
+Example:
+--------------------
+if (timeLastModified(source) >= timeLastModified(target, SysTime.min))
+{
+ // must (re)build
+}
+else
+{
+ // target is up-to-date
+}
+--------------------
++/
+SysTime timeLastModified(R)(R name, SysTime returnIfMissing)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R))
+{
+ version (Windows)
+ {
+ if (!exists(name))
+ return returnIfMissing;
+
+ SysTime dummy;
+ SysTime ftm;
+
+ getTimesWin(name, dummy, dummy, ftm);
+
+ return ftm;
+ }
+ else version (Posix)
+ {
+ auto namez = name.tempCString!FSChar();
+ static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
+ {
+ return stat(namez, &buf);
+ }
+ stat_t statbuf = void;
+
+ return trustedStat(namez, statbuf) != 0 ?
+ returnIfMissing :
+ statTimeToStdTime!'m'(statbuf);
+ }
+}
+
+@safe unittest
+{
+ //std.process.system("echo a > deleteme") == 0 || assert(false);
+ if (exists(deleteme))
+ remove(deleteme);
+
+ write(deleteme, "a\n");
+
+ scope(exit)
+ {
+ assert(exists(deleteme));
+ remove(deleteme);
+ }
+
+ // assert(lastModified("deleteme") >
+ // lastModified("this file does not exist", SysTime.min));
+ //assert(lastModified("deleteme") > lastModified(__FILE__));
+}
+
+
+// Tests sub-second precision of querying file times.
+// Should pass on most modern systems running on modern filesystems.
+// Exceptions:
+// - FreeBSD, where one would need to first set the
+// vfs.timestamp_precision sysctl to a value greater than zero.
+// - OS X, where the native filesystem (HFS+) stores filesystem
+// timestamps with 1-second precision.
+version (FreeBSD) {} else
+version (OSX) {} else
+@system unittest
+{
+ import core.thread;
+
+ if (exists(deleteme))
+ remove(deleteme);
+
+ SysTime lastTime;
+ foreach (n; 0 .. 3)
+ {
+ write(deleteme, "a");
+ auto time = timeLastModified(deleteme);
+ remove(deleteme);
+ assert(time != lastTime);
+ lastTime = time;
+ Thread.sleep(10.msecs);
+ }
+}
+
+
+/**
+ * Determine whether the given file (or directory) _exists.
+ * Params:
+ * name = string or range of characters representing the file _name
+ * Returns:
+ * true if the file _name specified as input _exists
+ */
+bool exists(R)(R name)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ return existsImpl(name.tempCString!FSChar());
+}
+
+/// ditto
+bool exists(R)(auto ref R name)
+if (isConvertibleToString!R)
+{
+ return exists!(StringTypeOf!R)(name);
+}
+
+private bool existsImpl(const(FSChar)* namez) @trusted nothrow @nogc
+{
+ version (Windows)
+ {
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
+ // fileio/base/getfileattributes.asp
+ return GetFileAttributesW(namez) != 0xFFFFFFFF;
+ }
+ else version (Posix)
+ {
+ /*
+ The reason why we use stat (and not access) here is
+ the quirky behavior of access for SUID programs: if
+ we used access, a file may not appear to "exist",
+ despite that the program would be able to open it
+ just fine. The behavior in question is described as
+ follows in the access man page:
+
+ > The check is done using the calling process's real
+ > UID and GID, rather than the effective IDs as is
+ > done when actually attempting an operation (e.g.,
+ > open(2)) on the file. This allows set-user-ID
+ > programs to easily determine the invoking user's
+ > authority.
+
+ While various operating systems provide eaccess or
+ euidaccess functions, these are not part of POSIX -
+ so it's safer to use stat instead.
+ */
+
+ stat_t statbuf = void;
+ return lstat(namez, &statbuf) == 0;
+ }
+ else
+ static assert(0);
+}
+
+@safe unittest
+{
+ assert(exists("."));
+ assert(!exists("this file does not exist"));
+ write(deleteme, "a\n");
+ scope(exit) { assert(exists(deleteme)); remove(deleteme); }
+ assert(exists(deleteme));
+}
+
+@safe unittest // Bugzilla 16573
+{
+ enum S : string { foo = "foo" }
+ assert(__traits(compiles, S.foo.exists));
+}
+
+/++
+ Returns the attributes of the given file.
+
+ Note that the file attributes on Windows and Posix systems are
+ completely different. On Windows, they're what is returned by
+ $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx,
+ GetFileAttributes), whereas on Posix systems, they're the $(LUCKY
+ st_mode) value which is part of the $(D stat struct) gotten by
+ calling the $(HTTP en.wikipedia.org/wiki/Stat_%28Unix%29, $(D stat))
+ function.
+
+ On Posix systems, if the given file is a symbolic link, then
+ attributes are the attributes of the file pointed to by the symbolic
+ link.
+
+ Params:
+ name = The file to get the attributes of.
+
+ Throws: $(D FileException) on error.
+ +/
+uint getAttributes(R)(R name)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ version (Windows)
+ {
+ auto namez = name.tempCString!FSChar();
+ static auto trustedGetFileAttributesW(const(FSChar)* namez) @trusted
+ {
+ return GetFileAttributesW(namez);
+ }
+ immutable result = trustedGetFileAttributesW(namez);
+
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias names = name;
+ else
+ string names = null;
+ cenforce(result != INVALID_FILE_ATTRIBUTES, names, namez);
+
+ return result;
+ }
+ else version (Posix)
+ {
+ auto namez = name.tempCString!FSChar();
+ static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
+ {
+ return stat(namez, &buf);
+ }
+ stat_t statbuf = void;
+
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias names = name;
+ else
+ string names = null;
+ cenforce(trustedStat(namez, statbuf) == 0, names, namez);
+
+ return statbuf.st_mode;
+ }
+}
+
+/// ditto
+uint getAttributes(R)(auto ref R name)
+if (isConvertibleToString!R)
+{
+ return getAttributes!(StringTypeOf!R)(name);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, getAttributes(TestAliasedString(null))));
+}
+
+/++
+ If the given file is a symbolic link, then this returns the attributes of the
+ symbolic link itself rather than file that it points to. If the given file
+ is $(I not) a symbolic link, then this function returns the same result
+ as getAttributes.
+
+ On Windows, getLinkAttributes is identical to getAttributes. It exists on
+ Windows so that you don't have to special-case code for Windows when dealing
+ with symbolic links.
+
+ Params:
+ name = The file to get the symbolic link attributes of.
+
+ Returns:
+ the attributes
+
+ Throws:
+ $(D FileException) on error.
+ +/
+uint getLinkAttributes(R)(R name)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ version (Windows)
+ {
+ return getAttributes(name);
+ }
+ else version (Posix)
+ {
+ auto namez = name.tempCString!FSChar();
+ static auto trustedLstat(const(FSChar)* namez, ref stat_t buf) @trusted
+ {
+ return lstat(namez, &buf);
+ }
+ stat_t lstatbuf = void;
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias names = name;
+ else
+ string names = null;
+ cenforce(trustedLstat(namez, lstatbuf) == 0, names, namez);
+ return lstatbuf.st_mode;
+ }
+}
+
+/// ditto
+uint getLinkAttributes(R)(auto ref R name)
+if (isConvertibleToString!R)
+{
+ return getLinkAttributes!(StringTypeOf!R)(name);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, getLinkAttributes(TestAliasedString(null))));
+}
+
+/++
+ Set the _attributes of the given file.
+
+ Params:
+ name = the file _name
+ attributes = the _attributes to set the file to
+
+ Throws:
+ $(D FileException) if the given file does not exist.
+ +/
+void setAttributes(R)(R name, uint attributes)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ version (Windows)
+ {
+ auto namez = name.tempCString!FSChar();
+ static auto trustedSetFileAttributesW(const(FSChar)* namez, uint dwFileAttributes) @trusted
+ {
+ return SetFileAttributesW(namez, dwFileAttributes);
+ }
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias names = name;
+ else
+ string names = null;
+ cenforce(trustedSetFileAttributesW(namez, attributes), names, namez);
+ }
+ else version (Posix)
+ {
+ auto namez = name.tempCString!FSChar();
+ static auto trustedChmod(const(FSChar)* namez, mode_t mode) @trusted
+ {
+ return chmod(namez, mode);
+ }
+ assert(attributes <= mode_t.max);
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias names = name;
+ else
+ string names = null;
+ cenforce(!trustedChmod(namez, cast(mode_t) attributes), names, namez);
+ }
+}
+
+/// ditto
+void setAttributes(R)(auto ref R name, uint attributes)
+if (isConvertibleToString!R)
+{
+ return setAttributes!(StringTypeOf!R)(name, attributes);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, setAttributes(TestAliasedString(null), 0)));
+}
+
+/++
+ Returns whether the given file is a directory.
+
+ Params:
+ name = The path to the file.
+
+ Returns:
+ true if name specifies a directory
+
+ Throws:
+ $(D FileException) if the given file does not exist.
+
+Example:
+--------------------
+assert(!"/etc/fonts/fonts.conf".isDir);
+assert("/usr/share/include".isDir);
+--------------------
+ +/
+@property bool isDir(R)(R name)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ version (Windows)
+ {
+ return (getAttributes(name) & FILE_ATTRIBUTE_DIRECTORY) != 0;
+ }
+ else version (Posix)
+ {
+ return (getAttributes(name) & S_IFMT) == S_IFDIR;
+ }
+}
+
+/// ditto
+@property bool isDir(R)(auto ref R name)
+if (isConvertibleToString!R)
+{
+ return name.isDir!(StringTypeOf!R);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, TestAliasedString(null).isDir));
+}
+
+@safe unittest
+{
+ version (Windows)
+ {
+ if ("C:\\Program Files\\".exists)
+ assert("C:\\Program Files\\".isDir);
+
+ if ("C:\\Windows\\system.ini".exists)
+ assert(!"C:\\Windows\\system.ini".isDir);
+ }
+ else version (Posix)
+ {
+ if (system_directory.exists)
+ assert(system_directory.isDir);
+
+ if (system_file.exists)
+ assert(!system_file.isDir);
+ }
+}
+
+@system unittest
+{
+ version (Windows)
+ enum dir = "C:\\Program Files\\";
+ else version (Posix)
+ enum dir = system_directory;
+
+ if (dir.exists)
+ {
+ DirEntry de = DirEntry(dir);
+ assert(de.isDir);
+ assert(DirEntry(dir).isDir);
+ }
+}
+
+/++
+ Returns whether the given file _attributes are for a directory.
+
+ Params:
+ attributes = The file _attributes.
+
+ Returns:
+ true if attributes specifies a directory
+
+Example:
+--------------------
+assert(!attrIsDir(getAttributes("/etc/fonts/fonts.conf")));
+assert(!attrIsDir(getLinkAttributes("/etc/fonts/fonts.conf")));
+--------------------
+ +/
+bool attrIsDir(uint attributes) @safe pure nothrow @nogc
+{
+ version (Windows)
+ {
+ return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+ }
+ else version (Posix)
+ {
+ return (attributes & S_IFMT) == S_IFDIR;
+ }
+}
+
+@safe unittest
+{
+ version (Windows)
+ {
+ if ("C:\\Program Files\\".exists)
+ {
+ assert(attrIsDir(getAttributes("C:\\Program Files\\")));
+ assert(attrIsDir(getLinkAttributes("C:\\Program Files\\")));
+ }
+
+ if ("C:\\Windows\\system.ini".exists)
+ {
+ assert(!attrIsDir(getAttributes("C:\\Windows\\system.ini")));
+ assert(!attrIsDir(getLinkAttributes("C:\\Windows\\system.ini")));
+ }
+ }
+ else version (Posix)
+ {
+ if (system_directory.exists)
+ {
+ assert(attrIsDir(getAttributes(system_directory)));
+ assert(attrIsDir(getLinkAttributes(system_directory)));
+ }
+
+ if (system_file.exists)
+ {
+ assert(!attrIsDir(getAttributes(system_file)));
+ assert(!attrIsDir(getLinkAttributes(system_file)));
+ }
+ }
+}
+
+
+/++
+ Returns whether the given file (or directory) is a file.
+
+ On Windows, if a file is not a directory, then it's a file. So,
+ either $(D isFile) or $(D isDir) will return true for any given file.
+
+ On Posix systems, if $(D isFile) is $(D true), that indicates that the file
+ is a regular file (e.g. not a block not device). So, on Posix systems, it's
+ possible for both $(D isFile) and $(D isDir) to be $(D false) for a
+ particular file (in which case, it's a special file). You can use
+ $(D getAttributes) to get the attributes to figure out what type of special
+ it is, or you can use $(D DirEntry) to get at its $(D statBuf), which is the
+ result from $(D stat). In either case, see the man page for $(D stat) for
+ more information.
+
+ Params:
+ name = The path to the file.
+
+ Returns:
+ true if name specifies a file
+
+ Throws:
+ $(D FileException) if the given file does not exist.
+
+Example:
+--------------------
+assert("/etc/fonts/fonts.conf".isFile);
+assert(!"/usr/share/include".isFile);
+--------------------
+ +/
+@property bool isFile(R)(R name)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ version (Windows)
+ return !name.isDir;
+ else version (Posix)
+ return (getAttributes(name) & S_IFMT) == S_IFREG;
+}
+
+/// ditto
+@property bool isFile(R)(auto ref R name)
+if (isConvertibleToString!R)
+{
+ return isFile!(StringTypeOf!R)(name);
+}
+
+@system unittest // bugzilla 15658
+{
+ DirEntry e = DirEntry(".");
+ static assert(is(typeof(isFile(e))));
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, TestAliasedString(null).isFile));
+}
+
+@safe unittest
+{
+ version (Windows)
+ {
+ if ("C:\\Program Files\\".exists)
+ assert(!"C:\\Program Files\\".isFile);
+
+ if ("C:\\Windows\\system.ini".exists)
+ assert("C:\\Windows\\system.ini".isFile);
+ }
+ else version (Posix)
+ {
+ if (system_directory.exists)
+ assert(!system_directory.isFile);
+
+ if (system_file.exists)
+ assert(system_file.isFile);
+ }
+}
+
+
+/++
+ Returns whether the given file _attributes are for a file.
+
+ On Windows, if a file is not a directory, it's a file. So, either
+ $(D attrIsFile) or $(D attrIsDir) will return $(D true) for the
+ _attributes of any given file.
+
+ On Posix systems, if $(D attrIsFile) is $(D true), that indicates that the
+ file is a regular file (e.g. not a block not device). So, on Posix systems,
+ it's possible for both $(D attrIsFile) and $(D attrIsDir) to be $(D false)
+ for a particular file (in which case, it's a special file). If a file is a
+ special file, you can use the _attributes to check what type of special file
+ it is (see the man page for $(D stat) for more information).
+
+ Params:
+ attributes = The file _attributes.
+
+ Returns:
+ true if the given file _attributes are for a file
+
+Example:
+--------------------
+assert(attrIsFile(getAttributes("/etc/fonts/fonts.conf")));
+assert(attrIsFile(getLinkAttributes("/etc/fonts/fonts.conf")));
+--------------------
+ +/
+bool attrIsFile(uint attributes) @safe pure nothrow @nogc
+{
+ version (Windows)
+ {
+ return (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
+ }
+ else version (Posix)
+ {
+ return (attributes & S_IFMT) == S_IFREG;
+ }
+}
+
+@safe unittest
+{
+ version (Windows)
+ {
+ if ("C:\\Program Files\\".exists)
+ {
+ assert(!attrIsFile(getAttributes("C:\\Program Files\\")));
+ assert(!attrIsFile(getLinkAttributes("C:\\Program Files\\")));
+ }
+
+ if ("C:\\Windows\\system.ini".exists)
+ {
+ assert(attrIsFile(getAttributes("C:\\Windows\\system.ini")));
+ assert(attrIsFile(getLinkAttributes("C:\\Windows\\system.ini")));
+ }
+ }
+ else version (Posix)
+ {
+ if (system_directory.exists)
+ {
+ assert(!attrIsFile(getAttributes(system_directory)));
+ assert(!attrIsFile(getLinkAttributes(system_directory)));
+ }
+
+ if (system_file.exists)
+ {
+ assert(attrIsFile(getAttributes(system_file)));
+ assert(attrIsFile(getLinkAttributes(system_file)));
+ }
+ }
+}
+
+
+/++
+ Returns whether the given file is a symbolic link.
+
+ On Windows, returns $(D true) when the file is either a symbolic link or a
+ junction point.
+
+ Params:
+ name = The path to the file.
+
+ Returns:
+ true if name is a symbolic link
+
+ Throws:
+ $(D FileException) if the given file does not exist.
+ +/
+@property bool isSymlink(R)(R name)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ version (Windows)
+ return (getAttributes(name) & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+ else version (Posix)
+ return (getLinkAttributes(name) & S_IFMT) == S_IFLNK;
+}
+
+/// ditto
+@property bool isSymlink(R)(auto ref R name)
+if (isConvertibleToString!R)
+{
+ return name.isSymlink!(StringTypeOf!R);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, TestAliasedString(null).isSymlink));
+}
+
+@system unittest
+{
+ version (Windows)
+ {
+ if ("C:\\Program Files\\".exists)
+ assert(!"C:\\Program Files\\".isSymlink);
+
+ if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
+ assert("C:\\Documents and Settings\\".isSymlink);
+
+ enum fakeSymFile = "C:\\Windows\\system.ini";
+ if (fakeSymFile.exists)
+ {
+ assert(!fakeSymFile.isSymlink);
+
+ assert(!fakeSymFile.isSymlink);
+ assert(!attrIsSymlink(getAttributes(fakeSymFile)));
+ assert(!attrIsSymlink(getLinkAttributes(fakeSymFile)));
+
+ assert(attrIsFile(getAttributes(fakeSymFile)));
+ assert(attrIsFile(getLinkAttributes(fakeSymFile)));
+ assert(!attrIsDir(getAttributes(fakeSymFile)));
+ assert(!attrIsDir(getLinkAttributes(fakeSymFile)));
+
+ assert(getAttributes(fakeSymFile) == getLinkAttributes(fakeSymFile));
+ }
+ }
+ else version (Posix)
+ {
+ if (system_directory.exists)
+ {
+ assert(!system_directory.isSymlink);
+
+ immutable symfile = deleteme ~ "_slink\0";
+ scope(exit) if (symfile.exists) symfile.remove();
+
+ core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
+
+ assert(symfile.isSymlink);
+ assert(!attrIsSymlink(getAttributes(symfile)));
+ assert(attrIsSymlink(getLinkAttributes(symfile)));
+
+ assert(attrIsDir(getAttributes(symfile)));
+ assert(!attrIsDir(getLinkAttributes(symfile)));
+
+ assert(!attrIsFile(getAttributes(symfile)));
+ assert(!attrIsFile(getLinkAttributes(symfile)));
+ }
+
+ if (system_file.exists)
+ {
+ assert(!system_file.isSymlink);
+
+ immutable symfile = deleteme ~ "_slink\0";
+ scope(exit) if (symfile.exists) symfile.remove();
+
+ core.sys.posix.unistd.symlink(system_file, symfile.ptr);
+
+ assert(symfile.isSymlink);
+ assert(!attrIsSymlink(getAttributes(symfile)));
+ assert(attrIsSymlink(getLinkAttributes(symfile)));
+
+ assert(!attrIsDir(getAttributes(symfile)));
+ assert(!attrIsDir(getLinkAttributes(symfile)));
+
+ assert(attrIsFile(getAttributes(symfile)));
+ assert(!attrIsFile(getLinkAttributes(symfile)));
+ }
+ }
+
+ static assert(__traits(compiles, () @safe { return "dummy".isSymlink; }));
+}
+
+
+/++
+ Returns whether the given file attributes are for a symbolic link.
+
+ On Windows, return $(D true) when the file is either a symbolic link or a
+ junction point.
+
+ Params:
+ attributes = The file attributes.
+
+ Returns:
+ true if attributes are for a symbolic link
+
+Example:
+--------------------
+core.sys.posix.unistd.symlink("/etc/fonts/fonts.conf", "/tmp/alink");
+
+assert(!getAttributes("/tmp/alink").isSymlink);
+assert(getLinkAttributes("/tmp/alink").isSymlink);
+--------------------
+ +/
+bool attrIsSymlink(uint attributes) @safe pure nothrow @nogc
+{
+ version (Windows)
+ return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+ else version (Posix)
+ return (attributes & S_IFMT) == S_IFLNK;
+}
+
+
+/****************************************************
+ * Change directory to $(D pathname).
+ * Throws: $(D FileException) on error.
+ */
+void chdir(R)(R pathname)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ // Place outside of @trusted block
+ auto pathz = pathname.tempCString!FSChar();
+
+ version (Windows)
+ {
+ static auto trustedChdir(const(FSChar)* pathz) @trusted
+ {
+ return SetCurrentDirectoryW(pathz);
+ }
+ }
+ else version (Posix)
+ {
+ static auto trustedChdir(const(FSChar)* pathz) @trusted
+ {
+ return core.sys.posix.unistd.chdir(pathz) == 0;
+ }
+ }
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias pathStr = pathname;
+ else
+ string pathStr = null;
+ cenforce(trustedChdir(pathz), pathStr, pathz);
+}
+
+/// ditto
+void chdir(R)(auto ref R pathname)
+if (isConvertibleToString!R)
+{
+ return chdir!(StringTypeOf!R)(pathname);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, chdir(TestAliasedString(null))));
+}
+
+/****************************************************
+Make directory $(D pathname).
+
+Throws: $(D FileException) on Posix or $(D WindowsException) on Windows
+ if an error occured.
+ */
+void mkdir(R)(R pathname)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ // Place outside of @trusted block
+ const pathz = pathname.tempCString!FSChar();
+
+ version (Windows)
+ {
+ static auto trustedCreateDirectoryW(const(FSChar)* pathz) @trusted
+ {
+ return CreateDirectoryW(pathz, null);
+ }
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias pathStr = pathname;
+ else
+ string pathStr = null;
+ wenforce(trustedCreateDirectoryW(pathz), pathStr, pathz);
+ }
+ else version (Posix)
+ {
+ import std.conv : octal;
+
+ static auto trustedMkdir(const(FSChar)* pathz, mode_t mode) @trusted
+ {
+ return core.sys.posix.sys.stat.mkdir(pathz, mode);
+ }
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias pathStr = pathname;
+ else
+ string pathStr = null;
+ cenforce(trustedMkdir(pathz, octal!777) == 0, pathStr, pathz);
+ }
+}
+
+/// ditto
+void mkdir(R)(auto ref R pathname)
+if (isConvertibleToString!R)
+{
+ return mkdir!(StringTypeOf!R)(pathname);
+}
+
+@safe unittest
+{
+ import std.path : mkdir;
+ static assert(__traits(compiles, mkdir(TestAliasedString(null))));
+}
+
+// Same as mkdir but ignores "already exists" errors.
+// Returns: "true" if the directory was created,
+// "false" if it already existed.
+private bool ensureDirExists()(in char[] pathname)
+{
+ import std.exception : enforce;
+ const pathz = pathname.tempCString!FSChar();
+
+ version (Windows)
+ {
+ if (() @trusted { return CreateDirectoryW(pathz, null); }())
+ return true;
+ cenforce(GetLastError() == ERROR_ALREADY_EXISTS, pathname.idup);
+ }
+ else version (Posix)
+ {
+ import std.conv : octal;
+
+ if (() @trusted { return core.sys.posix.sys.stat.mkdir(pathz, octal!777); }() == 0)
+ return true;
+ cenforce(errno == EEXIST || errno == EISDIR, pathname);
+ }
+ enforce(pathname.isDir, new FileException(pathname.idup));
+ return false;
+}
+
+/****************************************************
+ * Make directory and all parent directories as needed.
+ *
+ * Does nothing if the directory specified by
+ * $(D pathname) already exists.
+ *
+ * Throws: $(D FileException) on error.
+ */
+
+void mkdirRecurse(in char[] pathname) @safe
+{
+ import std.path : dirName, baseName;
+
+ const left = dirName(pathname);
+ if (left.length != pathname.length && !exists(left))
+ {
+ mkdirRecurse(left);
+ }
+ if (!baseName(pathname).empty)
+ {
+ ensureDirExists(pathname);
+ }
+}
+
+@safe unittest
+{
+ import std.exception : assertThrown;
+ {
+ import std.path : buildPath, buildNormalizedPath;
+
+ immutable basepath = deleteme ~ "_dir";
+ scope(exit) () @trusted { rmdirRecurse(basepath); }();
+
+ auto path = buildPath(basepath, "a", "..", "b");
+ mkdirRecurse(path);
+ path = path.buildNormalizedPath;
+ assert(path.isDir);
+
+ path = buildPath(basepath, "c");
+ write(path, "");
+ assertThrown!FileException(mkdirRecurse(path));
+
+ path = buildPath(basepath, "d");
+ mkdirRecurse(path);
+ mkdirRecurse(path); // should not throw
+ }
+
+ version (Windows)
+ {
+ assertThrown!FileException(mkdirRecurse(`1:\foobar`));
+ }
+
+ // bug3570
+ {
+ immutable basepath = deleteme ~ "_dir";
+ version (Windows)
+ {
+ immutable path = basepath ~ "\\fake\\here\\";
+ }
+ else version (Posix)
+ {
+ immutable path = basepath ~ `/fake/here/`;
+ }
+
+ mkdirRecurse(path);
+ assert(basepath.exists && basepath.isDir);
+ scope(exit) () @trusted { rmdirRecurse(basepath); }();
+ assert(path.exists && path.isDir);
+ }
+}
+
+/****************************************************
+Remove directory $(D pathname).
+
+Params:
+ pathname = Range or string specifying the directory name
+
+Throws: $(D FileException) on error.
+ */
+void rmdir(R)(R pathname)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
+ !isConvertibleToString!R)
+{
+ // Place outside of @trusted block
+ auto pathz = pathname.tempCString!FSChar();
+
+ version (Windows)
+ {
+ static auto trustedRmdir(const(FSChar)* pathz) @trusted
+ {
+ return RemoveDirectoryW(pathz);
+ }
+ }
+ else version (Posix)
+ {
+ static auto trustedRmdir(const(FSChar)* pathz) @trusted
+ {
+ return core.sys.posix.unistd.rmdir(pathz) == 0;
+ }
+ }
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias pathStr = pathname;
+ else
+ string pathStr = null;
+ cenforce(trustedRmdir(pathz), pathStr, pathz);
+}
+
+/// ditto
+void rmdir(R)(auto ref R pathname)
+if (isConvertibleToString!R)
+{
+ rmdir!(StringTypeOf!R)(pathname);
+}
+
+@safe unittest
+{
+ static assert(__traits(compiles, rmdir(TestAliasedString(null))));
+}
+
+/++
+ $(BLUE This function is Posix-Only.)
+
+ Creates a symbolic _link (_symlink).
+
+ Params:
+ original = The file that is being linked. This is the target path that's
+ stored in the _symlink. A relative path is relative to the created
+ _symlink.
+ link = The _symlink to create. A relative path is relative to the
+ current working directory.
+
+ Throws:
+ $(D FileException) on error (which includes if the _symlink already
+ exists).
+ +/
+version (StdDdoc) void symlink(RO, RL)(RO original, RL link)
+if ((isInputRange!RO && !isInfinite!RO && isSomeChar!(ElementEncodingType!RO) ||
+ isConvertibleToString!RO) &&
+ (isInputRange!RL && !isInfinite!RL && isSomeChar!(ElementEncodingType!RL) ||
+ isConvertibleToString!RL));
+else version (Posix) void symlink(RO, RL)(RO original, RL link)
+if ((isInputRange!RO && !isInfinite!RO && isSomeChar!(ElementEncodingType!RO) ||
+ isConvertibleToString!RO) &&
+ (isInputRange!RL && !isInfinite!RL && isSomeChar!(ElementEncodingType!RL) ||
+ isConvertibleToString!RL))
+{
+ static if (isConvertibleToString!RO || isConvertibleToString!RL)
+ {
+ import std.meta : staticMap;
+ alias Types = staticMap!(convertToString, RO, RL);
+ symlink!Types(original, link);
+ }
+ else
+ {
+ import std.conv : text;
+ auto oz = original.tempCString();
+ auto lz = link.tempCString();
+ alias posixSymlink = core.sys.posix.unistd.symlink;
+ immutable int result = () @trusted { return posixSymlink(oz, lz); } ();
+ cenforce(result == 0, text(link));
+ }
+}
+
+version (Posix) @safe unittest
+{
+ if (system_directory.exists)
+ {
+ immutable symfile = deleteme ~ "_slink\0";
+ scope(exit) if (symfile.exists) symfile.remove();
+
+ symlink(system_directory, symfile);
+
+ assert(symfile.exists);
+ assert(symfile.isSymlink);
+ assert(!attrIsSymlink(getAttributes(symfile)));
+ assert(attrIsSymlink(getLinkAttributes(symfile)));
+
+ assert(attrIsDir(getAttributes(symfile)));
+ assert(!attrIsDir(getLinkAttributes(symfile)));
+
+ assert(!attrIsFile(getAttributes(symfile)));
+ assert(!attrIsFile(getLinkAttributes(symfile)));
+ }
+
+ if (system_file.exists)
+ {
+ assert(!system_file.isSymlink);
+
+ immutable symfile = deleteme ~ "_slink\0";
+ scope(exit) if (symfile.exists) symfile.remove();
+
+ symlink(system_file, symfile);
+
+ assert(symfile.exists);
+ assert(symfile.isSymlink);
+ assert(!attrIsSymlink(getAttributes(symfile)));
+ assert(attrIsSymlink(getLinkAttributes(symfile)));
+
+ assert(!attrIsDir(getAttributes(symfile)));
+ assert(!attrIsDir(getLinkAttributes(symfile)));
+
+ assert(attrIsFile(getAttributes(symfile)));
+ assert(!attrIsFile(getLinkAttributes(symfile)));
+ }
+}
+
+version (Posix) @safe unittest
+{
+ static assert(__traits(compiles,
+ symlink(TestAliasedString(null), TestAliasedString(null))));
+}
+
+
+/++
+ $(BLUE This function is Posix-Only.)
+
+ Returns the path to the file pointed to by a symlink. Note that the
+ path could be either relative or absolute depending on the symlink.
+ If the path is relative, it's relative to the symlink, not the current
+ working directory.
+
+ Throws:
+ $(D FileException) on error.
+ +/
+version (StdDdoc) string readLink(R)(R link)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) ||
+ isConvertibleToString!R);
+else version (Posix) string readLink(R)(R link)
+if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) ||
+ isConvertibleToString!R)
+{
+ static if (isConvertibleToString!R)
+ {
+ return readLink!(convertToString!R)(link);
+ }
+ else
+ {
+ import std.conv : to;
+ import std.exception : assumeUnique;
+ alias posixReadlink = core.sys.posix.unistd.readlink;
+ enum bufferLen = 2048;
+ enum maxCodeUnits = 6;
+ char[bufferLen] buffer;
+ const linkz = link.tempCString();
+ auto size = () @trusted {
+ return posixReadlink(linkz, buffer.ptr, buffer.length);
+ } ();
+ cenforce(size != -1, to!string(link));
+
+ if (size <= bufferLen - maxCodeUnits)
+ return to!string(buffer[0 .. size]);
+
+ auto dynamicBuffer = new char[](bufferLen * 3 / 2);
+
+ foreach (i; 0 .. 10)
+ {
+ size = () @trusted {
+ return posixReadlink(linkz, dynamicBuffer.ptr,
+ dynamicBuffer.length);
+ } ();
+ cenforce(size != -1, to!string(link));
+
+ if (size <= dynamicBuffer.length - maxCodeUnits)
+ {
+ dynamicBuffer.length = size;
+ return () @trusted {
+ return assumeUnique(dynamicBuffer);
+ } ();
+ }
+
+ dynamicBuffer.length = dynamicBuffer.length * 3 / 2;
+ }
+
+ throw new FileException(to!string(link), "Path is too long to read.");
+ }
+}
+
+version (Posix) @safe unittest
+{
+ import std.exception : assertThrown;
+ import std.string;
+
+ foreach (file; [system_directory, system_file])
+ {
+ if (file.exists)
+ {
+ immutable symfile = deleteme ~ "_slink\0";
+ scope(exit) if (symfile.exists) symfile.remove();
+
+ symlink(file, symfile);
+ assert(readLink(symfile) == file, format("Failed file: %s", file));
+ }
+ }
+
+ assertThrown!FileException(readLink("/doesnotexist"));
+}
+
+version (Posix) @safe unittest
+{
+ static assert(__traits(compiles, readLink(TestAliasedString("foo"))));
+}
+
+version (Posix) @system unittest // input range of dchars
+{
+ mkdirRecurse(deleteme);
+ scope(exit) if (deleteme.exists) rmdirRecurse(deleteme);
+ write(deleteme ~ "/f", "");
+ import std.range.interfaces : InputRange, inputRangeObject;
+ import std.utf : byChar;
+ immutable string link = deleteme ~ "/l";
+ symlink("f", link);
+ InputRange!dchar linkr = inputRangeObject(link);
+ alias R = typeof(linkr);
+ static assert(isInputRange!R);
+ static assert(!isForwardRange!R);
+ assert(readLink(linkr) == "f");
+}
+
+
+/****************************************************
+ * Get the current working directory.
+ * Throws: $(D FileException) on error.
+ */
+version (Windows) string getcwd()
+{
+ import std.conv : to;
+ /* GetCurrentDirectory's return value:
+ 1. function succeeds: the number of characters that are written to
+ the buffer, not including the terminating null character.
+ 2. function fails: zero
+ 3. the buffer (lpBuffer) is not large enough: the required size of
+ the buffer, in characters, including the null-terminating character.
+ */
+ wchar[4096] buffW = void; //enough for most common case
+ immutable n = cenforce(GetCurrentDirectoryW(to!DWORD(buffW.length), buffW.ptr),
+ "getcwd");
+ // we can do it because toUTFX always produces a fresh string
+ if (n < buffW.length)
+ {
+ return buffW[0 .. n].to!string;
+ }
+ else //staticBuff isn't enough
+ {
+ auto ptr = cast(wchar*) malloc(wchar.sizeof * n);
+ scope(exit) free(ptr);
+ immutable n2 = GetCurrentDirectoryW(n, ptr);
+ cenforce(n2 && n2 < n, "getcwd");
+ return ptr[0 .. n2].to!string;
+ }
+}
+else version (Solaris) string getcwd()
+{
+ /* BUF_SIZE >= PATH_MAX */
+ enum BUF_SIZE = 4096;
+ /* The user should be able to specify any size buffer > 0 */
+ auto p = cenforce(core.sys.posix.unistd.getcwd(null, BUF_SIZE),
+ "cannot get cwd");
+ scope(exit) core.stdc.stdlib.free(p);
+ return p[0 .. core.stdc.string.strlen(p)].idup;
+}
+else version (Posix) string getcwd()
+{
+ auto p = cenforce(core.sys.posix.unistd.getcwd(null, 0),
+ "cannot get cwd");
+ scope(exit) core.stdc.stdlib.free(p);
+ return p[0 .. core.stdc.string.strlen(p)].idup;
+}
+
+@system unittest
+{
+ auto s = getcwd();
+ assert(s.length);
+}
+
+version (OSX)
+ private extern (C) int _NSGetExecutablePath(char* buf, uint* bufsize);
+else version (FreeBSD)
+ private extern (C) int sysctl (const int* name, uint namelen, void* oldp,
+ size_t* oldlenp, const void* newp, size_t newlen);
+else version (NetBSD)
+ private extern (C) int sysctl (const int* name, uint namelen, void* oldp,
+ size_t* oldlenp, const void* newp, size_t newlen);
+
+/**
+ * Returns the full path of the current executable.
+ *
+ * Throws:
+ * $(REF1 Exception, object)
+ */
+@trusted string thisExePath ()
+{
+ version (OSX)
+ {
+ import core.sys.posix.stdlib : realpath;
+ import std.conv : to;
+ import std.exception : errnoEnforce;
+
+ uint size;
+
+ _NSGetExecutablePath(null, &size); // get the length of the path
+ auto buffer = new char[size];
+ _NSGetExecutablePath(buffer.ptr, &size);
+
+ auto absolutePath = realpath(buffer.ptr, null); // let the function allocate
+
+ scope (exit)
+ {
+ if (absolutePath)
+ free(absolutePath);
+ }
+
+ errnoEnforce(absolutePath);
+ return to!(string)(absolutePath);
+ }
+ else version (linux)
+ {
+ return readLink("/proc/self/exe");
+ }
+ else version (Windows)
+ {
+ import std.conv : to;
+ import std.exception : enforce;
+
+ wchar[MAX_PATH] buf;
+ wchar[] buffer = buf[];
+
+ while (true)
+ {
+ auto len = GetModuleFileNameW(null, buffer.ptr, cast(DWORD) buffer.length);
+ enforce(len, sysErrorString(GetLastError()));
+ if (len != buffer.length)
+ return to!(string)(buffer[0 .. len]);
+ buffer.length *= 2;
+ }
+ }
+ else version (FreeBSD)
+ {
+ import std.exception : errnoEnforce, assumeUnique;
+ enum
+ {
+ CTL_KERN = 1,
+ KERN_PROC = 14,
+ KERN_PROC_PATHNAME = 12
+ }
+
+ int[4] mib = [CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1];
+ size_t len;
+
+ auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); // get the length of the path
+ errnoEnforce(result == 0);
+
+ auto buffer = new char[len - 1];
+ result = sysctl(mib.ptr, mib.length, buffer.ptr, &len, null, 0);
+ errnoEnforce(result == 0);
+
+ return buffer.assumeUnique;
+ }
+ else version (NetBSD)
+ {
+ return readLink("/proc/self/exe");
+ }
+ else version (Solaris)
+ {
+ import core.sys.posix.unistd : getpid;
+ import std.string : format;
+
+ // Only Solaris 10 and later
+ return readLink(format("/proc/%d/path/a.out", getpid()));
+ }
+ else
+ static assert(0, "thisExePath is not supported on this platform");
+}
+
+@safe unittest
+{
+ import std.path : isAbsolute;
+ auto path = thisExePath();
+
+ assert(path.exists);
+ assert(path.isAbsolute);
+ assert(path.isFile);
+}
+
+version (StdDdoc)
+{
+ /++
+ Info on a file, similar to what you'd get from stat on a Posix system.
+ +/
+ struct DirEntry
+ {
+ /++
+ Constructs a $(D DirEntry) for the given file (or directory).
+
+ Params:
+ path = The file (or directory) to get a DirEntry for.
+
+ Throws:
+ $(D FileException) if the file does not exist.
+ +/
+ this(string path);
+
+ version (Windows)
+ {
+ private this(string path, in WIN32_FIND_DATAW *fd);
+ }
+ else version (Posix)
+ {
+ private this(string path, core.sys.posix.dirent.dirent* fd);
+ }
+
+ /++
+ Returns the path to the file represented by this $(D DirEntry).
+
+Example:
+--------------------
+auto de1 = DirEntry("/etc/fonts/fonts.conf");
+assert(de1.name == "/etc/fonts/fonts.conf");
+
+auto de2 = DirEntry("/usr/share/include");
+assert(de2.name == "/usr/share/include");
+--------------------
+ +/
+ @property string name() const;
+
+
+ /++
+ Returns whether the file represented by this $(D DirEntry) is a
+ directory.
+
+Example:
+--------------------
+auto de1 = DirEntry("/etc/fonts/fonts.conf");
+assert(!de1.isDir);
+
+auto de2 = DirEntry("/usr/share/include");
+assert(de2.isDir);
+--------------------
+ +/
+ @property bool isDir();
+
+
+ /++
+ Returns whether the file represented by this $(D DirEntry) is a file.
+
+ On Windows, if a file is not a directory, then it's a file. So,
+ either $(D isFile) or $(D isDir) will return $(D true).
+
+ On Posix systems, if $(D isFile) is $(D true), that indicates that
+ the file is a regular file (e.g. not a block not device). So, on
+ Posix systems, it's possible for both $(D isFile) and $(D isDir) to
+ be $(D false) for a particular file (in which case, it's a special
+ file). You can use $(D attributes) or $(D statBuf) to get more
+ information about a special file (see the stat man page for more
+ details).
+
+Example:
+--------------------
+auto de1 = DirEntry("/etc/fonts/fonts.conf");
+assert(de1.isFile);
+
+auto de2 = DirEntry("/usr/share/include");
+assert(!de2.isFile);
+--------------------
+ +/
+ @property bool isFile();
+
+ /++
+ Returns whether the file represented by this $(D DirEntry) is a
+ symbolic link.
+
+ On Windows, return $(D true) when the file is either a symbolic
+ link or a junction point.
+ +/
+ @property bool isSymlink();
+
+ /++
+ Returns the size of the the file represented by this $(D DirEntry)
+ in bytes.
+ +/
+ @property ulong size();
+
+ /++
+ $(BLUE This function is Windows-Only.)
+
+ Returns the creation time of the file represented by this
+ $(D DirEntry).
+ +/
+ @property SysTime timeCreated() const;
+
+ /++
+ Returns the time that the file represented by this $(D DirEntry) was
+ last accessed.
+
+ Note that many file systems do not update the access time for files
+ (generally for performance reasons), so there's a good chance that
+ $(D timeLastAccessed) will return the same value as
+ $(D timeLastModified).
+ +/
+ @property SysTime timeLastAccessed();
+
+ /++
+ Returns the time that the file represented by this $(D DirEntry) was
+ last modified.
+ +/
+ @property SysTime timeLastModified();
+
+ /++
+ Returns the _attributes of the file represented by this $(D DirEntry).
+
+ Note that the file _attributes on Windows and Posix systems are
+ completely different. On, Windows, they're what is returned by
+ $(D GetFileAttributes)
+ $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx, GetFileAttributes)
+ Whereas, an Posix systems, they're the $(D st_mode) value which is
+ part of the $(D stat) struct gotten by calling $(D stat).
+
+ On Posix systems, if the file represented by this $(D DirEntry) is a
+ symbolic link, then _attributes are the _attributes of the file
+ pointed to by the symbolic link.
+ +/
+ @property uint attributes();
+
+ /++
+ On Posix systems, if the file represented by this $(D DirEntry) is a
+ symbolic link, then $(D linkAttributes) are the attributes of the
+ symbolic link itself. Otherwise, $(D linkAttributes) is identical to
+ $(D attributes).
+
+ On Windows, $(D linkAttributes) is identical to $(D attributes). It
+ exists on Windows so that you don't have to special-case code for
+ Windows when dealing with symbolic links.
+ +/
+ @property uint linkAttributes();
+
+ version (Windows)
+ alias stat_t = void*;
+
+ /++
+ $(BLUE This function is Posix-Only.)
+
+ The $(D stat) struct gotten from calling $(D stat).
+ +/
+ @property stat_t statBuf();
+ }
+}
+else version (Windows)
+{
+ struct DirEntry
+ {
+ public:
+ alias name this;
+
+ this(string path)
+ {
+ import std.datetime.systime : FILETIMEToSysTime;
+
+ if (!path.exists())
+ throw new FileException(path, "File does not exist");
+
+ _name = path;
+
+ with (getFileAttributesWin(path))
+ {
+ _size = makeUlong(nFileSizeLow, nFileSizeHigh);
+ _timeCreated = FILETIMEToSysTime(&ftCreationTime);
+ _timeLastAccessed = FILETIMEToSysTime(&ftLastAccessTime);
+ _timeLastModified = FILETIMEToSysTime(&ftLastWriteTime);
+ _attributes = dwFileAttributes;
+ }
+ }
+
+ private this(string path, in WIN32_FIND_DATAW *fd)
+ {
+ import core.stdc.wchar_ : wcslen;
+ import std.conv : to;
+ import std.datetime.systime : FILETIMEToSysTime;
+ import std.path : buildPath;
+
+ size_t clength = wcslen(fd.cFileName.ptr);
+ _name = buildPath(path, fd.cFileName[0 .. clength].to!string);
+ _size = (cast(ulong) fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
+ _timeCreated = FILETIMEToSysTime(&fd.ftCreationTime);
+ _timeLastAccessed = FILETIMEToSysTime(&fd.ftLastAccessTime);
+ _timeLastModified = FILETIMEToSysTime(&fd.ftLastWriteTime);
+ _attributes = fd.dwFileAttributes;
+ }
+
+ @property string name() const pure nothrow
+ {
+ return _name;
+ }
+
+ @property bool isDir() const pure nothrow
+ {
+ return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+ }
+
+ @property bool isFile() const pure nothrow
+ {
+ //Are there no options in Windows other than directory and file?
+ //If there are, then this probably isn't the best way to determine
+ //whether this DirEntry is a file or not.
+ return !isDir;
+ }
+
+ @property bool isSymlink() const pure nothrow
+ {
+ return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+ }
+
+ @property ulong size() const pure nothrow
+ {
+ return _size;
+ }
+
+ @property SysTime timeCreated() const pure nothrow
+ {
+ return cast(SysTime)_timeCreated;
+ }
+
+ @property SysTime timeLastAccessed() const pure nothrow
+ {
+ return cast(SysTime)_timeLastAccessed;
+ }
+
+ @property SysTime timeLastModified() const pure nothrow
+ {
+ return cast(SysTime)_timeLastModified;
+ }
+
+ @property uint attributes() const pure nothrow
+ {
+ return _attributes;
+ }
+
+ @property uint linkAttributes() const pure nothrow
+ {
+ return _attributes;
+ }
+
+ private:
+ string _name; /// The file or directory represented by this DirEntry.
+
+ SysTime _timeCreated; /// The time when the file was created.
+ SysTime _timeLastAccessed; /// The time when the file was last accessed.
+ SysTime _timeLastModified; /// The time when the file was last modified.
+
+ ulong _size; /// The size of the file in bytes.
+ uint _attributes; /// The file attributes from WIN32_FIND_DATAW.
+ }
+}
+else version (Posix)
+{
+ struct DirEntry
+ {
+ public:
+ alias name this;
+
+ this(string path)
+ {
+ if (!path.exists)
+ throw new FileException(path, "File does not exist");
+
+ _name = path;
+
+ _didLStat = false;
+ _didStat = false;
+ _dTypeSet = false;
+ }
+
+ private this(string path, core.sys.posix.dirent.dirent* fd)
+ {
+ import std.path : buildPath;
+
+ immutable len = core.stdc.string.strlen(fd.d_name.ptr);
+ _name = buildPath(path, fd.d_name[0 .. len]);
+
+ _didLStat = false;
+ _didStat = false;
+
+ //fd_d_type doesn't work for all file systems,
+ //in which case the result is DT_UNKOWN. But we
+ //can determine the correct type from lstat, so
+ //we'll only set the dtype here if we could
+ //correctly determine it (not lstat in the case
+ //of DT_UNKNOWN in case we don't ever actually
+ //need the dtype, thus potentially avoiding the
+ //cost of calling lstat).
+ static if (__traits(compiles, fd.d_type != DT_UNKNOWN))
+ {
+ if (fd.d_type != DT_UNKNOWN)
+ {
+ _dType = fd.d_type;
+ _dTypeSet = true;
+ }
+ else
+ _dTypeSet = false;
+ }
+ else
+ {
+ // e.g. Solaris does not have the d_type member
+ _dTypeSet = false;
+ }
+ }
+
+ @property string name() const pure nothrow
+ {
+ return _name;
+ }
+
+ @property bool isDir()
+ {
+ _ensureStatOrLStatDone();
+
+ return (_statBuf.st_mode & S_IFMT) == S_IFDIR;
+ }
+
+ @property bool isFile()
+ {
+ _ensureStatOrLStatDone();
+
+ return (_statBuf.st_mode & S_IFMT) == S_IFREG;
+ }
+
+ @property bool isSymlink()
+ {
+ _ensureLStatDone();
+
+ return (_lstatMode & S_IFMT) == S_IFLNK;
+ }
+
+ @property ulong size()
+ {
+ _ensureStatDone();
+ return _statBuf.st_size;
+ }
+
+ @property SysTime timeStatusChanged()
+ {
+ _ensureStatDone();
+
+ return statTimeToStdTime!'c'(_statBuf);
+ }
+
+ @property SysTime timeLastAccessed()
+ {
+ _ensureStatDone();
+
+ return statTimeToStdTime!'a'(_statBuf);
+ }
+
+ @property SysTime timeLastModified()
+ {
+ _ensureStatDone();
+
+ return statTimeToStdTime!'m'(_statBuf);
+ }
+
+ @property uint attributes()
+ {
+ _ensureStatDone();
+
+ return _statBuf.st_mode;
+ }
+
+ @property uint linkAttributes()
+ {
+ _ensureLStatDone();
+
+ return _lstatMode;
+ }
+
+ @property stat_t statBuf()
+ {
+ _ensureStatDone();
+
+ return _statBuf;
+ }
+
+ private:
+ /++
+ This is to support lazy evaluation, because doing stat's is
+ expensive and not always needed.
+ +/
+ void _ensureStatDone() @safe
+ {
+ import std.exception : enforce;
+
+ static auto trustedStat(in char[] path, stat_t* buf) @trusted
+ {
+ return stat(path.tempCString(), buf);
+ }
+ if (_didStat)
+ return;
+
+ enforce(trustedStat(_name, &_statBuf) == 0,
+ "Failed to stat file `" ~ _name ~ "'");
+
+ _didStat = true;
+ }
+
+ /++
+ This is to support lazy evaluation, because doing stat's is
+ expensive and not always needed.
+
+ Try both stat and lstat for isFile and isDir
+ to detect broken symlinks.
+ +/
+ void _ensureStatOrLStatDone()
+ {
+ if (_didStat)
+ return;
+
+ if ( stat(_name.tempCString(), &_statBuf) != 0 )
+ {
+ _ensureLStatDone();
+
+ _statBuf = stat_t.init;
+ _statBuf.st_mode = S_IFLNK;
+ }
+ else
+ {
+ _didStat = true;
+ }
+ }
+
+ /++
+ This is to support lazy evaluation, because doing stat's is
+ expensive and not always needed.
+ +/
+ void _ensureLStatDone()
+ {
+ import std.exception : enforce;
+
+ if (_didLStat)
+ return;
+
+ stat_t statbuf = void;
+
+ enforce(lstat(_name.tempCString(), &statbuf) == 0,
+ "Failed to stat file `" ~ _name ~ "'");
+
+ _lstatMode = statbuf.st_mode;
+
+ _dTypeSet = true;
+ _didLStat = true;
+ }
+
+ string _name; /// The file or directory represented by this DirEntry.
+
+ stat_t _statBuf = void; /// The result of stat().
+ uint _lstatMode; /// The stat mode from lstat().
+ ubyte _dType; /// The type of the file.
+
+ bool _didLStat = false; /// Whether lstat() has been called for this DirEntry.
+ bool _didStat = false; /// Whether stat() has been called for this DirEntry.
+ bool _dTypeSet = false; /// Whether the dType of the file has been set.
+ }
+}
+
+@system unittest
+{
+ version (Windows)
+ {
+ if ("C:\\Program Files\\".exists)
+ {
+ auto de = DirEntry("C:\\Program Files\\");
+ assert(!de.isFile);
+ assert(de.isDir);
+ assert(!de.isSymlink);
+ }
+
+ if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
+ {
+ auto de = DirEntry("C:\\Documents and Settings\\");
+ assert(de.isSymlink);
+ }
+
+ if ("C:\\Windows\\system.ini".exists)
+ {
+ auto de = DirEntry("C:\\Windows\\system.ini");
+ assert(de.isFile);
+ assert(!de.isDir);
+ assert(!de.isSymlink);
+ }
+ }
+ else version (Posix)
+ {
+ import std.exception : assertThrown;
+
+ if (system_directory.exists)
+ {
+ {
+ auto de = DirEntry(system_directory);
+ assert(!de.isFile);
+ assert(de.isDir);
+ assert(!de.isSymlink);
+ }
+
+ immutable symfile = deleteme ~ "_slink\0";
+ scope(exit) if (symfile.exists) symfile.remove();
+
+ core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
+
+ {
+ auto de = DirEntry(symfile);
+ assert(!de.isFile);
+ assert(de.isDir);
+ assert(de.isSymlink);
+ }
+
+ symfile.remove();
+ core.sys.posix.unistd.symlink((deleteme ~ "_broken_symlink\0").ptr, symfile.ptr);
+
+ {
+ //Issue 8298
+ DirEntry de = DirEntry(symfile);
+
+ assert(!de.isFile);
+ assert(!de.isDir);
+ assert(de.isSymlink);
+ assertThrown(de.size);
+ assertThrown(de.timeStatusChanged);
+ assertThrown(de.timeLastAccessed);
+ assertThrown(de.timeLastModified);
+ assertThrown(de.attributes);
+ assertThrown(de.statBuf);
+ assert(symfile.exists);
+ symfile.remove();
+ }
+ }
+
+ if (system_file.exists)
+ {
+ auto de = DirEntry(system_file);
+ assert(de.isFile);
+ assert(!de.isDir);
+ assert(!de.isSymlink);
+ }
+ }
+}
+
+alias PreserveAttributes = Flag!"preserveAttributes";
+
+version (StdDdoc)
+{
+ /// Defaults to $(D Yes.preserveAttributes) on Windows, and the opposite on all other platforms.
+ PreserveAttributes preserveAttributesDefault;
+}
+else version (Windows)
+{
+ enum preserveAttributesDefault = Yes.preserveAttributes;
+}
+else
+{
+ enum preserveAttributesDefault = No.preserveAttributes;
+}
+
+/***************************************************
+Copy file $(D from) _to file $(D to). File timestamps are preserved.
+File attributes are preserved, if $(D preserve) equals $(D Yes.preserveAttributes).
+On Windows only $(D Yes.preserveAttributes) (the default on Windows) is supported.
+If the target file exists, it is overwritten.
+
+Params:
+ from = string or range of characters representing the existing file name
+ to = string or range of characters representing the target file name
+ preserve = whether to _preserve the file attributes
+
+Throws: $(D FileException) on error.
+ */
+void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault)
+if (isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) && !isConvertibleToString!RF &&
+ isInputRange!RT && !isInfinite!RT && isSomeChar!(ElementEncodingType!RT) && !isConvertibleToString!RT)
+{
+ // Place outside of @trusted block
+ auto fromz = from.tempCString!FSChar();
+ auto toz = to.tempCString!FSChar();
+
+ static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char))
+ alias f = from;
+ else
+ enum string f = null;
+
+ static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char))
+ alias t = to;
+ else
+ enum string t = null;
+
+ copyImpl(f, t, fromz, toz, preserve);
+}
+
+/// ditto
+void copy(RF, RT)(auto ref RF from, auto ref RT to, PreserveAttributes preserve = preserveAttributesDefault)
+if (isConvertibleToString!RF || isConvertibleToString!RT)
+{
+ import std.meta : staticMap;
+ alias Types = staticMap!(convertToString, RF, RT);
+ copy!Types(from, to, preserve);
+}
+
+@safe unittest // issue 15319
+{
+ assert(__traits(compiles, copy("from.txt", "to.txt")));
+}
+
+private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz,
+ PreserveAttributes preserve) @trusted
+{
+ version (Windows)
+ {
+ assert(preserve == Yes.preserveAttributes);
+ immutable result = CopyFileW(fromz, toz, false);
+ if (!result)
+ {
+ import core.stdc.wchar_ : wcslen;
+ import std.conv : to;
+
+ if (!t)
+ t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
+
+ throw new FileException(t);
+ }
+ }
+ else version (Posix)
+ {
+ static import core.stdc.stdio;
+ import std.conv : to, octal;
+
+ immutable fdr = core.sys.posix.fcntl.open(fromz, O_RDONLY);
+ cenforce(fdr != -1, f, fromz);
+ scope(exit) core.sys.posix.unistd.close(fdr);
+
+ stat_t statbufr = void;
+ cenforce(fstat(fdr, &statbufr) == 0, f, fromz);
+ //cenforce(core.sys.posix.sys.stat.fstat(fdr, &statbufr) == 0, f, fromz);
+
+ immutable fdw = core.sys.posix.fcntl.open(toz,
+ O_CREAT | O_WRONLY, octal!666);
+ cenforce(fdw != -1, t, toz);
+ {
+ scope(failure) core.sys.posix.unistd.close(fdw);
+
+ stat_t statbufw = void;
+ cenforce(fstat(fdw, &statbufw) == 0, t, toz);
+ if (statbufr.st_dev == statbufw.st_dev && statbufr.st_ino == statbufw.st_ino)
+ throw new FileException(t, "Source and destination are the same file");
+ }
+
+ scope(failure) core.stdc.stdio.remove(toz);
+ {
+ scope(failure) core.sys.posix.unistd.close(fdw);
+ cenforce(ftruncate(fdw, 0) == 0, t, toz);
+
+ auto BUFSIZ = 4096u * 16;
+ auto buf = core.stdc.stdlib.malloc(BUFSIZ);
+ if (!buf)
+ {
+ BUFSIZ = 4096;
+ buf = core.stdc.stdlib.malloc(BUFSIZ);
+ if (!buf)
+ {
+ import core.exception : onOutOfMemoryError;
+ onOutOfMemoryError();
+ }
+ }
+ scope(exit) core.stdc.stdlib.free(buf);
+
+ for (auto size = statbufr.st_size; size; )
+ {
+ immutable toxfer = (size > BUFSIZ) ? BUFSIZ : cast(size_t) size;
+ cenforce(
+ core.sys.posix.unistd.read(fdr, buf, toxfer) == toxfer
+ && core.sys.posix.unistd.write(fdw, buf, toxfer) == toxfer,
+ f, fromz);
+ assert(size >= toxfer);
+ size -= toxfer;
+ }
+ if (preserve)
+ cenforce(fchmod(fdw, to!mode_t(statbufr.st_mode)) == 0, f, fromz);
+ }
+
+ cenforce(core.sys.posix.unistd.close(fdw) != -1, f, fromz);
+
+ utimbuf utim = void;
+ utim.actime = cast(time_t) statbufr.st_atime;
+ utim.modtime = cast(time_t) statbufr.st_mtime;
+
+ cenforce(utime(toz, &utim) != -1, f, fromz);
+ }
+}
+
+@safe unittest
+{
+ import std.algorithm, std.file; // issue 14817
+ auto t1 = deleteme, t2 = deleteme~"2";
+ scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
+ write(t1, "11");
+ copy(t1, t2);
+ assert(readText(t2) == "11");
+ write(t1, "2");
+ copy(t1, t2);
+ assert(readText(t2) == "2");
+
+ import std.utf : byChar;
+ copy(t1.byChar, t2.byChar);
+ assert(readText(t2.byChar) == "2");
+}
+
+@safe version (Posix) @safe unittest //issue 11434
+{
+ import std.conv : octal;
+ auto t1 = deleteme, t2 = deleteme~"2";
+ scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
+ write(t1, "1");
+ setAttributes(t1, octal!767);
+ copy(t1, t2, Yes.preserveAttributes);
+ assert(readText(t2) == "1");
+ assert(getAttributes(t2) == octal!100767);
+}
+
+@safe unittest // issue 15865
+{
+ import std.exception : assertThrown;
+ auto t = deleteme;
+ write(t, "a");
+ scope(exit) t.remove();
+ assertThrown!FileException(copy(t, t));
+ assert(readText(t) == "a");
+}
+
+/++
+ Remove directory and all of its content and subdirectories,
+ recursively.
+
+ Throws:
+ $(D FileException) if there is an error (including if the given
+ file is not a directory).
+ +/
+void rmdirRecurse(in char[] pathname)
+{
+ //No references to pathname will be kept after rmdirRecurse,
+ //so the cast is safe
+ rmdirRecurse(DirEntry(cast(string) pathname));
+}
+
+/++
+ Remove directory and all of its content and subdirectories,
+ recursively.
+
+ Throws:
+ $(D FileException) if there is an error (including if the given
+ file is not a directory).
+ +/
+void rmdirRecurse(ref DirEntry de)
+{
+ if (!de.isDir)
+ throw new FileException(de.name, "Not a directory");
+
+ if (de.isSymlink)
+ {
+ version (Windows)
+ rmdir(de.name);
+ else
+ remove(de.name);
+ }
+ else
+ {
+ // all children, recursively depth-first
+ foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false))
+ {
+ attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name);
+ }
+
+ // the dir itself
+ rmdir(de.name);
+ }
+}
+///ditto
+//Note, without this overload, passing an RValue DirEntry still works, but
+//actually fully reconstructs a DirEntry inside the
+//"rmdirRecurse(in char[] pathname)" implementation. That is needlessly
+//expensive.
+//A DirEntry is a bit big (72B), so keeping the "by ref" signature is desirable.
+void rmdirRecurse(DirEntry de)
+{
+ rmdirRecurse(de);
+}
+
+version (Windows) @system unittest
+{
+ import std.exception : enforce;
+ auto d = deleteme ~ r".dir\a\b\c\d\e\f\g";
+ mkdirRecurse(d);
+ rmdirRecurse(deleteme ~ ".dir");
+ enforce(!exists(deleteme ~ ".dir"));
+}
+
+version (Posix) @system unittest
+{
+ import std.exception : enforce, collectException;
+ import std.process : executeShell;
+ collectException(rmdirRecurse(deleteme));
+ auto d = deleteme~"/a/b/c/d/e/f/g";
+ enforce(collectException(mkdir(d)));
+ mkdirRecurse(d);
+ core.sys.posix.unistd.symlink((deleteme~"/a/b/c\0").ptr,
+ (deleteme~"/link\0").ptr);
+ rmdirRecurse(deleteme~"/link");
+ enforce(exists(d));
+ rmdirRecurse(deleteme);
+ enforce(!exists(deleteme));
+
+ d = deleteme~"/a/b/c/d/e/f/g";
+ mkdirRecurse(d);
+ version (Android) string link_cmd = "ln -s ";
+ else string link_cmd = "ln -sf ";
+ executeShell(link_cmd~deleteme~"/a/b/c "~deleteme~"/link");
+ rmdirRecurse(deleteme);
+ enforce(!exists(deleteme));
+}
+
+@system unittest
+{
+ void[] buf;
+
+ buf = new void[10];
+ (cast(byte[]) buf)[] = 3;
+ string unit_file = deleteme ~ "-unittest_write.tmp";
+ if (exists(unit_file)) remove(unit_file);
+ write(unit_file, buf);
+ void[] buf2 = read(unit_file);
+ assert(buf == buf2);
+
+ string unit2_file = deleteme ~ "-unittest_write2.tmp";
+ copy(unit_file, unit2_file);
+ buf2 = read(unit2_file);
+ assert(buf == buf2);
+
+ remove(unit_file);
+ assert(!exists(unit_file));
+ remove(unit2_file);
+ assert(!exists(unit2_file));
+}
+
+/**
+ * Dictates directory spanning policy for $(D_PARAM dirEntries) (see below).
+ */
+enum SpanMode
+{
+ /** Only spans one directory. */
+ shallow,
+ /** Spans the directory in
+ $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Post-order,
+ _depth-first $(B post)-order), i.e. the content of any
+ subdirectory is spanned before that subdirectory itself. Useful
+ e.g. when recursively deleting files. */
+ depth,
+ /** Spans the directory in
+ $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Pre-order, depth-first
+ $(B pre)-order), i.e. the content of any subdirectory is spanned
+ right after that subdirectory itself.
+
+ Note that $(D SpanMode.breadth) will not result in all directory
+ members occurring before any subdirectory members, i.e. it is not
+ _true
+ $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Breadth-first_search,
+ _breadth-first traversal).
+ */
+ breadth,
+}
+
+private struct DirIteratorImpl
+{
+ import std.array : Appender, appender;
+ SpanMode _mode;
+ // Whether we should follow symlinked directories while iterating.
+ // It also indicates whether we should avoid functions which call
+ // stat (since we should only need lstat in this case and it would
+ // be more efficient to not call stat in addition to lstat).
+ bool _followSymlink;
+ DirEntry _cur;
+ Appender!(DirHandle[]) _stack;
+ Appender!(DirEntry[]) _stashed; //used in depth first mode
+ //stack helpers
+ void pushExtra(DirEntry de){ _stashed.put(de); }
+ //ditto
+ bool hasExtra(){ return !_stashed.data.empty; }
+ //ditto
+ DirEntry popExtra()
+ {
+ DirEntry de;
+ de = _stashed.data[$-1];
+ _stashed.shrinkTo(_stashed.data.length - 1);
+ return de;
+
+ }
+ version (Windows)
+ {
+ struct DirHandle
+ {
+ string dirpath;
+ HANDLE h;
+ }
+
+ bool stepIn(string directory)
+ {
+ import std.path : chainPath;
+
+ auto search_pattern = chainPath(directory, "*.*");
+ WIN32_FIND_DATAW findinfo;
+ HANDLE h = FindFirstFileW(search_pattern.tempCString!FSChar(), &findinfo);
+ cenforce(h != INVALID_HANDLE_VALUE, directory);
+ _stack.put(DirHandle(directory, h));
+ return toNext(false, &findinfo);
+ }
+
+ bool next()
+ {
+ if (_stack.data.empty)
+ return false;
+ WIN32_FIND_DATAW findinfo;
+ return toNext(true, &findinfo);
+ }
+
+ bool toNext(bool fetch, WIN32_FIND_DATAW* findinfo)
+ {
+ import core.stdc.wchar_ : wcscmp;
+
+ if (fetch)
+ {
+ if (FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE)
+ {
+ popDirStack();
+ return false;
+ }
+ }
+ while ( wcscmp(findinfo.cFileName.ptr, ".") == 0
+ || wcscmp(findinfo.cFileName.ptr, "..") == 0)
+ if (FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE)
+ {
+ popDirStack();
+ return false;
+ }
+ _cur = DirEntry(_stack.data[$-1].dirpath, findinfo);
+ return true;
+ }
+
+ void popDirStack()
+ {
+ assert(!_stack.data.empty);
+ FindClose(_stack.data[$-1].h);
+ _stack.shrinkTo(_stack.data.length-1);
+ }
+
+ void releaseDirStack()
+ {
+ foreach ( d; _stack.data)
+ FindClose(d.h);
+ }
+
+ bool mayStepIn()
+ {
+ return _followSymlink ? _cur.isDir : _cur.isDir && !_cur.isSymlink;
+ }
+ }
+ else version (Posix)
+ {
+ struct DirHandle
+ {
+ string dirpath;
+ DIR* h;
+ }
+
+ bool stepIn(string directory)
+ {
+ auto h = directory.length ? opendir(directory.tempCString()) : opendir(".");
+ cenforce(h, directory);
+ _stack.put(DirHandle(directory, h));
+ return next();
+ }
+
+ bool next()
+ {
+ if (_stack.data.empty)
+ return false;
+ for (dirent* fdata; (fdata = readdir(_stack.data[$-1].h)) != null; )
+ {
+ // Skip "." and ".."
+ if (core.stdc.string.strcmp(fdata.d_name.ptr, ".") &&
+ core.stdc.string.strcmp(fdata.d_name.ptr, "..") )
+ {
+ _cur = DirEntry(_stack.data[$-1].dirpath, fdata);
+ return true;
+ }
+ }
+ popDirStack();
+ return false;
+ }
+
+ void popDirStack()
+ {
+ assert(!_stack.data.empty);
+ closedir(_stack.data[$-1].h);
+ _stack.shrinkTo(_stack.data.length-1);
+ }
+
+ void releaseDirStack()
+ {
+ foreach ( d; _stack.data)
+ closedir(d.h);
+ }
+
+ bool mayStepIn()
+ {
+ return _followSymlink ? _cur.isDir : attrIsDir(_cur.linkAttributes);
+ }
+ }
+
+ this(R)(R pathname, SpanMode mode, bool followSymlink)
+ if (isInputRange!R && isSomeChar!(ElementEncodingType!R))
+ {
+ _mode = mode;
+ _followSymlink = followSymlink;
+ _stack = appender(cast(DirHandle[])[]);
+ if (_mode == SpanMode.depth)
+ _stashed = appender(cast(DirEntry[])[]);
+
+ static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ alias pathnameStr = pathname;
+ else
+ {
+ import std.array : array;
+ string pathnameStr = pathname.array;
+ }
+ if (stepIn(pathnameStr))
+ {
+ if (_mode == SpanMode.depth)
+ while (mayStepIn())
+ {
+ auto thisDir = _cur;
+ if (stepIn(_cur.name))
+ {
+ pushExtra(thisDir);
+ }
+ else
+ break;
+ }
+ }
+ }
+ @property bool empty(){ return _stashed.data.empty && _stack.data.empty; }
+ @property DirEntry front(){ return _cur; }
+ void popFront()
+ {
+ switch (_mode)
+ {
+ case SpanMode.depth:
+ if (next())
+ {
+ while (mayStepIn())
+ {
+ auto thisDir = _cur;
+ if (stepIn(_cur.name))
+ {
+ pushExtra(thisDir);
+ }
+ else
+ break;
+ }
+ }
+ else if (hasExtra())
+ _cur = popExtra();
+ break;
+ case SpanMode.breadth:
+ if (mayStepIn())
+ {
+ if (!stepIn(_cur.name))
+ while (!empty && !next()){}
+ }
+ else
+ while (!empty && !next()){}
+ break;
+ default:
+ next();
+ }
+ }
+
+ ~this()
+ {
+ releaseDirStack();
+ }
+}
+
+struct DirIterator
+{
+private:
+ RefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl;
+ this(string pathname, SpanMode mode, bool followSymlink)
+ {
+ impl = typeof(impl)(pathname, mode, followSymlink);
+ }
+public:
+ @property bool empty(){ return impl.empty; }
+ @property DirEntry front(){ return impl.front; }
+ void popFront(){ impl.popFront(); }
+
+}
+/++
+ Returns an input range of $(D DirEntry) that lazily iterates a given directory,
+ also provides two ways of foreach iteration. The iteration variable can be of
+ type $(D string) if only the name is needed, or $(D DirEntry)
+ if additional details are needed. The span _mode dictates how the
+ directory is traversed. The name of each iterated directory entry
+ contains the absolute _path.
+
+ Params:
+ path = The directory to iterate over.
+ If empty, the current directory will be iterated.
+
+ pattern = Optional string with wildcards, such as $(RED
+ "*.d"). When present, it is used to filter the
+ results by their file name. The supported wildcard
+ strings are described under $(REF globMatch,
+ std,_path).
+
+ mode = Whether the directory's sub-directories should be
+ iterated in depth-first port-order ($(LREF depth)),
+ depth-first pre-order ($(LREF breadth)), or not at all
+ ($(LREF shallow)).
+
+ followSymlink = Whether symbolic links which point to directories
+ should be treated as directories and their contents
+ iterated over.
+
+ Throws:
+ $(D FileException) if the directory does not exist.
+
+Example:
+--------------------
+// Iterate a directory in depth
+foreach (string name; dirEntries("destroy/me", SpanMode.depth))
+{
+ remove(name);
+}
+
+// Iterate the current directory in breadth
+foreach (string name; dirEntries("", SpanMode.breadth))
+{
+ writeln(name);
+}
+
+// Iterate a directory and get detailed info about it
+foreach (DirEntry e; dirEntries("dmd-testing", SpanMode.breadth))
+{
+ writeln(e.name, "\t", e.size);
+}
+
+// Iterate over all *.d files in current directory and all its subdirectories
+auto dFiles = dirEntries("", SpanMode.depth).filter!(f => f.name.endsWith(".d"));
+foreach (d; dFiles)
+ writeln(d.name);
+
+// Hook it up with std.parallelism to compile them all in parallel:
+foreach (d; parallel(dFiles, 1)) //passes by 1 file to each thread
+{
+ string cmd = "dmd -c " ~ d.name;
+ writeln(cmd);
+ std.process.system(cmd);
+}
+
+// Iterate over all D source files in current directory and all its
+// subdirectories
+auto dFiles = dirEntries("","*.{d,di}",SpanMode.depth);
+foreach (d; dFiles)
+ writeln(d.name);
+--------------------
+ +/
+auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
+{
+ return DirIterator(path, mode, followSymlink);
+}
+
+/// Duplicate functionality of D1's $(D std.file.listdir()):
+@safe unittest
+{
+ string[] listdir(string pathname)
+ {
+ import std.algorithm;
+ import std.array;
+ import std.file;
+ import std.path;
+
+ return std.file.dirEntries(pathname, SpanMode.shallow)
+ .filter!(a => a.isFile)
+ .map!(a => std.path.baseName(a.name))
+ .array;
+ }
+
+ void main(string[] args)
+ {
+ import std.stdio;
+
+ string[] files = listdir(args[1]);
+ writefln("%s", files);
+ }
+}
+
+@system unittest
+{
+ import std.algorithm.comparison : equal;
+ import std.algorithm.iteration : map;
+ import std.algorithm.searching : startsWith;
+ import std.array : array;
+ import std.conv : to;
+ import std.path : dirEntries, buildPath, absolutePath;
+ import std.process : thisProcessID;
+ import std.range.primitives : walkLength;
+
+ version (Android)
+ string testdir = deleteme; // This has to be an absolute path when
+ // called from a shared library on Android,
+ // ie an apk
+ else
+ string testdir = "deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID); // needs to be relative
+ mkdirRecurse(buildPath(testdir, "somedir"));
+ scope(exit) rmdirRecurse(testdir);
+ write(buildPath(testdir, "somefile"), null);
+ write(buildPath(testdir, "somedir", "somedeepfile"), null);
+
+ // testing range interface
+ size_t equalEntries(string relpath, SpanMode mode)
+ {
+ import std.exception : enforce;
+ auto len = enforce(walkLength(dirEntries(absolutePath(relpath), mode)));
+ assert(walkLength(dirEntries(relpath, mode)) == len);
+ assert(equal(
+ map!(a => absolutePath(a.name))(dirEntries(relpath, mode)),
+ map!(a => a.name)(dirEntries(absolutePath(relpath), mode))));
+ return len;
+ }
+
+ assert(equalEntries(testdir, SpanMode.shallow) == 2);
+ assert(equalEntries(testdir, SpanMode.depth) == 3);
+ assert(equalEntries(testdir, SpanMode.breadth) == 3);
+
+ // testing opApply
+ foreach (string name; dirEntries(testdir, SpanMode.breadth))
+ {
+ //writeln(name);
+ assert(name.startsWith(testdir));
+ }
+ foreach (DirEntry e; dirEntries(absolutePath(testdir), SpanMode.breadth))
+ {
+ //writeln(name);
+ assert(e.isFile || e.isDir, e.name);
+ }
+
+ //issue 7264
+ foreach (string name; dirEntries(testdir, "*.d", SpanMode.breadth))
+ {
+
+ }
+ foreach (entry; dirEntries(testdir, SpanMode.breadth))
+ {
+ static assert(is(typeof(entry) == DirEntry));
+ }
+ //issue 7138
+ auto a = array(dirEntries(testdir, SpanMode.shallow));
+
+ // issue 11392
+ auto dFiles = dirEntries(testdir, SpanMode.shallow);
+ foreach (d; dFiles){}
+
+ // issue 15146
+ dirEntries("", SpanMode.shallow).walkLength();
+}
+
+/// Ditto
+auto dirEntries(string path, string pattern, SpanMode mode,
+ bool followSymlink = true)
+{
+ import std.algorithm.iteration : filter;
+ import std.path : globMatch, baseName;
+
+ bool f(DirEntry de) { return globMatch(baseName(de.name), pattern); }
+ return filter!f(DirIterator(path, mode, followSymlink));
+}
+
+@system unittest
+{
+ import std.stdio : writefln;
+ immutable dpath = deleteme ~ "_dir";
+ immutable fpath = deleteme ~ "_file";
+ immutable sdpath = deleteme ~ "_sdir";
+ immutable sfpath = deleteme ~ "_sfile";
+ scope(exit)
+ {
+ if (dpath.exists) rmdirRecurse(dpath);
+ if (fpath.exists) remove(fpath);
+ if (sdpath.exists) remove(sdpath);
+ if (sfpath.exists) remove(sfpath);
+ }
+
+ mkdir(dpath);
+ write(fpath, "hello world");
+ version (Posix)
+ {
+ core.sys.posix.unistd.symlink((dpath ~ '\0').ptr, (sdpath ~ '\0').ptr);
+ core.sys.posix.unistd.symlink((fpath ~ '\0').ptr, (sfpath ~ '\0').ptr);
+ }
+
+ static struct Flags { bool dir, file, link; }
+ auto tests = [dpath : Flags(true), fpath : Flags(false, true)];
+ version (Posix)
+ {
+ tests[sdpath] = Flags(true, false, true);
+ tests[sfpath] = Flags(false, true, true);
+ }
+
+ auto past = Clock.currTime() - 2.seconds;
+ auto future = past + 4.seconds;
+
+ foreach (path, flags; tests)
+ {
+ auto de = DirEntry(path);
+ assert(de.name == path);
+ assert(de.isDir == flags.dir);
+ assert(de.isFile == flags.file);
+ assert(de.isSymlink == flags.link);
+
+ assert(de.isDir == path.isDir);
+ assert(de.isFile == path.isFile);
+ assert(de.isSymlink == path.isSymlink);
+ assert(de.size == path.getSize());
+ assert(de.attributes == getAttributes(path));
+ assert(de.linkAttributes == getLinkAttributes(path));
+
+ scope(failure) writefln("[%s] [%s] [%s] [%s]", past, de.timeLastAccessed, de.timeLastModified, future);
+ assert(de.timeLastAccessed > past);
+ assert(de.timeLastAccessed < future);
+ assert(de.timeLastModified > past);
+ assert(de.timeLastModified < future);
+
+ assert(attrIsDir(de.attributes) == flags.dir);
+ assert(attrIsDir(de.linkAttributes) == (flags.dir && !flags.link));
+ assert(attrIsFile(de.attributes) == flags.file);
+ assert(attrIsFile(de.linkAttributes) == (flags.file && !flags.link));
+ assert(!attrIsSymlink(de.attributes));
+ assert(attrIsSymlink(de.linkAttributes) == flags.link);
+
+ version (Windows)
+ {
+ assert(de.timeCreated > past);
+ assert(de.timeCreated < future);
+ }
+ else version (Posix)
+ {
+ assert(de.timeStatusChanged > past);
+ assert(de.timeStatusChanged < future);
+ assert(de.attributes == de.statBuf.st_mode);
+ }
+ }
+}
+
+
+/**
+ * Reads a file line by line and parses the line into a single value or a
+ * $(REF Tuple, std,typecons) of values depending on the length of `Types`.
+ * The lines are parsed using the specified format string. The format string is
+ * passed to $(REF formattedRead, std,_format), and therefore must conform to the
+ * _format string specification outlined in $(MREF std, _format).
+ *
+ * Params:
+ * Types = the types that each of the elements in the line should be returned as
+ * filename = the name of the file to read
+ * format = the _format string to use when reading
+ *
+ * Returns:
+ * If only one type is passed, then an array of that type. Otherwise, an
+ * array of $(REF Tuple, std,typecons)s.
+ *
+ * Throws:
+ * `Exception` if the format string is malformed. Also, throws `Exception`
+ * if any of the lines in the file are not fully consumed by the call
+ * to $(REF formattedRead, std,_format). Meaning that no empty lines or lines
+ * with extra characters are allowed.
+ */
+Select!(Types.length == 1, Types[0][], Tuple!(Types)[])
+slurp(Types...)(string filename, in char[] format)
+{
+ import std.array : appender;
+ import std.conv : text;
+ import std.exception : enforce;
+ import std.format : formattedRead;
+ import std.stdio : File;
+
+ auto app = appender!(typeof(return))();
+ ElementType!(typeof(return)) toAdd;
+ auto f = File(filename);
+ scope(exit) f.close();
+ foreach (line; f.byLine())
+ {
+ formattedRead(line, format, &toAdd);
+ enforce(line.empty,
+ text("Trailing characters at the end of line: `", line,
+ "'"));
+ app.put(toAdd);
+ }
+ return app.data;
+}
+
+///
+@system unittest
+{
+ import std.typecons : tuple;
+
+ scope(exit)
+ {
+ assert(exists(deleteme));
+ remove(deleteme);
+ }
+
+ write(deleteme, "12 12.25\n345 1.125"); // deleteme is the name of a temporary file
+
+ // Load file; each line is an int followed by comma, whitespace and a
+ // double.
+ auto a = slurp!(int, double)(deleteme, "%s %s");
+ assert(a.length == 2);
+ assert(a[0] == tuple(12, 12.25));
+ assert(a[1] == tuple(345, 1.125));
+}
+
+
+/**
+Returns the path to a directory for temporary files.
+
+On Windows, this function returns the result of calling the Windows API function
+$(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992.aspx, $(D GetTempPath)).
+
+On POSIX platforms, it searches through the following list of directories
+and returns the first one which is found to exist:
+$(OL
+ $(LI The directory given by the $(D TMPDIR) environment variable.)
+ $(LI The directory given by the $(D TEMP) environment variable.)
+ $(LI The directory given by the $(D TMP) environment variable.)
+ $(LI $(D /tmp))
+ $(LI $(D /var/tmp))
+ $(LI $(D /usr/tmp))
+)
+
+On all platforms, $(D tempDir) returns $(D ".") on failure, representing
+the current working directory.
+
+The return value of the function is cached, so the procedures described
+above will only be performed the first time the function is called. All
+subsequent runs will return the same string, regardless of whether
+environment variables and directory structures have changed in the
+meantime.
+
+The POSIX $(D tempDir) algorithm is inspired by Python's
+$(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, $(D tempfile.tempdir)).
+*/
+string tempDir() @trusted
+{
+ static string cache;
+ if (cache is null)
+ {
+ version (Windows)
+ {
+ import std.conv : to;
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992(v=vs.85).aspx
+ wchar[MAX_PATH + 2] buf;
+ DWORD len = GetTempPathW(buf.length, buf.ptr);
+ if (len) cache = buf[0 .. len].to!string;
+ }
+ else version (Android)
+ {
+ // Don't check for a global temporary directory as
+ // Android doesn't have one.
+ }
+ else version (Posix)
+ {
+ import std.process : environment;
+ // This function looks through the list of alternative directories
+ // and returns the first one which exists and is a directory.
+ static string findExistingDir(T...)(lazy T alternatives)
+ {
+ foreach (dir; alternatives)
+ if (!dir.empty && exists(dir)) return dir;
+ return null;
+ }
+
+ cache = findExistingDir(environment.get("TMPDIR"),
+ environment.get("TEMP"),
+ environment.get("TMP"),
+ "/tmp",
+ "/var/tmp",
+ "/usr/tmp");
+ }
+ else static assert(false, "Unsupported platform");
+
+ if (cache is null) cache = getcwd();
+ }
+ return cache;
+}