| // 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 (DragonFlyBSD) {} 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 (DragonFlyBSD) | |
| { | |
| return readLink("/proc/curproc/file"); | |
| } | |
| 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; | |
| static if (is(typeof(fd.d_namlen))) | |
| immutable len = fd.d_namlen; | |
| else | |
| immutable len = (() @trusted => core.stdc.string.strlen(fd.d_name.ptr))(); | |
| _name = buildPath(path, (() @trusted => fd.d_name.ptr[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 (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; | |
| } |