| // 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.idup, file, line); |
| else |
| super(text(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. |
| */ |
| |
| 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. |
| +/ |
| 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 = to!(typeof(f))(fromz[0 .. wcslen(fromz)]); |
| |
| if (!t) |
| t = to!(typeof(t))(toz[0 .. wcslen(toz)]); |
| |
| enforce(false, |
| new FileException( |
| text("Attempting to rename file ", f, " to ", t))); |
| } |
| } |
| else version (Posix) |
| { |
| static import core.stdc.stdio; |
| |
| cenforce(core.stdc.stdio.rename(fromz, toz) == 0, t, toz); |
| } |
| } |
| |
| @safe unittest |
| { |
| import std.utf : byWchar; |
| |
| auto t1 = deleteme, t2 = deleteme~"2"; |
| scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove(); |
| |
| write(t1, "1"); |
| rename(t1, t2); |
| assert(readText(t2) == "1"); |
| |
| write(t1, "2"); |
| rename(t1, t2.byWchar); |
| assert(readText(t2) == "2"); |
| } |
| |
| /*************************************************** |
| Delete file `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 = strlen(namez); |
| name = namez[0 .. len]; |
| } |
| cenforce(core.stdc.stdio.remove(namez) == 0, |
| "Failed to remove file " ~ name); |
| } |
| } |
| |
| version (Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(R)(R name) |
| if (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(in 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!(GENERIC_WRITE, |
| 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); |
| } |
| |
| /++ |
| 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(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 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(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(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 |
| { |
| import std.exception : enforce; |
| |
| if (_didStat) |
| return; |
| |
| enforce(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 |
| { |
| import std.exception : enforce; |
| |
| if (_didLStat) |
| return; |
| |
| stat_t statbuf = void; |
| enforce(lstat(_name.tempCString(), &statbuf) == 0, |
| "Failed to stat file `" ~ _name ~ "'"); |
| |
| _lstatMode = statbuf.st_mode; |
| |
| _dTypeSet = true; |
| _didLStat = true; |
| } |
| |
| string _name; /// The file or directory represented by this DirEntry. |
| |
| stat_t _statBuf = void; /// The result of stat(). |
| uint _lstatMode; /// The stat mode from lstat(). |
| ubyte _dType; /// The type of the file. |
| |
| bool _didLStat = false; /// Whether lstat() has been called for this DirEntry. |
| bool _didStat = false; /// Whether stat() has been called for this DirEntry. |
| bool _dTypeSet = false; /// Whether the dType of the file has been set. |
| } |
| } |
| |
| @system unittest |
| { |
| version (Windows) |
| { |
| if ("C:\\Program Files\\".exists) |
| { |
| auto de = DirEntry("C:\\Program Files\\"); |
| assert(!de.isFile); |
| assert(de.isDir); |
| assert(!de.isSymlink); |
| } |
| |
| if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists) |
| { |
| auto de = DirEntry("C:\\Documents and Settings\\"); |
| assert(de.isSymlink); |
| } |
| |
| if ("C:\\Windows\\system.ini".exists) |
| { |
| auto de = DirEntry("C:\\Windows\\system.ini"); |
| assert(de.isFile); |
| assert(!de.isDir); |
| assert(!de.isSymlink); |
| } |
| } |
| else version (Posix) |
| { |
| import std.exception : assertThrown; |
| |
| if (system_directory.exists) |
| { |
| { |
| auto de = DirEntry(system_directory); |
| assert(!de.isFile); |
| assert(de.isDir); |
| assert(!de.isSymlink); |
| } |
| |
| immutable symfile = deleteme ~ "_slink\0"; |
| scope(exit) if (symfile.exists) symfile.remove(); |
| |
| core.sys.posix.unistd.symlink(system_directory, symfile.ptr); |
| |
| { |
| auto de = DirEntry(symfile); |
| assert(!de.isFile); |
| assert(de.isDir); |
| assert(de.isSymlink); |
| } |
| |
| symfile.remove(); |
| core.sys.posix.unistd.symlink((deleteme ~ "_broken_symlink\0").ptr, symfile.ptr); |
| |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=8298 |
| DirEntry de = DirEntry(symfile); |
| |
| assert(!de.isFile); |
| assert(!de.isDir); |
| assert(de.isSymlink); |
| assertThrown(de.size); |
| assertThrown(de.timeStatusChanged); |
| assertThrown(de.timeLastAccessed); |
| assertThrown(de.timeLastModified); |
| assertThrown(de.attributes); |
| assertThrown(de.statBuf); |
| assert(symfile.exists); |
| symfile.remove(); |
| } |
| } |
| |
| if (system_file.exists) |
| { |
| auto de = DirEntry(system_file); |
| assert(de.isFile); |
| assert(!de.isDir); |
| assert(!de.isSymlink); |
| } |
| } |
| } |
| |
| alias PreserveAttributes = Flag!"preserveAttributes"; |
| |
| version (StdDdoc) |
| { |
| /// Defaults to `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; |
| |