| // 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;) | |
| $(DIVC quickindex, | |
| $(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) | |
| $(LREF timeLastAccessed) | |
| $(LREF timeStatusChanged) | |
| )) | |
| $(TR $(TD Other) $(TD | |
| $(LREF DirEntry) | |
| $(LREF FileException) | |
| $(LREF PreserveAttributes) | |
| $(LREF SpanMode) | |
| $(LREF getAvailableDiskSpace) | |
| )) | |
| )) | |
| Copyright: Copyright The D Language Foundation 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), | |
| $(HTTP jmdavisprog.com, 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; | |
| import std.traits; | |
| import std.typecons; | |
| version (OSX) | |
| version = Darwin; | |
| else version (iOS) | |
| version = Darwin; | |
| else version (TVOS) | |
| version = Darwin; | |
| else version (WatchOS) | |
| version = Darwin; | |
| version (Windows) | |
| { | |
| import core.sys.windows.winbase, core.sys.windows.winnt, 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; // WCHAR can be aliased to wchar or wchar_t | |
| } | |
| 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 : text; | |
| import std.path : buildPath; | |
| import std.process : thisProcessID; | |
| enum base = "deleteme.dmd.unittest.pid"; | |
| static string fileName; | |
| if (!fileName) | |
| fileName = text(buildPath(tempDir(), base), thisProcessID); | |
| return fileName; | |
| } | |
| version (StdUnittest) private struct TestAliasedString | |
| { | |
| string get() @safe @nogc pure nothrow return scope { 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; | |
| private this(scope const(char)[] name, scope const(char)[] msg, string file, size_t line, uint errno) @safe pure | |
| { | |
| if (msg.empty) | |
| super(name is null ? "(null)" : name.idup, file, line); | |
| else | |
| super(text(name is null ? "(null)" : name, ": ", msg), file, line); | |
| this.errno = 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(scope const(char)[] name, scope const(char)[] msg, string file = __FILE__, size_t line = __LINE__) @safe pure | |
| { | |
| this(name, msg, file, line, 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 `__FILE__`. | |
| line = The _line where the error occurred. | |
| Defaults to `__LINE__`. | |
| +/ | |
| version (Windows) this(scope const(char)[] name, | |
| uint errno = .GetLastError(), | |
| string file = __FILE__, | |
| size_t line = __LINE__) @safe | |
| { | |
| this(name, generateSysErrorMsg(errno), file, line, errno); | |
| } | |
| else version (Posix) this(scope const(char)[] name, | |
| uint errno = .errno, | |
| string file = __FILE__, | |
| size_t line = __LINE__) @trusted | |
| { | |
| import std.exception : errnoString; | |
| this(name, errnoString(errno), file, line, errno); | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| assertThrown!FileException("non.existing.file.".readText); | |
| } | |
| private T cenforce(T)(T condition, lazy scope 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, scope const(char)[] name, scope 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, scope const(char)[] name, scope 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); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=17102 | |
| @safe unittest | |
| { | |
| try | |
| { | |
| cenforce(false, null, null, | |
| __FILE__, __LINE__); | |
| } | |
| catch (FileException) {} | |
| } | |
| /* ********************************** | |
| * Basic File operations. | |
| */ | |
| /******************************************** | |
| Read entire contents of file `name` and returns it as an untyped | |
| array. If the file size is larger than `upTo`, only `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. | |
| See_Also: $(REF readText, std,file) for reading and validating a text file. | |
| */ | |
| void[] read(R)(R name, size_t upTo = size_t.max) | |
| if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) | |
| { | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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); | |
| } | |
| std.file.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(scope const(char)[] name, scope const(FSChar)* namez, | |
| size_t upTo = size_t.max) @trusted | |
| { | |
| import core.memory : GC; | |
| import std.algorithm.comparison : min; | |
| import std.conv : to; | |
| import std.checkedint : checked; | |
| // 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 = GC.malloc(initialAlloc, GC.BlkAttr.NO_SCAN)[0 .. initialAlloc]; | |
| scope(failure) GC.free(result.ptr); | |
| auto size = checked(size_t(0)); | |
| for (;;) | |
| { | |
| immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size.get, | |
| (min(result.length, upTo) - size).get); | |
| 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.get, GC.BlkAttr.NO_SCAN)[0 .. newAlloc.get]; | |
| } | |
| return result.length - size >= maxSlackMemoryAllowed | |
| ? GC.realloc(result.ptr, size.get, GC.BlkAttr.NO_SCAN)[0 .. size.get] | |
| : result[0 .. size.get]; | |
| } | |
| version (Windows) | |
| private extern (Windows) @nogc nothrow | |
| { | |
| pragma(mangle, CreateFileW.mangleof) | |
| HANDLE trustedCreateFileW(scope const(wchar)* namez, DWORD dwDesiredAccess, | |
| DWORD dwShareMode, SECURITY_ATTRIBUTES* lpSecurityAttributes, | |
| DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, | |
| HANDLE hTemplateFile) @trusted; | |
| pragma(mangle, CloseHandle.mangleof) BOOL trustedCloseHandle(HANDLE) @trusted; | |
| } | |
| version (Windows) private void[] readImpl(scope const(char)[] name, scope const(FSChar)* namez, | |
| size_t upTo = size_t.max) @trusted | |
| { | |
| import core.memory : GC; | |
| import std.algorithm.comparison : min; | |
| static trustedGetFileSize(HANDLE hFile, out ulong fileSize) | |
| { | |
| 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, size_t nNumberOfBytesToRead) | |
| { | |
| // Read by chunks of size < 4GB (Windows API limit) | |
| size_t 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 = () { return GC.malloc(size, GC.BlkAttr.NO_SCAN)[0 .. size]; } (); | |
| scope(failure) | |
| { | |
| () { 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/cpuinfo"); | |
| 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"); | |
| } | |
| /++ | |
| Reads and validates (using $(REF validate, std, utf)) a text file. S can be | |
| an array of any character type. However, no width or endian conversions are | |
| performed. So, if the width or endianness of the characters in the given | |
| file differ from the width or endianness of the element type of S, then | |
| validation will fail. | |
| Params: | |
| S = the string type of the file | |
| name = string or range of characters representing the file _name | |
| Returns: Array of characters read. | |
| Throws: $(LREF FileException) if there is an error reading the file, | |
| $(REF UTFException, std, utf) on UTF decoding error. | |
| See_Also: $(REF read, std,file) for reading a binary file. | |
| +/ | |
| S readText(S = string, R)(auto ref R name) | |
| if (isSomeString!S && (isSomeFiniteCharInputRange!R || is(StringTypeOf!R))) | |
| { | |
| import std.algorithm.searching : startsWith; | |
| import std.encoding : getBOM, BOM; | |
| import std.exception : enforce; | |
| import std.format : format; | |
| import std.utf : UTFException, validate; | |
| static if (is(StringTypeOf!R)) | |
| StringTypeOf!R filename = name; | |
| else | |
| auto filename = name; | |
| static auto trustedCast(T)(void[] buf) @trusted { return cast(T) buf; } | |
| auto data = trustedCast!(ubyte[])(read(filename)); | |
| immutable bomSeq = getBOM(data); | |
| immutable bom = bomSeq.schema; | |
| static if (is(immutable ElementEncodingType!S == immutable char)) | |
| { | |
| with(BOM) switch (bom) | |
| { | |
| case utf16be: | |
| case utf16le: throw new UTFException("UTF-8 requested. BOM is for UTF-16"); | |
| case utf32be: | |
| case utf32le: throw new UTFException("UTF-8 requested. BOM is for UTF-32"); | |
| default: break; | |
| } | |
| } | |
| else static if (is(immutable ElementEncodingType!S == immutable wchar)) | |
| { | |
| with(BOM) switch (bom) | |
| { | |
| case utf8: throw new UTFException("UTF-16 requested. BOM is for UTF-8"); | |
| case utf16be: | |
| { | |
| version (BigEndian) | |
| break; | |
| else | |
| throw new UTFException("BOM is for UTF-16 LE on Big Endian machine"); | |
| } | |
| case utf16le: | |
| { | |
| version (BigEndian) | |
| throw new UTFException("BOM is for UTF-16 BE on Little Endian machine"); | |
| else | |
| break; | |
| } | |
| case utf32be: | |
| case utf32le: throw new UTFException("UTF-8 requested. BOM is for UTF-32"); | |
| default: break; | |
| } | |
| } | |
| else | |
| { | |
| with(BOM) switch (bom) | |
| { | |
| case utf8: throw new UTFException("UTF-16 requested. BOM is for UTF-8"); | |
| case utf16be: | |
| case utf16le: throw new UTFException("UTF-8 requested. BOM is for UTF-16"); | |
| case utf32be: | |
| { | |
| version (BigEndian) | |
| break; | |
| else | |
| throw new UTFException("BOM is for UTF-32 LE on Big Endian machine"); | |
| } | |
| case utf32le: | |
| { | |
| version (BigEndian) | |
| throw new UTFException("BOM is for UTF-32 BE on Little Endian machine"); | |
| else | |
| break; | |
| } | |
| default: break; | |
| } | |
| } | |
| if (data.length % ElementEncodingType!S.sizeof != 0) | |
| throw new UTFException(format!"The content of %s is not UTF-%s"(filename, ElementEncodingType!S.sizeof * 8)); | |
| auto result = trustedCast!S(data); | |
| validate(result); | |
| return result; | |
| } | |
| /// Read file with UTF-8 text. | |
| @safe unittest | |
| { | |
| write(deleteme, "abc"); // deleteme is the name of a temporary file | |
| scope(exit) remove(deleteme); | |
| string content = readText(deleteme); | |
| assert(content == "abc"); | |
| } | |
| // Read file with UTF-8 text but try to read it as UTF-16. | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| import std.utf : UTFException; | |
| write(deleteme, "abc"); | |
| scope(exit) remove(deleteme); | |
| // Throws because the file is not valid UTF-16. | |
| assertThrown!UTFException(readText!wstring(deleteme)); | |
| } | |
| // Read file with UTF-16 text. | |
| @safe unittest | |
| { | |
| import std.algorithm.searching : skipOver; | |
| write(deleteme, "\uFEFFabc"w); // With BOM | |
| scope(exit) remove(deleteme); | |
| auto content = readText!wstring(deleteme); | |
| assert(content == "\uFEFFabc"w); | |
| // Strips BOM if present. | |
| content.skipOver('\uFEFF'); | |
| assert(content == "abc"w); | |
| } | |
| @safe unittest | |
| { | |
| static assert(__traits(compiles, readText(TestAliasedString(null)))); | |
| } | |
| @safe unittest | |
| { | |
| import std.array : appender; | |
| import std.bitmanip : append, Endian; | |
| import std.exception : assertThrown; | |
| import std.path : buildPath; | |
| import std.string : representation; | |
| import std.utf : UTFException; | |
| mkdir(deleteme); | |
| scope(exit) rmdirRecurse(deleteme); | |
| immutable none8 = buildPath(deleteme, "none8"); | |
| immutable none16 = buildPath(deleteme, "none16"); | |
| immutable utf8 = buildPath(deleteme, "utf8"); | |
| immutable utf16be = buildPath(deleteme, "utf16be"); | |
| immutable utf16le = buildPath(deleteme, "utf16le"); | |
| immutable utf32be = buildPath(deleteme, "utf32be"); | |
| immutable utf32le = buildPath(deleteme, "utf32le"); | |
| immutable utf7 = buildPath(deleteme, "utf7"); | |
| write(none8, "京都市"); | |
| write(none16, "京都市"w); | |
| write(utf8, (cast(char[])[0xEF, 0xBB, 0xBF]) ~ "京都市"); | |
| { | |
| auto str = "\uFEFF京都市"w; | |
| auto arr = appender!(ubyte[])(); | |
| foreach (c; str) | |
| arr.append(c); | |
| write(utf16be, arr.data); | |
| } | |
| { | |
| auto str = "\uFEFF京都市"w; | |
| auto arr = appender!(ubyte[])(); | |
| foreach (c; str) | |
| arr.append!(ushort, Endian.littleEndian)(c); | |
| write(utf16le, arr.data); | |
| } | |
| { | |
| auto str = "\U0000FEFF京都市"d; | |
| auto arr = appender!(ubyte[])(); | |
| foreach (c; str) | |
| arr.append(c); | |
| write(utf32be, arr.data); | |
| } | |
| { | |
| auto str = "\U0000FEFF京都市"d; | |
| auto arr = appender!(ubyte[])(); | |
| foreach (c; str) | |
| arr.append!(uint, Endian.littleEndian)(c); | |
| write(utf32le, arr.data); | |
| } | |
| write(utf7, (cast(ubyte[])[0x2B, 0x2F, 0x76, 0x38, 0x2D]) ~ "foobar".representation); | |
| assertThrown!UTFException(readText(none16)); | |
| assert(readText(utf8) == (cast(char[])[0xEF, 0xBB, 0xBF]) ~ "京都市"); | |
| assertThrown!UTFException(readText(utf16be)); | |
| assertThrown!UTFException(readText(utf16le)); | |
| assertThrown!UTFException(readText(utf32be)); | |
| assertThrown!UTFException(readText(utf32le)); | |
| assert(readText(utf7) == (cast(char[])[0x2B, 0x2F, 0x76, 0x38, 0x2D]) ~ "foobar"); | |
| assertThrown!UTFException(readText!wstring(none8)); | |
| assert(readText!wstring(none16) == "京都市"w); | |
| assertThrown!UTFException(readText!wstring(utf8)); | |
| version (BigEndian) | |
| { | |
| assert(readText!wstring(utf16be) == "\uFEFF京都市"w); | |
| assertThrown!UTFException(readText!wstring(utf16le)); | |
| } | |
| else | |
| { | |
| assertThrown!UTFException(readText!wstring(utf16be)); | |
| assert(readText!wstring(utf16le) == "\uFEFF京都市"w); | |
| } | |
| assertThrown!UTFException(readText!wstring(utf32be)); | |
| assertThrown!UTFException(readText!wstring(utf32le)); | |
| assertThrown!UTFException(readText!wstring(utf7)); | |
| assertThrown!UTFException(readText!dstring(utf8)); | |
| assertThrown!UTFException(readText!dstring(utf16be)); | |
| assertThrown!UTFException(readText!dstring(utf16le)); | |
| version (BigEndian) | |
| { | |
| assert(readText!dstring(utf32be) == "\U0000FEFF京都市"d); | |
| assertThrown!UTFException(readText!dstring(utf32le)); | |
| } | |
| else | |
| { | |
| assertThrown!UTFException(readText!dstring(utf32be)); | |
| assert(readText!dstring(utf32le) == "\U0000FEFF京都市"d); | |
| } | |
| assertThrown!UTFException(readText!dstring(utf7)); | |
| } | |
| /********************************************* | |
| Write `buffer` to file `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: $(LREF FileException) on error. | |
| See_also: $(REF toFile, std,stdio) | |
| */ | |
| void write(R)(R name, const void[] buffer) | |
| if ((isSomeFiniteCharInputRange!R || isSomeString!R) && !isConvertibleToString!R) | |
| { | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) | |
| writeImpl(name, name.tempCString!FSChar(), buffer, false); | |
| else | |
| writeImpl(null, name.tempCString!FSChar(), buffer, false); | |
| } | |
| /// | |
| @safe 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 | |
| const bytes = read(deleteme); | |
| const fileInts = () @trusted { return cast(int[]) bytes; }(); | |
| assert(fileInts == 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 `buffer` to file `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: $(LREF FileException) on error. | |
| */ | |
| void append(R)(R name, const void[] buffer) | |
| if ((isSomeFiniteCharInputRange!R || isSomeString!R) && !isConvertibleToString!R) | |
| { | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) | |
| writeImpl(name, name.tempCString!FSChar(), buffer, true); | |
| else | |
| writeImpl(null, name.tempCString!FSChar(), buffer, true); | |
| } | |
| /// | |
| @safe 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); | |
| const bytes = read(deleteme); | |
| const fileInts = () @trusted { return cast(int[]) bytes; }(); | |
| assert(fileInts == 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(scope const(char)[] name, scope const(FSChar)* namez, | |
| scope const(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(scope const(char)[] name, scope const(FSChar)* namez, | |
| scope const(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 `from` _to `to`, moving it between directories if required. | |
| * If the target file exists, it is overwritten. | |
| * | |
| * It is not possible to rename a file across different mount points | |
| * or drives. On POSIX, the operation is atomic. That means, if `to` | |
| * already exists there will be no time period during the operation | |
| * where `to` is missing. See | |
| * $(HTTP man7.org/linux/man-pages/man2/rename.2.html, manpage for rename) | |
| * for more details. | |
| * | |
| * Params: | |
| * from = string or range of characters representing the existing file name | |
| * to = string or range of characters representing the target file name | |
| * | |
| * Throws: $(LREF FileException) on error. | |
| */ | |
| void rename(RF, RT)(RF from, RT to) | |
| if ((isSomeFiniteCharInputRange!RF || isSomeString!RF) && !isConvertibleToString!RF && | |
| (isSomeFiniteCharInputRange!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(immutable ElementEncodingType!RF == immutable char)) | |
| alias f = from; | |
| else | |
| enum string f = null; | |
| static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable 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))); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| auto t1 = deleteme, t2 = deleteme~"2"; | |
| scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove(); | |
| t1.write("1"); | |
| t1.rename(t2); | |
| assert(t2.readText == "1"); | |
| t1.write("2"); | |
| t1.rename(t2); | |
| assert(t2.readText == "2"); | |
| } | |
| private void renameImpl(scope const(char)[] f, scope const(char)[] t, | |
| scope const(FSChar)* fromz, scope 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 = fromz ? to!(typeof(f))(fromz[0 .. wcslen(fromz)]) : "(null)"; | |
| if (!t) | |
| t = toz ? to!(typeof(t))(toz[0 .. wcslen(toz)]) : "(null)"; | |
| 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 `name`. | |
| Params: | |
| name = string or range of characters representing the file _name | |
| Throws: $(LREF FileException) on error. | |
| */ | |
| void remove(R)(R name) | |
| if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) | |
| { | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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 | |
| { | |
| import std.exception : assertThrown; | |
| deleteme.write("Hello"); | |
| assert(deleteme.readText == "Hello"); | |
| deleteme.remove; | |
| assertThrown!FileException(deleteme.readText); | |
| } | |
| @safe unittest | |
| { | |
| static assert(__traits(compiles, remove(TestAliasedString("foo")))); | |
| } | |
| private void removeImpl(scope const(char)[] name, scope 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 = namez ? strlen(namez) : 0; | |
| name = namez[0 .. len]; | |
| } | |
| cenforce(core.stdc.stdio.remove(namez) == 0, | |
| "Failed to remove file " ~ (name is null ? "(null)" : name)); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| import std.exception : collectExceptionMsg, assertThrown; | |
| import std.algorithm.searching : startsWith; | |
| string filename = null; // e.g. as returned by File.tmpfile.name | |
| version (linux) | |
| { | |
| // exact exception message is OS-dependent | |
| auto msg = filename.remove.collectExceptionMsg!FileException; | |
| assert(msg.startsWith("Failed to remove file (null):"), msg); | |
| } | |
| else version (Windows) | |
| { | |
| // don't test exact message on windows, it's language dependent | |
| auto msg = filename.remove.collectExceptionMsg!FileException; | |
| assert(msg.startsWith("(null):"), msg); | |
| } | |
| else | |
| { | |
| assertThrown!FileException(filename.remove); | |
| } | |
| } | |
| version (Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(R)(R name) | |
| if (isSomeFiniteCharInputRange!R) | |
| { | |
| auto namez = name.tempCString!FSChar(); | |
| WIN32_FILE_ATTRIBUTE_DATA fad = void; | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) | |
| { | |
| static void getFA(scope const(char)[] name, scope 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(scope 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; | |
| } | |
| version (Posix) private extern (C) pragma(mangle, stat.mangleof) | |
| int trustedStat(scope const(FSChar)* namez, ref stat_t buf) @nogc nothrow @trusted; | |
| /** | |
| Get size of file `name` in bytes. | |
| Params: | |
| name = string or range of characters representing the file _name | |
| Returns: | |
| The size of file in bytes. | |
| Throws: | |
| $(LREF FileException) on error (e.g., file not found). | |
| */ | |
| ulong getSize(R)(R name) | |
| if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) | |
| { | |
| version (Windows) | |
| { | |
| with (getFileAttributesWin(name)) | |
| return makeUlong(nFileSizeLow, nFileSizeHigh); | |
| } | |
| else version (Posix) | |
| { | |
| auto namez = name.tempCString(); | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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 | |
| { | |
| scope(exit) deleteme.remove; | |
| // create a file of size 1 | |
| write(deleteme, "a"); | |
| assert(getSize(deleteme) == 1); | |
| // create a file of size 3 | |
| write(deleteme, "abc"); | |
| assert(getSize(deleteme) == 3); | |
| } | |
| @safe unittest | |
| { | |
| // create a file of size 1 | |
| write(deleteme, "a"); | |
| scope(exit) deleteme.exists && deleteme.remove; | |
| 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 const 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 `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: | |
| $(LREF FileException) on error. | |
| +/ | |
| void getTimes(R)(R name, | |
| out SysTime accessTime, | |
| out SysTime modificationTime) | |
| if (isSomeFiniteCharInputRange!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(); | |
| stat_t statbuf = void; | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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 | |
| { | |
| import std.datetime : abs, SysTime; | |
| scope(exit) deleteme.remove; | |
| write(deleteme, "a"); | |
| SysTime accessTime, modificationTime; | |
| getTimes(deleteme, accessTime, modificationTime); | |
| import std.datetime : Clock, seconds; | |
| auto currTime = Clock.currTime(); | |
| enum leeway = 5.seconds; | |
| auto diffAccess = accessTime - currTime; | |
| auto diffModification = modificationTime - currTime; | |
| assert(abs(diffAccess) <= leeway); | |
| assert(abs(diffModification) <= leeway); | |
| } | |
| @safe unittest | |
| { | |
| SysTime atime, mtime; | |
| static assert(__traits(compiles, getTimes(TestAliasedString("foo"), atime, mtime))); | |
| } | |
| @safe unittest | |
| { | |
| import std.stdio : writefln; | |
| auto currTime = Clock.currTime(); | |
| write(deleteme, "a"); | |
| scope(exit) assert(deleteme.exists), deleteme.remove; | |
| SysTime accessTime1; | |
| SysTime modificationTime1; | |
| getTimes(deleteme, accessTime1, modificationTime1); | |
| enum leeway = 5.seconds; | |
| { | |
| 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 `name`. | |
| This is the same as `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: | |
| $(LREF FileException) on error. | |
| +/ | |
| void getTimesWin(R)(R name, | |
| out SysTime fileCreationTime, | |
| out SysTime fileAccessTime, | |
| out SysTime fileModificationTime) | |
| if (isSomeFiniteCharInputRange!R || isConvertibleToString!R); | |
| // above line contains both constraints for docs | |
| // (so users know how it can be called) | |
| } | |
| else version (Windows) | |
| { | |
| void getTimesWin(R)(R name, | |
| out SysTime fileCreationTime, | |
| out SysTime fileAccessTime, | |
| out SysTime fileModificationTime) | |
| if (isSomeFiniteCharInputRange!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))); | |
| } | |
| } | |
| version (Darwin) | |
| private | |
| { | |
| import core.stdc.config : c_ulong; | |
| enum ATTR_CMN_MODTIME = 0x00000400, ATTR_CMN_ACCTIME = 0x00001000; | |
| alias attrgroup_t = uint; | |
| static struct attrlist | |
| { | |
| ushort bitmapcount, reserved; | |
| attrgroup_t commonattr, volattr, dirattr, fileattr, forkattr; | |
| } | |
| extern(C) int setattrlist(scope const(char)* path, scope ref attrlist attrs, | |
| scope void* attrbuf, size_t attrBufSize, c_ulong options) nothrow @nogc @system; | |
| } | |
| /++ | |
| Set access/modified times of file or folder `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: | |
| $(LREF FileException) on error. | |
| +/ | |
| void setTimes(R)(R name, | |
| SysTime accessTime, | |
| SysTime modificationTime) | |
| if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) | |
| { | |
| auto namez = name.tempCString!FSChar(); | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) | |
| alias names = name; | |
| else | |
| string names = null; | |
| setTimesImpl(names, namez, accessTime, modificationTime); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.datetime : DateTime, hnsecs, SysTime; | |
| scope(exit) deleteme.remove; | |
| write(deleteme, "a"); | |
| SysTime accessTime = SysTime(DateTime(2010, 10, 4, 0, 0, 30)); | |
| SysTime modificationTime = SysTime(DateTime(2018, 10, 4, 0, 0, 30)); | |
| setTimes(deleteme, accessTime, modificationTime); | |
| SysTime accessTimeResolved, modificationTimeResolved; | |
| getTimes(deleteme, accessTimeResolved, modificationTimeResolved); | |
| assert(accessTime == accessTimeResolved); | |
| assert(modificationTime == modificationTimeResolved); | |
| } | |
| /// ditto | |
| void setTimes(R)(auto ref R name, | |
| SysTime accessTime, | |
| SysTime modificationTime) | |
| if (isConvertibleToString!R) | |
| { | |
| setTimes!(StringTypeOf!R)(name, accessTime, modificationTime); | |
| } | |
| private void setTimesImpl(scope const(char)[] names, scope const(FSChar)* namez, | |
| SysTime accessTime, SysTime modificationTime) @trusted | |
| { | |
| version (Windows) | |
| { | |
| import std.datetime.systime : SysTimeToFILETIME; | |
| const ta = SysTimeToFILETIME(accessTime); | |
| const tm = SysTimeToFILETIME(modificationTime); | |
| alias defaults = | |
| AliasSeq!(FILE_WRITE_ATTRIBUTES, | |
| 0, | |
| null, | |
| OPEN_EXISTING, | |
| FILE_ATTRIBUTE_NORMAL | | |
| FILE_ATTRIBUTE_DIRECTORY | | |
| FILE_FLAG_BACKUP_SEMANTICS, | |
| HANDLE.init); | |
| auto h = CreateFileW(namez, defaults); | |
| cenforce(h != INVALID_HANDLE_VALUE, names, namez); | |
| scope(exit) | |
| cenforce(CloseHandle(h), names, namez); | |
| cenforce(SetFileTime(h, null, &ta, &tm), names, namez); | |
| } | |
| else | |
| { | |
| static if (is(typeof(&utimensat))) | |
| { | |
| timespec[2] t = void; | |
| t[0] = accessTime.toTimeSpec(); | |
| t[1] = modificationTime.toTimeSpec(); | |
| cenforce(utimensat(AT_FDCWD, namez, t, 0) == 0, names, namez); | |
| } | |
| else | |
| { | |
| version (Darwin) | |
| { | |
| // Set modification & access times with setattrlist to avoid precision loss. | |
| attrlist attrs = { bitmapcount: 5, reserved: 0, | |
| commonattr: ATTR_CMN_MODTIME | ATTR_CMN_ACCTIME, | |
| volattr: 0, dirattr: 0, fileattr: 0, forkattr: 0 }; | |
| timespec[2] attrbuf = [modificationTime.toTimeSpec(), accessTime.toTimeSpec()]; | |
| if (0 == setattrlist(namez, attrs, &attrbuf, attrbuf.sizeof, 0)) | |
| return; | |
| if (.errno != ENOTSUP) | |
| cenforce(false, names, namez); | |
| // Not all volumes support setattrlist. In such cases | |
| // fall through to the utimes implementation. | |
| } | |
| timeval[2] t = void; | |
| t[0] = accessTime.toTimeVal(); | |
| t[1] = modificationTime.toTimeVal(); | |
| cenforce(utimes(namez, t) == 0, names, namez); | |
| } | |
| } | |
| } | |
| @safe unittest | |
| { | |
| if (false) // Test instatiation | |
| setTimes(TestAliasedString("foo"), SysTime.init, SysTime.init); | |
| } | |
| @safe 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); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=23683 | |
| @safe unittest | |
| { | |
| scope(exit) deleteme.remove; | |
| import std.stdio : File; | |
| auto f = File(deleteme, "wb"); | |
| SysTime time = SysTime(DateTime(2018, 10, 4, 0, 0, 30)); | |
| setTimes(deleteme, time, time); | |
| } | |
| /++ | |
| Returns the time that the given file was last modified. | |
| Params: | |
| name = the name of the file to check | |
| Returns: | |
| A $(REF SysTime,std,datetime,systime). | |
| Throws: | |
| $(LREF FileException) if the given file does not exist. | |
| +/ | |
| SysTime timeLastModified(R)(R name) | |
| if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) | |
| { | |
| version (Windows) | |
| { | |
| SysTime dummy; | |
| SysTime ftm; | |
| getTimesWin(name, dummy, dummy, ftm); | |
| return ftm; | |
| } | |
| else version (Posix) | |
| { | |
| auto namez = name.tempCString!FSChar(); | |
| stat_t statbuf = void; | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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 | |
| { | |
| import std.datetime : abs, DateTime, hnsecs, SysTime; | |
| scope(exit) deleteme.remove; | |
| import std.datetime : Clock, seconds; | |
| auto currTime = Clock.currTime(); | |
| enum leeway = 5.seconds; | |
| deleteme.write("bb"); | |
| assert(abs(deleteme.timeLastModified - currTime) <= leeway); | |
| } | |
| @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 `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 `source` (i.e., `target` is | |
| older than `source` or does not exist), use the comparison | |
| below. The code throws a $(LREF FileException) if `source` does not | |
| exist (as it should). On the other hand, the `SysTime.min` default | |
| makes a non-existing `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. | |
| Returns: | |
| A $(REF SysTime,std,datetime,systime). | |
| Example: | |
| -------------------- | |
| if (source.timeLastModified >= target.timeLastModified(SysTime.min)) | |
| { | |
| // must (re)build | |
| } | |
| else | |
| { | |
| // target is up-to-date | |
| } | |
| -------------------- | |
| +/ | |
| SysTime timeLastModified(R)(R name, SysTime returnIfMissing) | |
| if (isSomeFiniteCharInputRange!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(); | |
| stat_t statbuf = void; | |
| return trustedStat(namez, statbuf) != 0 ? | |
| returnIfMissing : | |
| statTimeToStdTime!'m'(statbuf); | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.datetime : SysTime; | |
| assert("file.does.not.exist".timeLastModified(SysTime.min) == SysTime.min); | |
| auto source = deleteme ~ "source"; | |
| auto target = deleteme ~ "target"; | |
| scope(exit) source.remove, target.remove; | |
| source.write("."); | |
| assert(target.timeLastModified(SysTime.min) < source.timeLastModified); | |
| target.write("."); | |
| assert(target.timeLastModified(SysTime.min) >= source.timeLastModified); | |
| } | |
| version (StdDdoc) | |
| { | |
| /++ | |
| $(BLUE This function is POSIX-Only.) | |
| Returns the time that the given file was last modified. | |
| Params: | |
| statbuf = stat_t retrieved from file. | |
| +/ | |
| SysTime timeLastModified()(auto ref stat_t statbuf) pure nothrow {assert(false);} | |
| /++ | |
| $(BLUE This function is POSIX-Only.) | |
| Returns the time that the given file was last accessed. | |
| Params: | |
| statbuf = stat_t retrieved from file. | |
| +/ | |
| SysTime timeLastAccessed()(auto ref stat_t statbuf) pure nothrow {assert(false);} | |
| /++ | |
| $(BLUE This function is POSIX-Only.) | |
| Returns the time that the given file was last changed. | |
| Params: | |
| statbuf = stat_t retrieved from file. | |
| +/ | |
| SysTime timeStatusChanged()(auto ref stat_t statbuf) pure nothrow {assert(false);} | |
| } | |
| else version (Posix) | |
| { | |
| SysTime timeLastModified()(auto ref stat_t statbuf) pure nothrow | |
| { | |
| return statTimeToStdTime!'m'(statbuf); | |
| } | |
| SysTime timeLastAccessed()(auto ref stat_t statbuf) pure nothrow | |
| { | |
| return statTimeToStdTime!'a'(statbuf); | |
| } | |
| SysTime timeStatusChanged()(auto ref stat_t statbuf) pure nothrow | |
| { | |
| return statTimeToStdTime!'c'(statbuf); | |
| } | |
| @safe unittest | |
| { | |
| stat_t statbuf; | |
| // check that both lvalues and rvalues work | |
| timeLastAccessed(statbuf); | |
| cast(void) timeLastAccessed(stat_t.init); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| //std.process.executeShell("echo a > deleteme"); | |
| 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. | |
| // | |
| // Note: on linux systems, although in theory a change to a file date | |
| // can be tracked with precision of 4 msecs, this test waits 20 msecs | |
| // to prevent possible problems relative to the CI services the dlang uses, | |
| // as they may have the HZ setting that controls the software clock set to 100 | |
| // (instead of the more common 250). | |
| // see https://man7.org/linux/man-pages/man7/time.7.html | |
| // https://stackoverflow.com/a/14393315, | |
| // https://issues.dlang.org/show_bug.cgi?id=21148 | |
| version (FreeBSD) {} else | |
| version (DragonFlyBSD) {} else | |
| version (OSX) {} else | |
| @safe 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; | |
| () @trusted { Thread.sleep(20.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 (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) | |
| { | |
| return existsImpl(name.tempCString!FSChar()); | |
| } | |
| /// ditto | |
| bool exists(R)(auto ref R name) | |
| if (isConvertibleToString!R) | |
| { | |
| return exists!(StringTypeOf!R)(name); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| auto f = deleteme ~ "does.not.exist"; | |
| assert(!f.exists); | |
| f.write("hello"); | |
| assert(f.exists); | |
| f.remove; | |
| assert(!f.exists); | |
| } | |
| private bool existsImpl(scope 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(!"this file does not exist".exists); | |
| deleteme.write("a\n"); | |
| scope(exit) deleteme.remove; | |
| assert(deleteme.exists); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=16573 | |
| @safe unittest | |
| { | |
| 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 | |
| `st_mode` value which is part of the $(D stat struct) gotten by | |
| calling the $(HTTP en.wikipedia.org/wiki/Stat_%28Unix%29, `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. | |
| Returns: | |
| The attributes of the file as a `uint`. | |
| Throws: $(LREF FileException) on error. | |
| +/ | |
| uint getAttributes(R)(R name) | |
| if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) | |
| { | |
| version (Windows) | |
| { | |
| auto namez = name.tempCString!FSChar(); | |
| static auto trustedGetFileAttributesW(scope const(FSChar)* namez) @trusted | |
| { | |
| return GetFileAttributesW(namez); | |
| } | |
| immutable result = trustedGetFileAttributesW(namez); | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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(); | |
| stat_t statbuf = void; | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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); | |
| } | |
| /// getAttributes with a file | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| auto f = deleteme ~ "file"; | |
| scope(exit) f.remove; | |
| assert(!f.exists); | |
| assertThrown!FileException(f.getAttributes); | |
| f.write("."); | |
| auto attributes = f.getAttributes; | |
| assert(!attributes.attrIsDir); | |
| assert(attributes.attrIsFile); | |
| } | |
| /// getAttributes with a directory | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| auto dir = deleteme ~ "dir"; | |
| scope(exit) dir.rmdir; | |
| assert(!dir.exists); | |
| assertThrown!FileException(dir.getAttributes); | |
| dir.mkdir; | |
| auto attributes = dir.getAttributes; | |
| assert(attributes.attrIsDir); | |
| assert(!attributes.attrIsFile); | |
| } | |
| @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: | |
| $(LREF FileException) on error. | |
| +/ | |
| uint getLinkAttributes(R)(R name) | |
| if (isSomeFiniteCharInputRange!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(immutable ElementEncodingType!R == immutable 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 | |
| { | |
| import std.exception : assertThrown; | |
| auto source = deleteme ~ "source"; | |
| auto target = deleteme ~ "target"; | |
| assert(!source.exists); | |
| assertThrown!FileException(source.getLinkAttributes); | |
| // symlinking isn't available on Windows | |
| version (Posix) | |
| { | |
| scope(exit) source.remove, target.remove; | |
| target.write("target"); | |
| target.symlink(source); | |
| assert(source.readText == "target"); | |
| assert(source.isSymlink); | |
| assert(source.getLinkAttributes.attrIsSymlink); | |
| } | |
| } | |
| /// if the file is no symlink, getLinkAttributes behaves like getAttributes | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| auto f = deleteme ~ "file"; | |
| scope(exit) f.remove; | |
| assert(!f.exists); | |
| assertThrown!FileException(f.getLinkAttributes); | |
| f.write("."); | |
| auto attributes = f.getLinkAttributes; | |
| assert(!attributes.attrIsDir); | |
| assert(attributes.attrIsFile); | |
| } | |
| /// if the file is no symlink, getLinkAttributes behaves like getAttributes | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| auto dir = deleteme ~ "dir"; | |
| scope(exit) dir.rmdir; | |
| assert(!dir.exists); | |
| assertThrown!FileException(dir.getLinkAttributes); | |
| dir.mkdir; | |
| auto attributes = dir.getLinkAttributes; | |
| assert(attributes.attrIsDir); | |
| assert(!attributes.attrIsFile); | |
| } | |
| @safe unittest | |
| { | |
| static assert(__traits(compiles, getLinkAttributes(TestAliasedString(null)))); | |
| } | |
| /++ | |
| Set the _attributes of the given file. | |
| For example, a programmatic equivalent of Unix's `chmod +x name` | |
| to make a file executable is | |
| `name.setAttributes(name.getAttributes | octal!700)`. | |
| Params: | |
| name = the file _name | |
| attributes = the _attributes to set the file to | |
| Throws: | |
| $(LREF FileException) if the given file does not exist. | |
| +/ | |
| void setAttributes(R)(R name, uint attributes) | |
| if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) | |
| { | |
| version (Windows) | |
| { | |
| auto namez = name.tempCString!FSChar(); | |
| static auto trustedSetFileAttributesW(scope const(FSChar)* namez, uint dwFileAttributes) @trusted | |
| { | |
| return SetFileAttributesW(namez, dwFileAttributes); | |
| } | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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(scope const(FSChar)* namez, mode_t mode) @trusted | |
| { | |
| return chmod(namez, mode); | |
| } | |
| assert(attributes <= mode_t.max); | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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))); | |
| } | |
| /// setAttributes with a file | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| import std.conv : octal; | |
| auto f = deleteme ~ "file"; | |
| version (Posix) | |
| { | |
| scope(exit) f.remove; | |
| assert(!f.exists); | |
| assertThrown!FileException(f.setAttributes(octal!777)); | |
| f.write("."); | |
| auto attributes = f.getAttributes; | |
| assert(!attributes.attrIsDir); | |
| assert(attributes.attrIsFile); | |
| f.setAttributes(octal!777); | |
| attributes = f.getAttributes; | |
| assert((attributes & 1023) == octal!777); | |
| } | |
| } | |
| /// setAttributes with a directory | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| import std.conv : octal; | |
| auto dir = deleteme ~ "dir"; | |
| version (Posix) | |
| { | |
| scope(exit) dir.rmdir; | |
| assert(!dir.exists); | |
| assertThrown!FileException(dir.setAttributes(octal!777)); | |
| dir.mkdir; | |
| auto attributes = dir.getAttributes; | |
| assert(attributes.attrIsDir); | |
| assert(!attributes.attrIsFile); | |
| dir.setAttributes(octal!777); | |
| attributes = dir.getAttributes; | |
| assert((attributes & 1023) == octal!777); | |
| } | |
| } | |
| /++ | |
| Returns whether the given file is a directory. | |
| Params: | |
| name = The path to the file. | |
| Returns: | |
| true if name specifies a directory | |
| Throws: | |
| $(LREF FileException) if the given file does not exist. | |
| +/ | |
| @property bool isDir(R)(R name) | |
| if (isSomeFiniteCharInputRange!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 | |
| { | |
| import std.exception : assertThrown; | |
| auto dir = deleteme ~ "dir"; | |
| auto f = deleteme ~ "f"; | |
| scope(exit) dir.rmdir, f.remove; | |
| assert(!dir.exists); | |
| assertThrown!FileException(dir.isDir); | |
| dir.mkdir; | |
| assert(dir.isDir); | |
| f.write("."); | |
| assert(!f.isDir); | |
| } | |
| @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); | |
| } | |
| } | |
| @safe 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 | |
| +/ | |
| 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 | |
| { | |
| import std.exception : assertThrown; | |
| auto dir = deleteme ~ "dir"; | |
| auto f = deleteme ~ "f"; | |
| scope(exit) dir.rmdir, f.remove; | |
| assert(!dir.exists); | |
| assertThrown!FileException(dir.getAttributes.attrIsDir); | |
| dir.mkdir; | |
| assert(dir.isDir); | |
| assert(dir.getAttributes.attrIsDir); | |
| f.write("."); | |
| assert(!f.isDir); | |
| assert(!f.getAttributes.attrIsDir); | |
| } | |
| @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 `isFile` or `isDir` will return true for any given file. | |
| On POSIX systems, if `isFile` is `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 `isFile` and `isDir` to be `false` for a | |
| particular file (in which case, it's a special file). You can use | |
| `getAttributes` to get the attributes to figure out what type of special | |
| it is, or you can use `DirEntry` to get at its `statBuf`, which is the | |
| result from `stat`. In either case, see the man page for `stat` for | |
| more information. | |
| Params: | |
| name = The path to the file. | |
| Returns: | |
| true if name specifies a file | |
| Throws: | |
| $(LREF FileException) if the given file does not exist. | |
| +/ | |
| @property bool isFile(R)(R name) | |
| if (isSomeFiniteCharInputRange!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); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| auto dir = deleteme ~ "dir"; | |
| auto f = deleteme ~ "f"; | |
| scope(exit) dir.rmdir, f.remove; | |
| dir.mkdir; | |
| assert(!dir.isFile); | |
| assert(!f.exists); | |
| assertThrown!FileException(f.isFile); | |
| f.write("."); | |
| assert(f.isFile); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=15658 | |
| @safe unittest | |
| { | |
| 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 | |
| `attrIsFile` or `attrIsDir` will return `true` for the | |
| _attributes of any given file. | |
| On POSIX systems, if `attrIsFile` is `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 `attrIsFile` and `attrIsDir` to be `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 `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 | |
| { | |
| import std.exception : assertThrown; | |
| auto dir = deleteme ~ "dir"; | |
| auto f = deleteme ~ "f"; | |
| scope(exit) dir.rmdir, f.remove; | |
| dir.mkdir; | |
| assert(!dir.isFile); | |
| assert(!dir.getAttributes.attrIsFile); | |
| assert(!f.exists); | |
| assertThrown!FileException(f.getAttributes.attrIsFile); | |
| f.write("."); | |
| assert(f.isFile); | |
| assert(f.getAttributes.attrIsFile); | |
| } | |
| @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 `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: | |
| $(LREF FileException) if the given file does not exist. | |
| +/ | |
| @property bool isSymlink(R)(R name) | |
| if (isSomeFiniteCharInputRange!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)); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| auto source = deleteme ~ "source"; | |
| auto target = deleteme ~ "target"; | |
| assert(!source.exists); | |
| assertThrown!FileException(source.isSymlink); | |
| // symlinking isn't available on Windows | |
| version (Posix) | |
| { | |
| scope(exit) source.remove, target.remove; | |
| target.write("target"); | |
| target.symlink(source); | |
| assert(source.readText == "target"); | |
| assert(source.isSymlink); | |
| assert(source.getLinkAttributes.attrIsSymlink); | |
| } | |
| } | |
| @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 `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; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| auto source = deleteme ~ "source"; | |
| auto target = deleteme ~ "target"; | |
| assert(!source.exists); | |
| assertThrown!FileException(source.getLinkAttributes.attrIsSymlink); | |
| // symlinking isn't available on Windows | |
| version (Posix) | |
| { | |
| scope(exit) source.remove, target.remove; | |
| target.write("target"); | |
| target.symlink(source); | |
| assert(source.readText == "target"); | |
| assert(source.isSymlink); | |
| assert(source.getLinkAttributes.attrIsSymlink); | |
| } | |
| } | |
| /** | |
| Change directory to `pathname`. Equivalent to `cd` on | |
| Windows and POSIX. | |
| Params: | |
| pathname = the directory to step into | |
| Throws: $(LREF FileException) on error. | |
| */ | |
| void chdir(R)(R pathname) | |
| if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) | |
| { | |
| // Place outside of @trusted block | |
| auto pathz = pathname.tempCString!FSChar(); | |
| version (Windows) | |
| { | |
| static auto trustedChdir(scope const(FSChar)* pathz) @trusted | |
| { | |
| return SetCurrentDirectoryW(pathz); | |
| } | |
| } | |
| else version (Posix) | |
| { | |
| static auto trustedChdir(scope const(FSChar)* pathz) @trusted | |
| { | |
| return core.sys.posix.unistd.chdir(pathz) == 0; | |
| } | |
| } | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.algorithm.sorting : sort; | |
| import std.array : array; | |
| import std.path : buildPath; | |
| auto cwd = getcwd; | |
| auto dir = deleteme ~ "dir"; | |
| dir.mkdir; | |
| scope(exit) cwd.chdir, dir.rmdirRecurse; | |
| dir.buildPath("a").write("."); | |
| dir.chdir; // step into dir | |
| "b".write("."); | |
| assert(dirEntries(".", SpanMode.shallow).array.sort.equal( | |
| [".".buildPath("a"), ".".buildPath("b")] | |
| )); | |
| } | |
| @safe unittest | |
| { | |
| static assert(__traits(compiles, chdir(TestAliasedString(null)))); | |
| } | |
| /** | |
| Make a new directory `pathname`. | |
| Params: | |
| pathname = the path of the directory to make | |
| Throws: | |
| $(LREF FileException) on POSIX or $(LREF WindowsException) on Windows | |
| if an error occured. | |
| */ | |
| void mkdir(R)(R pathname) | |
| if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) | |
| { | |
| // Place outside of @trusted block | |
| const pathz = pathname.tempCString!FSChar(); | |
| version (Windows) | |
| { | |
| static auto trustedCreateDirectoryW(scope const(FSChar)* pathz) @trusted | |
| { | |
| return CreateDirectoryW(pathz, null); | |
| } | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) | |
| alias pathStr = pathname; | |
| else | |
| string pathStr = null; | |
| wenforce(trustedCreateDirectoryW(pathz), pathStr, pathz); | |
| } | |
| else version (Posix) | |
| { | |
| import std.conv : octal; | |
| static auto trustedMkdir(scope const(FSChar)* pathz, mode_t mode) @trusted | |
| { | |
| return core.sys.posix.sys.stat.mkdir(pathz, mode); | |
| } | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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.file : mkdir; | |
| static assert(__traits(compiles, mkdir(TestAliasedString(null)))); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.file : mkdir; | |
| auto dir = deleteme ~ "dir"; | |
| scope(exit) dir.rmdir; | |
| dir.mkdir; | |
| assert(dir.exists); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| assertThrown("a/b/c/d/e".mkdir); | |
| } | |
| // Same as mkdir but ignores "already exists" errors. | |
| // Returns: "true" if the directory was created, | |
| // "false" if it already existed. | |
| private bool ensureDirExists()(scope const(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 | |
| `pathname` already exists. | |
| Params: | |
| pathname = the full path of the directory to create | |
| Throws: $(LREF FileException) on error. | |
| */ | |
| void mkdirRecurse(scope const(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.path : buildPath; | |
| auto dir = deleteme ~ "dir"; | |
| scope(exit) dir.rmdirRecurse; | |
| dir.mkdir; | |
| assert(dir.exists); | |
| dir.mkdirRecurse; // does nothing | |
| // creates all parent directories as needed | |
| auto nested = dir.buildPath("a", "b", "c"); | |
| nested.mkdirRecurse; | |
| assert(nested.exists); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| scope(exit) deleteme.remove; | |
| deleteme.write("a"); | |
| // cannot make directory as it's already a file | |
| assertThrown!FileException(deleteme.mkdirRecurse); | |
| } | |
| @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`)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=3570 | |
| { | |
| 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 `pathname`. | |
| Params: | |
| pathname = Range or string specifying the directory name | |
| Throws: $(LREF FileException) on error. | |
| */ | |
| void rmdir(R)(R pathname) | |
| if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R) | |
| { | |
| // Place outside of @trusted block | |
| auto pathz = pathname.tempCString!FSChar(); | |
| version (Windows) | |
| { | |
| static auto trustedRmdir(scope const(FSChar)* pathz) @trusted | |
| { | |
| return RemoveDirectoryW(pathz); | |
| } | |
| } | |
| else version (Posix) | |
| { | |
| static auto trustedRmdir(scope const(FSChar)* pathz) @trusted | |
| { | |
| return core.sys.posix.unistd.rmdir(pathz) == 0; | |
| } | |
| } | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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)))); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| auto dir = deleteme ~ "dir"; | |
| dir.mkdir; | |
| assert(dir.exists); | |
| dir.rmdir; | |
| assert(!dir.exists); | |
| } | |
| /++ | |
| $(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: | |
| $(LREF FileException) on error (which includes if the _symlink already | |
| exists). | |
| +/ | |
| version (StdDdoc) void symlink(RO, RL)(RO original, RL link) | |
| if ((isSomeFiniteCharInputRange!RO || isConvertibleToString!RO) && | |
| (isSomeFiniteCharInputRange!RL || isConvertibleToString!RL)); | |
| else version (Posix) void symlink(RO, RL)(RO original, RL link) | |
| if ((isSomeFiniteCharInputRange!RO || isConvertibleToString!RO) && | |
| (isSomeFiniteCharInputRange!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: | |
| $(LREF FileException) on error. | |
| +/ | |
| version (StdDdoc) string readLink(R)(R link) | |
| if (isSomeFiniteCharInputRange!R || isConvertibleToString!R); | |
| else version (Posix) string readLink(R)(R link) | |
| if (isSomeFiniteCharInputRange!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!(ElementType!string) 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: $(LREF FileException) on error. | |
| */ | |
| version (Windows) string getcwd() @trusted | |
| { | |
| import std.conv : to; | |
| import std.checkedint : checked; | |
| /* 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. | |
| */ | |
| version (StdUnittest) | |
| enum BUF_SIZE = 10; // trigger reallocation code | |
| else | |
| enum BUF_SIZE = 4096; // enough for most common case | |
| wchar[BUF_SIZE] buffW = void; | |
| 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 cn = checked(n); | |
| auto ptr = cast(wchar*) malloc((cn * wchar.sizeof).get); | |
| scope(exit) free(ptr); | |
| immutable n2 = GetCurrentDirectoryW(cn.get, ptr); | |
| cenforce(n2 && n2 < cn, "getcwd"); | |
| return ptr[0 .. n2].to!string; | |
| } | |
| } | |
| else version (Solaris) string getcwd() @trusted | |
| { | |
| /* 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() @trusted | |
| { | |
| 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; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| auto s = getcwd(); | |
| assert(s.length); | |
| } | |
| /** | |
| * Returns the full path of the current executable. | |
| * | |
| * Returns: | |
| * The path of the executable as a `string`. | |
| * | |
| * Throws: | |
| * $(REF1 Exception, object) | |
| */ | |
| @trusted string thisExePath() | |
| { | |
| version (Darwin) | |
| { | |
| import core.sys.darwin.mach.dyld : _NSGetExecutablePath; | |
| 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); | |
| wenforce(len); | |
| if (len != buffer.length) | |
| return to!(string)(buffer[0 .. len]); | |
| buffer.length *= 2; | |
| } | |
| } | |
| else version (DragonFlyBSD) | |
| { | |
| import core.sys.dragonflybsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME; | |
| import std.exception : errnoEnforce, assumeUnique; | |
| 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 (FreeBSD) | |
| { | |
| import core.sys.freebsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME; | |
| import std.exception : errnoEnforce, assumeUnique; | |
| 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) | |
| { | |
| import core.sys.netbsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC_ARGS, KERN_PROC_PATHNAME; | |
| import std.exception : errnoEnforce, assumeUnique; | |
| int[4] mib = [CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME]; | |
| 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 (OpenBSD) | |
| { | |
| import core.sys.openbsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC_ARGS, KERN_PROC_ARGV; | |
| import core.sys.posix.unistd : getpid; | |
| import std.conv : to; | |
| import std.exception : enforce, errnoEnforce; | |
| import std.process : searchPathFor; | |
| int[4] mib = [CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV]; | |
| size_t len; | |
| auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); | |
| errnoEnforce(result == 0); | |
| auto argv = new char*[len - 1]; | |
| result = sysctl(mib.ptr, mib.length, argv.ptr, &len, null, 0); | |
| errnoEnforce(result == 0); | |
| auto argv0 = argv[0]; | |
| if (*argv0 == '/' || *argv0 == '.') | |
| { | |
| import core.sys.posix.stdlib : realpath; | |
| auto absolutePath = realpath(argv0, null); | |
| scope (exit) | |
| { | |
| if (absolutePath) | |
| free(absolutePath); | |
| } | |
| errnoEnforce(absolutePath); | |
| return to!(string)(absolutePath); | |
| } | |
| else | |
| { | |
| auto absolutePath = searchPathFor(to!string(argv0)); | |
| errnoEnforce(absolutePath); | |
| return absolutePath; | |
| } | |
| } | |
| 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 version (Hurd) | |
| { | |
| return readLink("/proc/self/exe"); | |
| } | |
| 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 | |
| { | |
| @safe: | |
| /++ | |
| Constructs a `DirEntry` for the given file (or directory). | |
| Params: | |
| path = The file (or directory) to get a DirEntry for. | |
| Throws: | |
| $(LREF FileException) if the file does not exist. | |
| +/ | |
| this(return scope 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 `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 return scope; | |
| /++ | |
| Returns whether the file represented by this `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() scope; | |
| /++ | |
| Returns whether the file represented by this `DirEntry` is a file. | |
| On Windows, if a file is not a directory, then it's a file. So, | |
| either `isFile` or `isDir` will return `true`. | |
| On POSIX systems, if `isFile` is `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 `isFile` and `isDir` to | |
| be `false` for a particular file (in which case, it's a special | |
| file). You can use `attributes` or `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() scope; | |
| /++ | |
| Returns whether the file represented by this `DirEntry` is a | |
| symbolic link. | |
| On Windows, return `true` when the file is either a symbolic | |
| link or a junction point. | |
| +/ | |
| @property bool isSymlink() scope; | |
| /++ | |
| Returns the size of the file represented by this `DirEntry` | |
| in bytes. | |
| +/ | |
| @property ulong size() scope; | |
| /++ | |
| $(BLUE This function is Windows-Only.) | |
| Returns the creation time of the file represented by this | |
| `DirEntry`. | |
| +/ | |
| @property SysTime timeCreated() const scope; | |
| /++ | |
| Returns the time that the file represented by this `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 | |
| `timeLastAccessed` will return the same value as | |
| `timeLastModified`. | |
| +/ | |
| @property SysTime timeLastAccessed() scope; | |
| /++ | |
| Returns the time that the file represented by this `DirEntry` was | |
| last modified. | |
| +/ | |
| @property SysTime timeLastModified() scope; | |
| /++ | |
| $(BLUE This function is POSIX-Only.) | |
| Returns the time that the file represented by this `DirEntry` was | |
| last changed (not only in contents, but also in permissions or ownership). | |
| +/ | |
| @property SysTime timeStatusChanged() const scope; | |
| /++ | |
| Returns the _attributes of the file represented by this `DirEntry`. | |
| Note that the file _attributes on Windows and POSIX systems are | |
| completely different. On, Windows, they're what is returned by | |
| `GetFileAttributes` | |
| $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx, GetFileAttributes) | |
| Whereas, an POSIX systems, they're the `st_mode` value which is | |
| part of the `stat` struct gotten by calling `stat`. | |
| On POSIX systems, if the file represented by this `DirEntry` is a | |
| symbolic link, then _attributes are the _attributes of the file | |
| pointed to by the symbolic link. | |
| +/ | |
| @property uint attributes() scope; | |
| /++ | |
| On POSIX systems, if the file represented by this `DirEntry` is a | |
| symbolic link, then `linkAttributes` are the attributes of the | |
| symbolic link itself. Otherwise, `linkAttributes` is identical to | |
| `attributes`. | |
| On Windows, `linkAttributes` is identical to `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() scope; | |
| version (Windows) | |
| alias stat_t = void*; | |
| /++ | |
| $(BLUE This function is POSIX-Only.) | |
| The `stat` struct gotten from calling `stat`. | |
| +/ | |
| @property stat_t statBuf() scope; | |
| } | |
| } | |
| else version (Windows) | |
| { | |
| struct DirEntry | |
| { | |
| @safe: | |
| public: | |
| alias name this; | |
| this(return scope 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, WIN32_FIND_DATAW *fd) @trusted | |
| { | |
| import core.stdc.wchar_ : wcslen; | |
| import std.conv : to; | |
| import std.datetime.systime : FILETIMEToSysTime; | |
| import std.path : buildPath; | |
| fd.cFileName[$ - 1] = 0; | |
| size_t clength = wcslen(&fd.cFileName[0]); | |
| _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 scope | |
| { | |
| return _name; | |
| } | |
| @property bool isDir() const pure nothrow scope | |
| { | |
| return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; | |
| } | |
| @property bool isFile() const pure nothrow scope | |
| { | |
| //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 scope | |
| { | |
| return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; | |
| } | |
| @property ulong size() const pure nothrow scope | |
| { | |
| return _size; | |
| } | |
| @property SysTime timeCreated() const pure nothrow return scope | |
| { | |
| return cast(SysTime)_timeCreated; | |
| } | |
| @property SysTime timeLastAccessed() const pure nothrow return scope | |
| { | |
| return cast(SysTime)_timeLastAccessed; | |
| } | |
| @property SysTime timeLastModified() const pure nothrow return scope | |
| { | |
| return cast(SysTime)_timeLastModified; | |
| } | |
| @property uint attributes() const pure nothrow scope | |
| { | |
| return _attributes; | |
| } | |
| @property uint linkAttributes() const pure nothrow scope | |
| { | |
| 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 | |
| { | |
| @safe: | |
| public: | |
| alias name this; | |
| this(return scope 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) @safe | |
| { | |
| 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 scope | |
| { | |
| return _name; | |
| } | |
| @property bool isDir() scope | |
| { | |
| _ensureStatOrLStatDone(); | |
| return (_statBuf.st_mode & S_IFMT) == S_IFDIR; | |
| } | |
| @property bool isFile() scope | |
| { | |
| _ensureStatOrLStatDone(); | |
| return (_statBuf.st_mode & S_IFMT) == S_IFREG; | |
| } | |
| @property bool isSymlink() scope | |
| { | |
| _ensureLStatDone(); | |
| return (_lstatMode & S_IFMT) == S_IFLNK; | |
| } | |
| @property ulong size() scope | |
| { | |
| _ensureStatDone(); | |
| return _statBuf.st_size; | |
| } | |
| @property SysTime timeStatusChanged() scope | |
| { | |
| _ensureStatDone(); | |
| return statTimeToStdTime!'c'(_statBuf); | |
| } | |
| @property SysTime timeLastAccessed() scope | |
| { | |
| _ensureStatDone(); | |
| return statTimeToStdTime!'a'(_statBuf); | |
| } | |
| @property SysTime timeLastModified() scope | |
| { | |
| _ensureStatDone(); | |
| return statTimeToStdTime!'m'(_statBuf); | |
| } | |
| @property uint attributes() scope | |
| { | |
| _ensureStatDone(); | |
| return _statBuf.st_mode; | |
| } | |
| @property uint linkAttributes() scope | |
| { | |
| _ensureLStatDone(); | |
| return _lstatMode; | |
| } | |
| @property stat_t statBuf() scope | |
| { | |
| _ensureStatDone(); | |
| return _statBuf; | |
| } | |
| private: | |
| /++ | |
| This is to support lazy evaluation, because doing stat's is | |
| expensive and not always needed. | |
| +/ | |
| void _ensureStatDone() @trusted scope | |
| { | |
| if (_didStat) | |
| return; | |
| cenforce(stat(_name.tempCString(), &_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() @trusted scope | |
| { | |
| 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() @trusted scope | |
| { | |
| if (_didLStat) | |
| return; | |
| stat_t statbuf = void; | |
| cenforce(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); | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=8298 | |
| DirEntry de = DirEntry(symfile); | |
| assert(!de.isFile); | |
| assert(!de.isDir); | |
| assert(de.isSymlink); | |
| assertThrown!FileException(de.size); | |
| assertThrown!FileException(de.timeStatusChanged); | |
| assertThrown!FileException(de.timeLastAccessed); | |
| assertThrown!FileException(de.timeLastModified); | |
| assertThrown!FileException(de.attributes); | |
| assertThrown!FileException(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 `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 `from` _to file `to`. File timestamps are preserved. | |
| File attributes are preserved, if `preserve` equals `Yes.preserveAttributes`. | |
| On Windows only `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: $(LREF FileException) on error. | |
| */ | |
| void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault) | |
| if (isSomeFiniteCharInputRange!RF && !isConvertibleToString!RF && | |
| isSomeFiniteCharInputRange!RT && !isConvertibleToString!RT) | |
| { | |
| // Place outside of @trusted block | |
| auto fromz = from.tempCString!FSChar(); | |
| auto toz = to.tempCString!FSChar(); | |
| static if (isNarrowString!RF && is(immutable ElementEncodingType!RF == immutable char)) | |
| alias f = from; | |
| else | |
| enum string f = null; | |
| static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable 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 | |
| { | |
| auto source = deleteme ~ "source"; | |
| auto target = deleteme ~ "target"; | |
| auto targetNonExistent = deleteme ~ "target2"; | |
| scope(exit) source.remove, target.remove, targetNonExistent.remove; | |
| source.write("source"); | |
| target.write("target"); | |
| assert(target.readText == "target"); | |
| source.copy(target); | |
| assert(target.readText == "source"); | |
| source.copy(targetNonExistent); | |
| assert(targetNonExistent.readText == "source"); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=15319 | |
| @safe unittest | |
| { | |
| assert(__traits(compiles, copy("from.txt", "to.txt"))); | |
| } | |
| private void copyImpl(scope const(char)[] f, scope const(char)[] t, | |
| scope const(FSChar)* fromz, scope 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; | |
| import std.format : format; | |
| /++ | |
| Reference resources: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew | |
| Because OS copyfilew handles both source and destination paths, | |
| the GetLastError does not accurately locate whether the error is for the source or destination. | |
| +/ | |
| if (!f) | |
| f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]); | |
| if (!t) | |
| t = to!(typeof(t))(toz[0 .. wcslen(toz)]); | |
| throw new FileException(format!"Copy from %s to %s"(f, 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); | |
| setTimesImpl(t, toz, statbufr.statTimeToStdTime!'a', statbufr.statTimeToStdTime!'m'); | |
| } | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=14817 | |
| @safe unittest | |
| { | |
| import std.algorithm, std.file; | |
| 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"); | |
| // https://issues.dlang.org/show_bug.cgi?id=20370 | |
| version (Windows) | |
| assert(t1.timeLastModified == t2.timeLastModified); | |
| else static if (is(typeof(&utimensat)) || is(typeof(&setattrlist))) | |
| assert(t1.timeLastModified == t2.timeLastModified); | |
| else | |
| assert(abs(t1.timeLastModified - t2.timeLastModified) < dur!"usecs"(1)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=11434 | |
| @safe version (Posix) @safe unittest | |
| { | |
| 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); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=15865 | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| auto t = deleteme; | |
| write(t, "a"); | |
| scope(exit) t.remove(); | |
| assertThrown!FileException(copy(t, t)); | |
| assert(readText(t) == "a"); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=19834 | |
| version (Windows) @safe unittest | |
| { | |
| import std.exception : collectException; | |
| import std.algorithm.searching : startsWith; | |
| import std.format : format; | |
| auto f = deleteme; | |
| auto t = f ~ "2"; | |
| auto ex = collectException(copy(f, t)); | |
| assert(ex.msg.startsWith(format!"Copy from %s to %s"(f, t))); | |
| } | |
| /++ | |
| Remove directory and all of its content and subdirectories, | |
| recursively. | |
| Params: | |
| pathname = the path of the directory to completely remove | |
| de = The $(LREF DirEntry) to remove | |
| Throws: | |
| $(LREF FileException) if there is an error (including if the given | |
| file is not a directory). | |
| +/ | |
| void rmdirRecurse(scope const(char)[] pathname) @safe | |
| { | |
| //No references to pathname will be kept after rmdirRecurse, | |
| //so the cast is safe | |
| rmdirRecurse(DirEntry((() @trusted => cast(string) pathname)())); | |
| } | |
| /// ditto | |
| void rmdirRecurse(ref scope DirEntry de) @safe | |
| { | |
| if (!de.isDir) | |
| throw new FileException(de.name, "Not a directory"); | |
| if (de.isSymlink) | |
| { | |
| version (Windows) | |
| rmdir(de.name); | |
| else | |
| remove(de.name); | |
| } | |
| else | |
| { | |
| // dirEntries is @system without DIP1000 because it uses | |
| // a DirIterator with a SafeRefCounted variable, but here, no | |
| // references to the payload are escaped to the outside, so this should | |
| // be @trusted | |
| () @trusted { | |
| // 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(scope DirEntry de) @safe | |
| { | |
| rmdirRecurse(de); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.path : buildPath; | |
| auto dir = deleteme.buildPath("a", "b", "c"); | |
| dir.mkdirRecurse; | |
| assert(dir.exists); | |
| deleteme.rmdirRecurse; | |
| assert(!dir.exists); | |
| assert(!deleteme.exists); | |
| } | |
| 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; | |
| 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); | |
| const linkTarget = deleteme ~ "/link"; | |
| symlink(deleteme ~ "/a/b/c", linkTarget); | |
| rmdirRecurse(deleteme); | |
| enforce(!exists(deleteme)); | |
| } | |
| @safe unittest | |
| { | |
| ubyte[] buf = new ubyte[10]; | |
| buf[] = 3; | |
| string unit_file = deleteme ~ "-unittest_write.tmp"; | |
| if (exists(unit_file)) remove(unit_file); | |
| write(unit_file, cast(void[]) buf); | |
| void[] buf2 = read(unit_file); | |
| assert(cast(void[]) buf == buf2); | |
| string unit2_file = deleteme ~ "-unittest_write2.tmp"; | |
| copy(unit_file, unit2_file); | |
| buf2 = read(unit2_file); | |
| assert(cast(void[]) 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 `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, | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.algorithm.iteration : map; | |
| import std.algorithm.sorting : sort; | |
| import std.array : array; | |
| import std.path : buildPath, relativePath; | |
| auto root = deleteme ~ "root"; | |
| scope(exit) root.rmdirRecurse; | |
| root.mkdir; | |
| root.buildPath("animals").mkdir; | |
| root.buildPath("animals", "cat").mkdir; | |
| alias removeRoot = (return scope e) => e.relativePath(root); | |
| assert(root.dirEntries(SpanMode.depth).map!removeRoot.equal( | |
| [buildPath("animals", "cat"), "animals"])); | |
| assert(root.dirEntries(SpanMode.breadth).map!removeRoot.equal( | |
| ["animals", buildPath("animals", "cat")])); | |
| root.buildPath("plants").mkdir; | |
| assert(root.dirEntries(SpanMode.shallow).array.sort.map!removeRoot.equal( | |
| ["animals", "plants"])); | |
| } | |
| private struct DirIteratorImpl | |
| { | |
| @safe: | |
| 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; | |
| DirHandle[] _stack; | |
| DirEntry[] _stashed; //used in depth first mode | |
| //stack helpers | |
| void pushExtra(DirEntry de) | |
| { | |
| _stashed ~= de; | |
| } | |
| //ditto | |
| bool hasExtra() | |
| { | |
| return _stashed.length != 0; | |
| } | |
| //ditto | |
| DirEntry popExtra() | |
| { | |
| DirEntry de; | |
| de = _stashed[$-1]; | |
| _stashed.popBack(); | |
| return de; | |
| } | |
| version (Windows) | |
| { | |
| WIN32_FIND_DATAW _findinfo; | |
| struct DirHandle | |
| { | |
| string dirpath; | |
| HANDLE h; | |
| } | |
| bool stepIn(string directory) @safe | |
| { | |
| import std.path : chainPath; | |
| auto searchPattern = chainPath(directory, "*.*"); | |
| static auto trustedFindFirstFileW(typeof(searchPattern) pattern, scope WIN32_FIND_DATAW* findinfo) @trusted | |
| { | |
| return FindFirstFileW(pattern.tempCString!FSChar(), findinfo); | |
| } | |
| HANDLE h = trustedFindFirstFileW(searchPattern, &_findinfo); | |
| cenforce(h != INVALID_HANDLE_VALUE, directory); | |
| _stack ~= DirHandle(directory, h); | |
| return toNext(false, &_findinfo); | |
| } | |
| bool next() | |
| { | |
| if (_stack.length == 0) | |
| return false; | |
| return toNext(true, &_findinfo); | |
| } | |
| bool toNext(bool fetch, scope WIN32_FIND_DATAW* findinfo) @trusted | |
| { | |
| import core.stdc.wchar_ : wcscmp; | |
| if (fetch) | |
| { | |
| if (FindNextFileW(_stack[$-1].h, findinfo) == FALSE) | |
| { | |
| popDirStack(); | |
| return false; | |
| } | |
| } | |
| while (wcscmp(&findinfo.cFileName[0], ".") == 0 || | |
| wcscmp(&findinfo.cFileName[0], "..") == 0) | |
| if (FindNextFileW(_stack[$-1].h, findinfo) == FALSE) | |
| { | |
| popDirStack(); | |
| return false; | |
| } | |
| _cur = DirEntry(_stack[$-1].dirpath, findinfo); | |
| return true; | |
| } | |
| void popDirStack() @trusted | |
| { | |
| assert(_stack.length != 0); | |
| FindClose(_stack[$-1].h); | |
| _stack.popBack(); | |
| } | |
| void releaseDirStack() @trusted | |
| { | |
| foreach (d; _stack) | |
| 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) | |
| { | |
| static auto trustedOpendir(string dir) @trusted | |
| { | |
| return opendir(dir.tempCString()); | |
| } | |
| auto h = directory.length ? trustedOpendir(directory) : trustedOpendir("."); | |
| cenforce(h, directory); | |
| _stack ~= (DirHandle(directory, h)); | |
| return next(); | |
| } | |
| bool next() @trusted | |
| { | |
| if (_stack.length == 0) | |
| return false; | |
| for (dirent* fdata; (fdata = readdir(_stack[$-1].h)) != null; ) | |
| { | |
| // Skip "." and ".." | |
| if (core.stdc.string.strcmp(&fdata.d_name[0], ".") && | |
| core.stdc.string.strcmp(&fdata.d_name[0], "..")) | |
| { | |
| _cur = DirEntry(_stack[$-1].dirpath, fdata); | |
| return true; | |
| } | |
| } | |
| popDirStack(); | |
| return false; | |
| } | |
| void popDirStack() @trusted | |
| { | |
| assert(_stack.length != 0); | |
| closedir(_stack[$-1].h); | |
| _stack.popBack(); | |
| } | |
| void releaseDirStack() @trusted | |
| { | |
| foreach (d; _stack) | |
| closedir(d.h); | |
| } | |
| bool mayStepIn() | |
| { | |
| return _followSymlink ? _cur.isDir : attrIsDir(_cur.linkAttributes); | |
| } | |
| } | |
| this(R)(R pathname, SpanMode mode, bool followSymlink) | |
| if (isSomeFiniteCharInputRange!R) | |
| { | |
| _mode = mode; | |
| _followSymlink = followSymlink; | |
| static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable 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.length == 0 && _stack.length == 0; | |
| } | |
| @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(); | |
| } | |
| } | |
| // Must be a template, because the destructor is unsafe or safe depending on | |
| // whether `-preview=dip1000` is in use. Otherwise, linking errors would | |
| // result. | |
| struct _DirIterator(bool useDIP1000) | |
| { | |
| static assert(useDIP1000 == dip1000Enabled, | |
| "Please don't override useDIP1000 to disagree with compiler switch."); | |
| private: | |
| SafeRefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl; | |
| this(string pathname, SpanMode mode, bool followSymlink) @trusted | |
| { | |
| impl = typeof(impl)(pathname, mode, followSymlink); | |
| } | |
| public: | |
| @property bool empty() @trusted { return impl.empty; } | |
| @property DirEntry front() @trusted { return impl.front; } | |
| void popFront() @trusted { impl.popFront(); } | |
| } | |
| // This has the client code to automatically use and link to the correct | |
| // template instance | |
| alias DirIterator = _DirIterator!dip1000Enabled; | |
| /++ | |
| Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| of `DirEntry` that lazily iterates a given directory, | |
| also provides two ways of foreach iteration. The iteration variable can be of | |
| type `string` if only the name is needed, or `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 or relative _path (depending on _pathname). | |
| Note: The order of returned directory entries is as it is provided by the | |
| operating system / filesystem, and may not follow any particular sorting. | |
| Params: | |
| useDIP1000 = used to instantiate this function separately for code with | |
| and without -preview=dip1000 compiler switch, because it | |
| affects the ABI of this function. Set automatically - | |
| don't touch. | |
| 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 post-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. | |
| Returns: | |
| An $(REF_ALTTEXT input range, isInputRange,std,range,primitives) of | |
| $(LREF DirEntry). | |
| Throws: | |
| $(UL | |
| $(LI $(LREF FileException) if the $(B path) directory does not exist or read permission is denied.) | |
| $(LI $(LREF FileException) if $(B mode) is not `shallow` and a subdirectory cannot be read.) | |
| ) | |
| 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.executeShell(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); | |
| -------------------- | |
| To handle subdirectories with denied read permission, use `SpanMode.shallow`: | |
| --- | |
| void scan(string path) | |
| { | |
| foreach (DirEntry entry; dirEntries(path, SpanMode.shallow)) | |
| { | |
| try | |
| { | |
| writeln(entry.name); | |
| if (entry.isDir) | |
| scan(entry.name); | |
| } | |
| catch (FileException fe) { continue; } // ignore | |
| } | |
| } | |
| scan(""); | |
| --- | |
| +/ | |
| // For some reason, doing the same alias-to-a-template trick as with DirIterator | |
| // does not work here. | |
| auto dirEntries(bool useDIP1000 = dip1000Enabled) | |
| (string path, SpanMode mode, bool followSymlink = true) | |
| { | |
| return _DirIterator!useDIP1000(path, mode, followSymlink); | |
| } | |
| /// Duplicate functionality of D1's `std.file.listdir()`: | |
| @safe unittest | |
| { | |
| string[] listdir(string pathname) | |
| { | |
| import std.algorithm.iteration : map, filter; | |
| import std.array : array; | |
| import std.path : baseName; | |
| return dirEntries(pathname, SpanMode.shallow) | |
| .filter!(a => a.isFile) | |
| .map!((return a) => baseName(a.name)) | |
| .array; | |
| } | |
| // Can be safe only with -preview=dip1000 | |
| @safe void main(string[] args) | |
| { | |
| import std.stdio : writefln; | |
| 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 : buildPath, absolutePath; | |
| import std.file : dirEntries; | |
| 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 = tempDir.buildPath("deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID)); | |
| 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!((return 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); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=7264 | |
| foreach (string name; dirEntries(testdir, "*.d", SpanMode.breadth)) | |
| { | |
| } | |
| foreach (entry; dirEntries(testdir, SpanMode.breadth)) | |
| { | |
| static assert(is(typeof(entry) == DirEntry)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=7138 | |
| auto a = array(dirEntries(testdir, SpanMode.shallow)); | |
| // https://issues.dlang.org/show_bug.cgi?id=11392 | |
| auto dFiles = dirEntries(testdir, SpanMode.shallow); | |
| foreach (d; dFiles){} | |
| // https://issues.dlang.org/show_bug.cgi?id=15146 | |
| dirEntries("", SpanMode.shallow).walkLength(); | |
| } | |
| /// Ditto | |
| auto dirEntries(bool useDIP1000 = dip1000Enabled) | |
| (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!useDIP1000(path, mode, followSymlink)); | |
| } | |
| @safe 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) () @trusted | |
| { | |
| 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); | |
| } | |
| } | |
| } | |
| // Make sure that dirEntries does not butcher Unicode file names | |
| // https://issues.dlang.org/show_bug.cgi?id=17962 | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.algorithm.iteration : map; | |
| import std.algorithm.sorting : sort; | |
| import std.array : array; | |
| import std.path : buildPath; | |
| import std.uni : normalize; | |
| // The Unicode normalization is required to make the tests pass on Mac OS X. | |
| auto dir = deleteme ~ normalize("𐐷"); | |
| scope(exit) if (dir.exists) rmdirRecurse(dir); | |
| mkdir(dir); | |
| auto files = ["Hello World", | |
| "Ma Chérie.jpeg", | |
| "さいごの果実.txt"].map!(a => buildPath(dir, normalize(a)))().array(); | |
| sort(files); | |
| foreach (file; files) | |
| write(file, "nothing"); | |
| auto result = dirEntries(dir, SpanMode.shallow).map!((return a) => a.name.normalize()).array(); | |
| sort(result); | |
| assert(equal(files, result)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=21250 | |
| @system unittest | |
| { | |
| import std.exception : assertThrown; | |
| assertThrown!Exception(dirEntries("237f5babd6de21f40915826699582e36", "*.bin", SpanMode.depth)); | |
| } | |
| /** | |
| * 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, scope const(char)[] format) | |
| { | |
| import std.array : appender; | |
| import std.conv : text; | |
| import std.exception : enforce; | |
| import std.format.read : formattedRead; | |
| import std.stdio : File; | |
| import std.string : stripRight; | |
| 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.stripRight("\r").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)); | |
| } | |
| @system unittest | |
| { | |
| import std.typecons : tuple; | |
| scope(exit) | |
| { | |
| assert(exists(deleteme)); | |
| remove(deleteme); | |
| } | |
| write(deleteme, "10\r\n20"); | |
| assert(slurp!(int)(deleteme, "%d") == [10, 20]); | |
| } | |
| /** | |
| Returns the path to a directory for temporary files. | |
| 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 `TMPDIR` environment variable.) | |
| $(LI The directory given by the `TEMP` environment variable.) | |
| $(LI The directory given by the `TMP` environment variable.) | |
| $(LI `/tmp/`) | |
| $(LI `/var/tmp/`) | |
| $(LI `/usr/tmp/`) | |
| ) | |
| On all platforms, `tempDir` returns the current working directory on failure. | |
| The return value of the function is cached, so the procedures described | |
| below 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 `tempDir` algorithm is inspired by Python's | |
| $(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, `tempfile.tempdir`). | |
| Returns: | |
| 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, `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 `TMPDIR` environment variable.) | |
| $(LI The directory given by the `TEMP` environment variable.) | |
| $(LI The directory given by the `TMP` environment variable.) | |
| $(LI `/tmp`) | |
| $(LI `/var/tmp`) | |
| $(LI `/usr/tmp`) | |
| ) | |
| On all platforms, `tempDir` returns `"."` on failure, representing | |
| the current working directory. | |
| */ | |
| string tempDir() @trusted | |
| { | |
| // We must check that the end of a path is not a separator, before adding another | |
| // If we don't we end up with https://issues.dlang.org/show_bug.cgi?id=22738 | |
| static string addSeparator(string input) | |
| { | |
| import std.path : dirSeparator; | |
| import std.algorithm.searching : endsWith; | |
| // It is very rare a directory path will reach this point with a directory separator at the end | |
| // However on OSX this can happen, so we must verify lest we break user code i.e. https://github.com/dlang/dub/pull/2208 | |
| if (!input.endsWith(dirSeparator)) | |
| return input ~ dirSeparator; | |
| else | |
| return input; | |
| } | |
| 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 addSeparator(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 = addSeparator(getcwd()); | |
| } | |
| } | |
| return cache; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.ascii : letters; | |
| import std.conv : to; | |
| import std.path : buildPath; | |
| import std.random : randomSample; | |
| import std.utf : byCodeUnit; | |
| // random id with 20 letters | |
| auto id = letters.byCodeUnit.randomSample(20).to!string; | |
| auto myFile = tempDir.buildPath(id ~ "my_tmp_file"); | |
| scope(exit) myFile.remove; | |
| myFile.write("hello"); | |
| assert(myFile.readText == "hello"); | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.searching : endsWith; | |
| import std.path : dirSeparator; | |
| assert(tempDir.endsWith(dirSeparator)); | |
| // https://issues.dlang.org/show_bug.cgi?id=22738 | |
| assert(!tempDir.endsWith(dirSeparator ~ dirSeparator)); | |
| } | |
| /** | |
| Returns the available disk space based on a given path. | |
| On Windows, `path` must be a directory; on POSIX systems, it can be a file or directory. | |
| Params: | |
| path = on Windows, it must be a directory; on POSIX it can be a file or directory | |
| Returns: | |
| Available space in bytes | |
| Throws: | |
| $(LREF FileException) in case of failure | |
| */ | |
| ulong getAvailableDiskSpace(scope const(char)[] path) @safe | |
| { | |
| version (Windows) | |
| { | |
| import core.sys.windows.winbase : GetDiskFreeSpaceExW; | |
| import core.sys.windows.winnt : ULARGE_INTEGER; | |
| import std.internal.cstring : tempCStringW; | |
| ULARGE_INTEGER freeBytesAvailable; | |
| auto err = () @trusted { | |
| return GetDiskFreeSpaceExW(path.tempCStringW(), &freeBytesAvailable, null, null); | |
| } (); | |
| cenforce(err != 0, "Cannot get available disk space"); | |
| return freeBytesAvailable.QuadPart; | |
| } | |
| else version (Posix) | |
| { | |
| import std.internal.cstring : tempCString; | |
| version (FreeBSD) | |
| { | |
| import core.sys.freebsd.sys.mount : statfs, statfs_t; | |
| statfs_t stats; | |
| auto err = () @trusted { | |
| return statfs(path.tempCString(), &stats); | |
| } (); | |
| cenforce(err == 0, "Cannot get available disk space"); | |
| return stats.f_bavail * stats.f_bsize; | |
| } | |
| else | |
| { | |
| import core.sys.posix.sys.statvfs : statvfs, statvfs_t; | |
| statvfs_t stats; | |
| auto err = () @trusted { | |
| return statvfs(path.tempCString(), &stats); | |
| } (); | |
| cenforce(err == 0, "Cannot get available disk space"); | |
| return stats.f_bavail * stats.f_frsize; | |
| } | |
| } | |
| else static assert(0, "Unsupported platform"); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| auto space = getAvailableDiskSpace("."); | |
| assert(space > 0); | |
| assertThrown!FileException(getAvailableDiskSpace("ThisFileDoesNotExist123123")); | |
| } |