mirror of
https://github.com/dlang/phobos.git
synced 2025-04-26 13:10:35 +03:00
5527 lines
156 KiB
D
5527 lines
156 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
Utilities for manipulating files and scanning directories. Functions
|
|
in this module handle files as a unit, e.g., read or write one file
|
|
at a time. For opening files and manipulating them via handles refer
|
|
to module $(MREF std, stdio).
|
|
|
|
$(SCRIPT inhibitQuickIndex = 1;)
|
|
$(DIVC quickindex,
|
|
$(BOOKTABLE,
|
|
$(TR $(TH Category) $(TH Functions))
|
|
$(TR $(TD General) $(TD
|
|
$(LREF exists)
|
|
$(LREF isDir)
|
|
$(LREF isFile)
|
|
$(LREF isSymlink)
|
|
$(LREF rename)
|
|
$(LREF thisExePath)
|
|
))
|
|
$(TR $(TD Directories) $(TD
|
|
$(LREF chdir)
|
|
$(LREF dirEntries)
|
|
$(LREF getcwd)
|
|
$(LREF mkdir)
|
|
$(LREF mkdirRecurse)
|
|
$(LREF rmdir)
|
|
$(LREF rmdirRecurse)
|
|
$(LREF tempDir)
|
|
))
|
|
$(TR $(TD Files) $(TD
|
|
$(LREF append)
|
|
$(LREF copy)
|
|
$(LREF read)
|
|
$(LREF readText)
|
|
$(LREF remove)
|
|
$(LREF slurp)
|
|
$(LREF write)
|
|
))
|
|
$(TR $(TD Symlinks) $(TD
|
|
$(LREF symlink)
|
|
$(LREF readLink)
|
|
))
|
|
$(TR $(TD Attributes) $(TD
|
|
$(LREF attrIsDir)
|
|
$(LREF attrIsFile)
|
|
$(LREF attrIsSymlink)
|
|
$(LREF getAttributes)
|
|
$(LREF getLinkAttributes)
|
|
$(LREF getSize)
|
|
$(LREF setAttributes)
|
|
))
|
|
$(TR $(TD Timestamp) $(TD
|
|
$(LREF getTimes)
|
|
$(LREF getTimesWin)
|
|
$(LREF setTimes)
|
|
$(LREF timeLastModified)
|
|
$(LREF timeLastAccessed)
|
|
$(LREF timeStatusChanged)
|
|
))
|
|
$(TR $(TD Other) $(TD
|
|
$(LREF DirEntry)
|
|
$(LREF FileException)
|
|
$(LREF PreserveAttributes)
|
|
$(LREF SpanMode)
|
|
$(LREF getAvailableDiskSpace)
|
|
))
|
|
))
|
|
|
|
|
|
Copyright: Copyright The D Language Foundation 2007 - 2011.
|
|
See_Also: The $(HTTP ddili.org/ders/d.en/files.html, official tutorial) for an
|
|
introduction to working with files in D, module
|
|
$(MREF std, stdio) for opening files and manipulating them via handles,
|
|
and module $(MREF std, path) for manipulating path strings.
|
|
|
|
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
Authors: $(HTTP digitalmars.com, Walter Bright),
|
|
$(HTTP erdani.org, Andrei Alexandrescu),
|
|
$(HTTP jmdavisprog.com, Jonathan M Davis)
|
|
Source: $(PHOBOSSRC std/file.d)
|
|
*/
|
|
module std.file;
|
|
|
|
import core.stdc.errno, core.stdc.stdlib, core.stdc.string;
|
|
import core.time : abs, dur, hnsecs, seconds;
|
|
|
|
import std.datetime.date : DateTime;
|
|
import std.datetime.systime : Clock, SysTime, unixTimeToStdTime;
|
|
import std.internal.cstring;
|
|
import std.meta;
|
|
import std.range;
|
|
import std.traits;
|
|
import std.typecons;
|
|
|
|
version (OSX)
|
|
version = Darwin;
|
|
else version (iOS)
|
|
version = Darwin;
|
|
else version (TVOS)
|
|
version = Darwin;
|
|
else version (WatchOS)
|
|
version = Darwin;
|
|
|
|
version (Windows)
|
|
{
|
|
import core.sys.windows.winbase, core.sys.windows.winnt, std.windows.syserror;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
import core.sys.posix.dirent, core.sys.posix.fcntl, core.sys.posix.sys.stat,
|
|
core.sys.posix.sys.time, core.sys.posix.unistd, core.sys.posix.utime;
|
|
}
|
|
else
|
|
static assert(false, "Module " ~ .stringof ~ " not implemented for this OS.");
|
|
|
|
// Character type used for operating system filesystem APIs
|
|
version (Windows)
|
|
{
|
|
private alias FSChar = WCHAR; // WCHAR can be aliased to wchar or wchar_t
|
|
}
|
|
else version (Posix)
|
|
{
|
|
private alias FSChar = char;
|
|
}
|
|
else
|
|
static assert(0);
|
|
|
|
// Purposefully not documented. Use at your own risk
|
|
@property string deleteme() @safe
|
|
{
|
|
import std.conv : text;
|
|
import std.path : buildPath;
|
|
import std.process : thisProcessID;
|
|
|
|
enum base = "deleteme.dmd.unittest.pid";
|
|
static string fileName;
|
|
|
|
if (!fileName)
|
|
fileName = text(buildPath(tempDir(), base), thisProcessID);
|
|
return fileName;
|
|
}
|
|
|
|
version (StdUnittest) private struct TestAliasedString
|
|
{
|
|
string get() @safe @nogc pure nothrow return scope { return _s; }
|
|
alias get this;
|
|
@disable this(this);
|
|
string _s;
|
|
}
|
|
|
|
version (Android)
|
|
{
|
|
package enum system_directory = "/system/etc";
|
|
package enum system_file = "/system/etc/hosts";
|
|
}
|
|
else version (Posix)
|
|
{
|
|
package enum system_directory = "/usr/include";
|
|
package enum system_file = "/usr/include/assert.h";
|
|
}
|
|
|
|
|
|
/++
|
|
Exception thrown for file I/O errors.
|
|
+/
|
|
class FileException : Exception
|
|
{
|
|
import std.conv : text, to;
|
|
|
|
/++
|
|
OS error code.
|
|
+/
|
|
immutable uint errno;
|
|
|
|
private this(scope const(char)[] name, scope const(char)[] msg, string file, size_t line, uint errno) @safe pure
|
|
{
|
|
if (msg.empty)
|
|
super(name is null ? "(null)" : name.idup, file, line);
|
|
else
|
|
super(text(name is null ? "(null)" : name, ": ", msg), file, line);
|
|
|
|
this.errno = errno;
|
|
}
|
|
|
|
/++
|
|
Constructor which takes an error message.
|
|
|
|
Params:
|
|
name = Name of file for which the error occurred.
|
|
msg = Message describing the error.
|
|
file = The file where the error occurred.
|
|
line = The _line where the error occurred.
|
|
+/
|
|
this(scope const(char)[] name, scope const(char)[] msg, string file = __FILE__, size_t line = __LINE__) @safe pure
|
|
{
|
|
this(name, msg, file, line, 0);
|
|
}
|
|
|
|
/++
|
|
Constructor which takes the error number ($(LUCKY GetLastError)
|
|
in Windows, $(D_PARAM errno) in POSIX).
|
|
|
|
Params:
|
|
name = Name of file for which the error occurred.
|
|
errno = The error number.
|
|
file = The file where the error occurred.
|
|
Defaults to `__FILE__`.
|
|
line = The _line where the error occurred.
|
|
Defaults to `__LINE__`.
|
|
+/
|
|
version (Windows) this(scope const(char)[] name,
|
|
uint errno = .GetLastError(),
|
|
string file = __FILE__,
|
|
size_t line = __LINE__) @safe
|
|
{
|
|
this(name, generateSysErrorMsg(errno), file, line, errno);
|
|
}
|
|
else version (Posix) this(scope const(char)[] name,
|
|
uint errno = .errno,
|
|
string file = __FILE__,
|
|
size_t line = __LINE__) @trusted
|
|
{
|
|
import std.exception : errnoString;
|
|
this(name, errnoString(errno), file, line, errno);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
assertThrown!FileException("non.existing.file.".readText);
|
|
}
|
|
|
|
private T cenforce(T)(T condition, lazy scope const(char)[] name, string file = __FILE__, size_t line = __LINE__)
|
|
{
|
|
if (condition)
|
|
return condition;
|
|
version (Windows)
|
|
{
|
|
throw new FileException(name, .GetLastError(), file, line);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
throw new FileException(name, .errno, file, line);
|
|
}
|
|
}
|
|
|
|
version (Windows)
|
|
@trusted
|
|
private T cenforce(T)(T condition, scope const(char)[] name, scope const(FSChar)* namez,
|
|
string file = __FILE__, size_t line = __LINE__)
|
|
{
|
|
if (condition)
|
|
return condition;
|
|
if (!name)
|
|
{
|
|
import core.stdc.wchar_ : wcslen;
|
|
import std.conv : to;
|
|
|
|
auto len = namez ? wcslen(namez) : 0;
|
|
name = to!string(namez[0 .. len]);
|
|
}
|
|
throw new FileException(name, .GetLastError(), file, line);
|
|
}
|
|
|
|
version (Posix)
|
|
@trusted
|
|
private T cenforce(T)(T condition, scope const(char)[] name, scope const(FSChar)* namez,
|
|
string file = __FILE__, size_t line = __LINE__)
|
|
{
|
|
if (condition)
|
|
return condition;
|
|
if (!name)
|
|
{
|
|
import core.stdc.string : strlen;
|
|
|
|
auto len = namez ? strlen(namez) : 0;
|
|
name = namez[0 .. len].idup;
|
|
}
|
|
throw new FileException(name, .errno, file, line);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=17102
|
|
@safe unittest
|
|
{
|
|
try
|
|
{
|
|
cenforce(false, null, null,
|
|
__FILE__, __LINE__);
|
|
}
|
|
catch (FileException) {}
|
|
}
|
|
|
|
/* **********************************
|
|
* Basic File operations.
|
|
*/
|
|
|
|
/********************************************
|
|
Read entire contents of file `name` and returns it as an untyped
|
|
array. If the file size is larger than `upTo`, only `upTo`
|
|
bytes are _read.
|
|
|
|
Params:
|
|
name = string or range of characters representing the file _name
|
|
upTo = if present, the maximum number of bytes to _read
|
|
|
|
Returns: Untyped array of bytes _read.
|
|
|
|
Throws: $(LREF FileException) on error.
|
|
|
|
See_Also: $(REF readText, std,file) for reading and validating a text file.
|
|
*/
|
|
|
|
void[] read(R)(R name, size_t upTo = size_t.max)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
return readImpl(name, name.tempCString!FSChar(), upTo);
|
|
else
|
|
return readImpl(null, name.tempCString!FSChar(), upTo);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.utf : byChar;
|
|
scope(exit)
|
|
{
|
|
assert(exists(deleteme));
|
|
remove(deleteme);
|
|
}
|
|
|
|
std.file.write(deleteme, "1234"); // deleteme is the name of a temporary file
|
|
assert(read(deleteme, 2) == "12");
|
|
assert(read(deleteme.byChar) == "1234");
|
|
assert((cast(const(ubyte)[])read(deleteme)).length == 4);
|
|
}
|
|
|
|
/// ditto
|
|
void[] read(R)(auto ref R name, size_t upTo = size_t.max)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return read!(StringTypeOf!R)(name, upTo);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, read(TestAliasedString(null))));
|
|
}
|
|
|
|
version (Posix) private void[] readImpl(scope const(char)[] name, scope const(FSChar)* namez,
|
|
size_t upTo = size_t.max) @trusted
|
|
{
|
|
import core.memory : GC;
|
|
import std.algorithm.comparison : min;
|
|
import std.conv : to;
|
|
import std.checkedint : checked;
|
|
|
|
// A few internal configuration parameters {
|
|
enum size_t
|
|
minInitialAlloc = 1024 * 4,
|
|
maxInitialAlloc = size_t.max / 2,
|
|
sizeIncrement = 1024 * 16,
|
|
maxSlackMemoryAllowed = 1024;
|
|
// }
|
|
|
|
immutable fd = core.sys.posix.fcntl.open(namez,
|
|
core.sys.posix.fcntl.O_RDONLY);
|
|
cenforce(fd != -1, name);
|
|
scope(exit) core.sys.posix.unistd.close(fd);
|
|
|
|
stat_t statbuf = void;
|
|
cenforce(fstat(fd, &statbuf) == 0, name, namez);
|
|
|
|
immutable initialAlloc = min(upTo, to!size_t(statbuf.st_size
|
|
? min(statbuf.st_size + 1, maxInitialAlloc)
|
|
: minInitialAlloc));
|
|
void[] result = GC.malloc(initialAlloc, GC.BlkAttr.NO_SCAN)[0 .. initialAlloc];
|
|
scope(failure) GC.free(result.ptr);
|
|
|
|
auto size = checked(size_t(0));
|
|
|
|
for (;;)
|
|
{
|
|
immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size.get,
|
|
(min(result.length, upTo) - size).get);
|
|
cenforce(actual != -1, name, namez);
|
|
if (actual == 0) break;
|
|
size += actual;
|
|
if (size >= upTo) break;
|
|
if (size < result.length) continue;
|
|
immutable newAlloc = size + sizeIncrement;
|
|
result = GC.realloc(result.ptr, newAlloc.get, GC.BlkAttr.NO_SCAN)[0 .. newAlloc.get];
|
|
}
|
|
|
|
return result.length - size >= maxSlackMemoryAllowed
|
|
? GC.realloc(result.ptr, size.get, GC.BlkAttr.NO_SCAN)[0 .. size.get]
|
|
: result[0 .. size.get];
|
|
}
|
|
|
|
version (Windows)
|
|
private extern (Windows) @nogc nothrow
|
|
{
|
|
pragma(mangle, CreateFileW.mangleof)
|
|
HANDLE trustedCreateFileW(scope const(wchar)* namez, DWORD dwDesiredAccess,
|
|
DWORD dwShareMode, SECURITY_ATTRIBUTES* lpSecurityAttributes,
|
|
DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
|
|
HANDLE hTemplateFile) @trusted;
|
|
|
|
pragma(mangle, CloseHandle.mangleof) BOOL trustedCloseHandle(HANDLE) @trusted;
|
|
}
|
|
|
|
version (Windows) private void[] readImpl(scope const(char)[] name, scope const(FSChar)* namez,
|
|
size_t upTo = size_t.max) @trusted
|
|
{
|
|
import core.memory : GC;
|
|
import std.algorithm.comparison : min;
|
|
static trustedGetFileSize(HANDLE hFile, out ulong fileSize)
|
|
{
|
|
DWORD sizeHigh;
|
|
DWORD sizeLow = GetFileSize(hFile, &sizeHigh);
|
|
const bool result = sizeLow != INVALID_FILE_SIZE;
|
|
if (result)
|
|
fileSize = makeUlong(sizeLow, sizeHigh);
|
|
return result;
|
|
}
|
|
static trustedReadFile(HANDLE hFile, void *lpBuffer, size_t nNumberOfBytesToRead)
|
|
{
|
|
// Read by chunks of size < 4GB (Windows API limit)
|
|
size_t totalNumRead = 0;
|
|
while (totalNumRead != nNumberOfBytesToRead)
|
|
{
|
|
const uint chunkSize = min(nNumberOfBytesToRead - totalNumRead, 0xffff_0000);
|
|
DWORD numRead = void;
|
|
const result = ReadFile(hFile, lpBuffer + totalNumRead, chunkSize, &numRead, null);
|
|
if (result == 0 || numRead != chunkSize)
|
|
return false;
|
|
totalNumRead += chunkSize;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
alias defaults =
|
|
AliasSeq!(GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE, (SECURITY_ATTRIBUTES*).init,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
|
|
HANDLE.init);
|
|
auto h = trustedCreateFileW(namez, defaults);
|
|
|
|
cenforce(h != INVALID_HANDLE_VALUE, name, namez);
|
|
scope(exit) cenforce(trustedCloseHandle(h), name, namez);
|
|
ulong fileSize = void;
|
|
cenforce(trustedGetFileSize(h, fileSize), name, namez);
|
|
size_t size = min(upTo, fileSize);
|
|
auto buf = () { return GC.malloc(size, GC.BlkAttr.NO_SCAN)[0 .. size]; } ();
|
|
|
|
scope(failure)
|
|
{
|
|
() { GC.free(buf.ptr); } ();
|
|
}
|
|
|
|
if (size)
|
|
cenforce(trustedReadFile(h, &buf[0], size), name, namez);
|
|
return buf[0 .. size];
|
|
}
|
|
|
|
version (linux) @safe unittest
|
|
{
|
|
// A file with "zero" length that doesn't have 0 length at all
|
|
auto s = std.file.readText("/proc/cpuinfo");
|
|
assert(s.length > 0);
|
|
//writefln("'%s'", s);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
scope(exit) if (exists(deleteme)) remove(deleteme);
|
|
import std.stdio;
|
|
auto f = File(deleteme, "w");
|
|
f.write("abcd"); f.flush();
|
|
assert(read(deleteme) == "abcd");
|
|
}
|
|
|
|
/++
|
|
Reads and validates (using $(REF validate, std, utf)) a text file. S can be
|
|
an array of any character type. However, no width or endian conversions are
|
|
performed. So, if the width or endianness of the characters in the given
|
|
file differ from the width or endianness of the element type of S, then
|
|
validation will fail.
|
|
|
|
Params:
|
|
S = the string type of the file
|
|
name = string or range of characters representing the file _name
|
|
|
|
Returns: Array of characters read.
|
|
|
|
Throws: $(LREF FileException) if there is an error reading the file,
|
|
$(REF UTFException, std, utf) on UTF decoding error.
|
|
|
|
See_Also: $(REF read, std,file) for reading a binary file.
|
|
+/
|
|
S readText(S = string, R)(auto ref R name)
|
|
if (isSomeString!S && (isSomeFiniteCharInputRange!R || is(StringTypeOf!R)))
|
|
{
|
|
import std.algorithm.searching : startsWith;
|
|
import std.encoding : getBOM, BOM;
|
|
import std.exception : enforce;
|
|
import std.format : format;
|
|
import std.utf : UTFException, validate;
|
|
|
|
static if (is(StringTypeOf!R))
|
|
StringTypeOf!R filename = name;
|
|
else
|
|
auto filename = name;
|
|
|
|
static auto trustedCast(T)(void[] buf) @trusted { return cast(T) buf; }
|
|
auto data = trustedCast!(ubyte[])(read(filename));
|
|
|
|
immutable bomSeq = getBOM(data);
|
|
immutable bom = bomSeq.schema;
|
|
|
|
static if (is(immutable ElementEncodingType!S == immutable char))
|
|
{
|
|
with(BOM) switch (bom)
|
|
{
|
|
case utf16be:
|
|
case utf16le: throw new UTFException("UTF-8 requested. BOM is for UTF-16");
|
|
case utf32be:
|
|
case utf32le: throw new UTFException("UTF-8 requested. BOM is for UTF-32");
|
|
default: break;
|
|
}
|
|
}
|
|
else static if (is(immutable ElementEncodingType!S == immutable wchar))
|
|
{
|
|
with(BOM) switch (bom)
|
|
{
|
|
case utf8: throw new UTFException("UTF-16 requested. BOM is for UTF-8");
|
|
case utf16be:
|
|
{
|
|
version (BigEndian)
|
|
break;
|
|
else
|
|
throw new UTFException("BOM is for UTF-16 LE on Big Endian machine");
|
|
}
|
|
case utf16le:
|
|
{
|
|
version (BigEndian)
|
|
throw new UTFException("BOM is for UTF-16 BE on Little Endian machine");
|
|
else
|
|
break;
|
|
}
|
|
case utf32be:
|
|
case utf32le: throw new UTFException("UTF-8 requested. BOM is for UTF-32");
|
|
default: break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
with(BOM) switch (bom)
|
|
{
|
|
case utf8: throw new UTFException("UTF-16 requested. BOM is for UTF-8");
|
|
case utf16be:
|
|
case utf16le: throw new UTFException("UTF-8 requested. BOM is for UTF-16");
|
|
case utf32be:
|
|
{
|
|
version (BigEndian)
|
|
break;
|
|
else
|
|
throw new UTFException("BOM is for UTF-32 LE on Big Endian machine");
|
|
}
|
|
case utf32le:
|
|
{
|
|
version (BigEndian)
|
|
throw new UTFException("BOM is for UTF-32 BE on Little Endian machine");
|
|
else
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
if (data.length % ElementEncodingType!S.sizeof != 0)
|
|
throw new UTFException(format!"The content of %s is not UTF-%s"(filename, ElementEncodingType!S.sizeof * 8));
|
|
|
|
auto result = trustedCast!S(data);
|
|
validate(result);
|
|
return result;
|
|
}
|
|
|
|
/// Read file with UTF-8 text.
|
|
@safe unittest
|
|
{
|
|
write(deleteme, "abc"); // deleteme is the name of a temporary file
|
|
scope(exit) remove(deleteme);
|
|
string content = readText(deleteme);
|
|
assert(content == "abc");
|
|
}
|
|
|
|
// Read file with UTF-8 text but try to read it as UTF-16.
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
import std.utf : UTFException;
|
|
|
|
write(deleteme, "abc");
|
|
scope(exit) remove(deleteme);
|
|
// Throws because the file is not valid UTF-16.
|
|
assertThrown!UTFException(readText!wstring(deleteme));
|
|
}
|
|
|
|
// Read file with UTF-16 text.
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.searching : skipOver;
|
|
|
|
write(deleteme, "\uFEFFabc"w); // With BOM
|
|
scope(exit) remove(deleteme);
|
|
auto content = readText!wstring(deleteme);
|
|
assert(content == "\uFEFFabc"w);
|
|
// Strips BOM if present.
|
|
content.skipOver('\uFEFF');
|
|
assert(content == "abc"w);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, readText(TestAliasedString(null))));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.array : appender;
|
|
import std.bitmanip : append, Endian;
|
|
import std.exception : assertThrown;
|
|
import std.path : buildPath;
|
|
import std.string : representation;
|
|
import std.utf : UTFException;
|
|
|
|
mkdir(deleteme);
|
|
scope(exit) rmdirRecurse(deleteme);
|
|
|
|
immutable none8 = buildPath(deleteme, "none8");
|
|
immutable none16 = buildPath(deleteme, "none16");
|
|
immutable utf8 = buildPath(deleteme, "utf8");
|
|
immutable utf16be = buildPath(deleteme, "utf16be");
|
|
immutable utf16le = buildPath(deleteme, "utf16le");
|
|
immutable utf32be = buildPath(deleteme, "utf32be");
|
|
immutable utf32le = buildPath(deleteme, "utf32le");
|
|
immutable utf7 = buildPath(deleteme, "utf7");
|
|
|
|
write(none8, "京都市");
|
|
write(none16, "京都市"w);
|
|
write(utf8, (cast(char[])[0xEF, 0xBB, 0xBF]) ~ "京都市");
|
|
{
|
|
auto str = "\uFEFF京都市"w;
|
|
auto arr = appender!(ubyte[])();
|
|
foreach (c; str)
|
|
arr.append(c);
|
|
write(utf16be, arr.data);
|
|
}
|
|
{
|
|
auto str = "\uFEFF京都市"w;
|
|
auto arr = appender!(ubyte[])();
|
|
foreach (c; str)
|
|
arr.append!(ushort, Endian.littleEndian)(c);
|
|
write(utf16le, arr.data);
|
|
}
|
|
{
|
|
auto str = "\U0000FEFF京都市"d;
|
|
auto arr = appender!(ubyte[])();
|
|
foreach (c; str)
|
|
arr.append(c);
|
|
write(utf32be, arr.data);
|
|
}
|
|
{
|
|
auto str = "\U0000FEFF京都市"d;
|
|
auto arr = appender!(ubyte[])();
|
|
foreach (c; str)
|
|
arr.append!(uint, Endian.littleEndian)(c);
|
|
write(utf32le, arr.data);
|
|
}
|
|
write(utf7, (cast(ubyte[])[0x2B, 0x2F, 0x76, 0x38, 0x2D]) ~ "foobar".representation);
|
|
|
|
assertThrown!UTFException(readText(none16));
|
|
assert(readText(utf8) == (cast(char[])[0xEF, 0xBB, 0xBF]) ~ "京都市");
|
|
assertThrown!UTFException(readText(utf16be));
|
|
assertThrown!UTFException(readText(utf16le));
|
|
assertThrown!UTFException(readText(utf32be));
|
|
assertThrown!UTFException(readText(utf32le));
|
|
assert(readText(utf7) == (cast(char[])[0x2B, 0x2F, 0x76, 0x38, 0x2D]) ~ "foobar");
|
|
|
|
assertThrown!UTFException(readText!wstring(none8));
|
|
assert(readText!wstring(none16) == "京都市"w);
|
|
assertThrown!UTFException(readText!wstring(utf8));
|
|
version (BigEndian)
|
|
{
|
|
assert(readText!wstring(utf16be) == "\uFEFF京都市"w);
|
|
assertThrown!UTFException(readText!wstring(utf16le));
|
|
}
|
|
else
|
|
{
|
|
assertThrown!UTFException(readText!wstring(utf16be));
|
|
assert(readText!wstring(utf16le) == "\uFEFF京都市"w);
|
|
}
|
|
assertThrown!UTFException(readText!wstring(utf32be));
|
|
assertThrown!UTFException(readText!wstring(utf32le));
|
|
assertThrown!UTFException(readText!wstring(utf7));
|
|
|
|
assertThrown!UTFException(readText!dstring(utf8));
|
|
assertThrown!UTFException(readText!dstring(utf16be));
|
|
assertThrown!UTFException(readText!dstring(utf16le));
|
|
version (BigEndian)
|
|
{
|
|
assert(readText!dstring(utf32be) == "\U0000FEFF京都市"d);
|
|
assertThrown!UTFException(readText!dstring(utf32le));
|
|
}
|
|
else
|
|
{
|
|
assertThrown!UTFException(readText!dstring(utf32be));
|
|
assert(readText!dstring(utf32le) == "\U0000FEFF京都市"d);
|
|
}
|
|
assertThrown!UTFException(readText!dstring(utf7));
|
|
}
|
|
|
|
/*********************************************
|
|
Write `buffer` to file `name`.
|
|
|
|
Creates the file if it does not already exist.
|
|
|
|
Params:
|
|
name = string or range of characters representing the file _name
|
|
buffer = data to be written to file
|
|
|
|
Throws: $(LREF FileException) on error.
|
|
|
|
See_also: $(REF toFile, std,stdio)
|
|
*/
|
|
void write(R)(R name, const void[] buffer)
|
|
if ((isSomeFiniteCharInputRange!R || isSomeString!R) && !isConvertibleToString!R)
|
|
{
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
writeImpl(name, name.tempCString!FSChar(), buffer, false);
|
|
else
|
|
writeImpl(null, name.tempCString!FSChar(), buffer, false);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
scope(exit)
|
|
{
|
|
assert(exists(deleteme));
|
|
remove(deleteme);
|
|
}
|
|
|
|
int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
|
|
write(deleteme, a); // deleteme is the name of a temporary file
|
|
const bytes = read(deleteme);
|
|
const fileInts = () @trusted { return cast(int[]) bytes; }();
|
|
assert(fileInts == a);
|
|
}
|
|
|
|
/// ditto
|
|
void write(R)(auto ref R name, const void[] buffer)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
write!(StringTypeOf!R)(name, buffer);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, write(TestAliasedString(null), null)));
|
|
}
|
|
|
|
/*********************************************
|
|
Appends `buffer` to file `name`.
|
|
|
|
Creates the file if it does not already exist.
|
|
|
|
Params:
|
|
name = string or range of characters representing the file _name
|
|
buffer = data to be appended to file
|
|
|
|
Throws: $(LREF FileException) on error.
|
|
*/
|
|
void append(R)(R name, const void[] buffer)
|
|
if ((isSomeFiniteCharInputRange!R || isSomeString!R) && !isConvertibleToString!R)
|
|
{
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
writeImpl(name, name.tempCString!FSChar(), buffer, true);
|
|
else
|
|
writeImpl(null, name.tempCString!FSChar(), buffer, true);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
scope(exit)
|
|
{
|
|
assert(exists(deleteme));
|
|
remove(deleteme);
|
|
}
|
|
|
|
int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
|
|
write(deleteme, a); // deleteme is the name of a temporary file
|
|
int[] b = [ 13, 21 ];
|
|
append(deleteme, b);
|
|
const bytes = read(deleteme);
|
|
const fileInts = () @trusted { return cast(int[]) bytes; }();
|
|
assert(fileInts == a ~ b);
|
|
}
|
|
|
|
/// ditto
|
|
void append(R)(auto ref R name, const void[] buffer)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
append!(StringTypeOf!R)(name, buffer);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, append(TestAliasedString("foo"), [0, 1, 2, 3])));
|
|
}
|
|
|
|
// POSIX implementation helper for write and append
|
|
|
|
version (Posix) private void writeImpl(scope const(char)[] name, scope const(FSChar)* namez,
|
|
scope const(void)[] buffer, bool append) @trusted
|
|
{
|
|
import std.conv : octal;
|
|
|
|
// append or write
|
|
auto mode = append ? O_CREAT | O_WRONLY | O_APPEND
|
|
: O_CREAT | O_WRONLY | O_TRUNC;
|
|
|
|
immutable fd = core.sys.posix.fcntl.open(namez, mode, octal!666);
|
|
cenforce(fd != -1, name, namez);
|
|
{
|
|
scope(failure) core.sys.posix.unistd.close(fd);
|
|
|
|
immutable size = buffer.length;
|
|
size_t sum, cnt = void;
|
|
while (sum != size)
|
|
{
|
|
cnt = (size - sum < 2^^30) ? (size - sum) : 2^^30;
|
|
const numwritten = core.sys.posix.unistd.write(fd, buffer.ptr + sum, cnt);
|
|
if (numwritten != cnt)
|
|
break;
|
|
sum += numwritten;
|
|
}
|
|
cenforce(sum == size, name, namez);
|
|
}
|
|
cenforce(core.sys.posix.unistd.close(fd) == 0, name, namez);
|
|
}
|
|
|
|
// Windows implementation helper for write and append
|
|
|
|
version (Windows) private void writeImpl(scope const(char)[] name, scope const(FSChar)* namez,
|
|
scope const(void)[] buffer, bool append) @trusted
|
|
{
|
|
HANDLE h;
|
|
if (append)
|
|
{
|
|
alias defaults =
|
|
AliasSeq!(GENERIC_WRITE, 0, null, OPEN_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
|
|
HANDLE.init);
|
|
|
|
h = CreateFileW(namez, defaults);
|
|
cenforce(h != INVALID_HANDLE_VALUE, name, namez);
|
|
cenforce(SetFilePointer(h, 0, null, FILE_END) != INVALID_SET_FILE_POINTER,
|
|
name, namez);
|
|
}
|
|
else // write
|
|
{
|
|
alias defaults =
|
|
AliasSeq!(GENERIC_WRITE, 0, null, CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
|
|
HANDLE.init);
|
|
|
|
h = CreateFileW(namez, defaults);
|
|
cenforce(h != INVALID_HANDLE_VALUE, name, namez);
|
|
}
|
|
immutable size = buffer.length;
|
|
size_t sum, cnt = void;
|
|
DWORD numwritten = void;
|
|
while (sum != size)
|
|
{
|
|
cnt = (size - sum < 2^^30) ? (size - sum) : 2^^30;
|
|
WriteFile(h, buffer.ptr + sum, cast(uint) cnt, &numwritten, null);
|
|
if (numwritten != cnt)
|
|
break;
|
|
sum += numwritten;
|
|
}
|
|
cenforce(sum == size && CloseHandle(h), name, namez);
|
|
}
|
|
|
|
/***************************************************
|
|
* Rename file `from` _to `to`, moving it between directories if required.
|
|
* If the target file exists, it is overwritten.
|
|
*
|
|
* It is not possible to rename a file across different mount points
|
|
* or drives. On POSIX, the operation is atomic. That means, if `to`
|
|
* already exists there will be no time period during the operation
|
|
* where `to` is missing. See
|
|
* $(HTTP man7.org/linux/man-pages/man2/rename.2.html, manpage for rename)
|
|
* for more details.
|
|
*
|
|
* Params:
|
|
* from = string or range of characters representing the existing file name
|
|
* to = string or range of characters representing the target file name
|
|
*
|
|
* Throws: $(LREF FileException) on error.
|
|
*/
|
|
void rename(RF, RT)(RF from, RT to)
|
|
if ((isSomeFiniteCharInputRange!RF || isSomeString!RF) && !isConvertibleToString!RF &&
|
|
(isSomeFiniteCharInputRange!RT || isSomeString!RT) && !isConvertibleToString!RT)
|
|
{
|
|
// Place outside of @trusted block
|
|
auto fromz = from.tempCString!FSChar();
|
|
auto toz = to.tempCString!FSChar();
|
|
|
|
static if (isNarrowString!RF && is(immutable ElementEncodingType!RF == immutable char))
|
|
alias f = from;
|
|
else
|
|
enum string f = null;
|
|
|
|
static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable char))
|
|
alias t = to;
|
|
else
|
|
enum string t = null;
|
|
|
|
renameImpl(f, t, fromz, toz);
|
|
}
|
|
|
|
/// ditto
|
|
void rename(RF, RT)(auto ref RF from, auto ref RT to)
|
|
if (isConvertibleToString!RF || isConvertibleToString!RT)
|
|
{
|
|
import std.meta : staticMap;
|
|
alias Types = staticMap!(convertToString, RF, RT);
|
|
rename!Types(from, to);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, rename(TestAliasedString(null), TestAliasedString(null))));
|
|
static assert(__traits(compiles, rename("", TestAliasedString(null))));
|
|
static assert(__traits(compiles, rename(TestAliasedString(null), "")));
|
|
import std.utf : byChar;
|
|
static assert(__traits(compiles, rename(TestAliasedString(null), "".byChar)));
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto t1 = deleteme, t2 = deleteme~"2";
|
|
scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
|
|
|
|
t1.write("1");
|
|
t1.rename(t2);
|
|
assert(t2.readText == "1");
|
|
|
|
t1.write("2");
|
|
t1.rename(t2);
|
|
assert(t2.readText == "2");
|
|
}
|
|
|
|
private void renameImpl(scope const(char)[] f, scope const(char)[] t,
|
|
scope const(FSChar)* fromz, scope const(FSChar)* toz) @trusted
|
|
{
|
|
version (Windows)
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
const result = MoveFileExW(fromz, toz, MOVEFILE_REPLACE_EXISTING);
|
|
if (!result)
|
|
{
|
|
import core.stdc.wchar_ : wcslen;
|
|
import std.conv : to, text;
|
|
|
|
if (!f)
|
|
f = 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 = namez ? strlen(namez) : 0;
|
|
name = namez[0 .. len];
|
|
}
|
|
cenforce(core.stdc.stdio.remove(namez) == 0,
|
|
"Failed to remove file " ~ (name is null ? "(null)" : name));
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.exception : collectExceptionMsg, assertThrown;
|
|
import std.algorithm.searching : startsWith;
|
|
|
|
string filename = null; // e.g. as returned by File.tmpfile.name
|
|
|
|
version (linux)
|
|
{
|
|
// exact exception message is OS-dependent
|
|
auto msg = filename.remove.collectExceptionMsg!FileException;
|
|
assert(msg.startsWith("Failed to remove file (null):"), msg);
|
|
}
|
|
else version (Windows)
|
|
{
|
|
// don't test exact message on windows, it's language dependent
|
|
auto msg = filename.remove.collectExceptionMsg!FileException;
|
|
assert(msg.startsWith("(null):"), msg);
|
|
}
|
|
else
|
|
{
|
|
assertThrown!FileException(filename.remove);
|
|
}
|
|
}
|
|
|
|
version (Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(R)(R name)
|
|
if (isSomeFiniteCharInputRange!R)
|
|
{
|
|
auto namez = name.tempCString!FSChar();
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA fad = void;
|
|
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
{
|
|
static void getFA(scope const(char)[] name, scope const(FSChar)* namez,
|
|
out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
|
|
{
|
|
import std.exception : enforce;
|
|
enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad),
|
|
new FileException(name.idup));
|
|
}
|
|
getFA(name, namez, fad);
|
|
}
|
|
else
|
|
{
|
|
static void getFA(scope const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
|
|
{
|
|
import core.stdc.wchar_ : wcslen;
|
|
import std.conv : to;
|
|
import std.exception : enforce;
|
|
|
|
enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad),
|
|
new FileException(namez[0 .. wcslen(namez)].to!string));
|
|
}
|
|
getFA(namez, fad);
|
|
}
|
|
return fad;
|
|
}
|
|
|
|
version (Windows) private ulong makeUlong(DWORD dwLow, DWORD dwHigh) @safe pure nothrow @nogc
|
|
{
|
|
ULARGE_INTEGER li;
|
|
li.LowPart = dwLow;
|
|
li.HighPart = dwHigh;
|
|
return li.QuadPart;
|
|
}
|
|
|
|
version (Posix) private extern (C) pragma(mangle, stat.mangleof)
|
|
int trustedStat(scope const(FSChar)* namez, ref stat_t buf) @nogc nothrow @trusted;
|
|
|
|
/**
|
|
Get size of file `name` in bytes.
|
|
|
|
Params:
|
|
name = string or range of characters representing the file _name
|
|
Returns:
|
|
The size of file in bytes.
|
|
Throws:
|
|
$(LREF FileException) on error (e.g., file not found).
|
|
*/
|
|
ulong getSize(R)(R name)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
version (Windows)
|
|
{
|
|
with (getFileAttributesWin(name))
|
|
return makeUlong(nFileSizeLow, nFileSizeHigh);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
auto namez = name.tempCString();
|
|
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias names = name;
|
|
else
|
|
string names = null;
|
|
stat_t statbuf = void;
|
|
cenforce(trustedStat(namez, statbuf) == 0, names, namez);
|
|
return statbuf.st_size;
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
ulong getSize(R)(auto ref R name)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return getSize!(StringTypeOf!R)(name);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, getSize(TestAliasedString("foo"))));
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
scope(exit) deleteme.remove;
|
|
|
|
// create a file of size 1
|
|
write(deleteme, "a");
|
|
assert(getSize(deleteme) == 1);
|
|
|
|
// create a file of size 3
|
|
write(deleteme, "abc");
|
|
assert(getSize(deleteme) == 3);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// create a file of size 1
|
|
write(deleteme, "a");
|
|
scope(exit) deleteme.exists && deleteme.remove;
|
|
assert(getSize(deleteme) == 1);
|
|
// create a file of size 3
|
|
write(deleteme, "abc");
|
|
import std.utf : byChar;
|
|
assert(getSize(deleteme.byChar) == 3);
|
|
}
|
|
|
|
// Reads a time field from a stat_t with full precision.
|
|
version (Posix)
|
|
private SysTime statTimeToStdTime(char which)(ref const stat_t statbuf)
|
|
{
|
|
auto unixTime = mixin(`statbuf.st_` ~ which ~ `time`);
|
|
long stdTime = unixTimeToStdTime(unixTime);
|
|
|
|
static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `tim`))))
|
|
stdTime += mixin(`statbuf.st_` ~ which ~ `tim.tv_nsec`) / 100;
|
|
else
|
|
static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `timensec`))))
|
|
stdTime += mixin(`statbuf.st_` ~ which ~ `timensec`) / 100;
|
|
else
|
|
static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `time_nsec`))))
|
|
stdTime += mixin(`statbuf.st_` ~ which ~ `time_nsec`) / 100;
|
|
else
|
|
static if (is(typeof(mixin(`statbuf.__st_` ~ which ~ `timensec`))))
|
|
stdTime += mixin(`statbuf.__st_` ~ which ~ `timensec`) / 100;
|
|
|
|
return SysTime(stdTime);
|
|
}
|
|
|
|
/++
|
|
Get the access and modified times of file or folder `name`.
|
|
|
|
Params:
|
|
name = File/Folder _name to get times for.
|
|
accessTime = Time the file/folder was last accessed.
|
|
modificationTime = Time the file/folder was last modified.
|
|
|
|
Throws:
|
|
$(LREF FileException) on error.
|
|
+/
|
|
void getTimes(R)(R name,
|
|
out SysTime accessTime,
|
|
out SysTime modificationTime)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
version (Windows)
|
|
{
|
|
import std.datetime.systime : FILETIMEToSysTime;
|
|
|
|
with (getFileAttributesWin(name))
|
|
{
|
|
accessTime = FILETIMEToSysTime(&ftLastAccessTime);
|
|
modificationTime = FILETIMEToSysTime(&ftLastWriteTime);
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
auto namez = name.tempCString();
|
|
|
|
stat_t statbuf = void;
|
|
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias names = name;
|
|
else
|
|
string names = null;
|
|
cenforce(trustedStat(namez, statbuf) == 0, names, namez);
|
|
|
|
accessTime = statTimeToStdTime!'a'(statbuf);
|
|
modificationTime = statTimeToStdTime!'m'(statbuf);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
void getTimes(R)(auto ref R name,
|
|
out SysTime accessTime,
|
|
out SysTime modificationTime)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return getTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.datetime : abs, SysTime;
|
|
|
|
scope(exit) deleteme.remove;
|
|
write(deleteme, "a");
|
|
|
|
SysTime accessTime, modificationTime;
|
|
|
|
getTimes(deleteme, accessTime, modificationTime);
|
|
|
|
import std.datetime : Clock, seconds;
|
|
auto currTime = Clock.currTime();
|
|
enum leeway = 5.seconds;
|
|
|
|
auto diffAccess = accessTime - currTime;
|
|
auto diffModification = modificationTime - currTime;
|
|
assert(abs(diffAccess) <= leeway);
|
|
assert(abs(diffModification) <= leeway);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
SysTime atime, mtime;
|
|
static assert(__traits(compiles, getTimes(TestAliasedString("foo"), atime, mtime)));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.stdio : writefln;
|
|
|
|
auto currTime = Clock.currTime();
|
|
|
|
write(deleteme, "a");
|
|
scope(exit) assert(deleteme.exists), deleteme.remove;
|
|
|
|
SysTime accessTime1;
|
|
SysTime modificationTime1;
|
|
|
|
getTimes(deleteme, accessTime1, modificationTime1);
|
|
|
|
enum leeway = 5.seconds;
|
|
|
|
{
|
|
auto diffa = accessTime1 - currTime;
|
|
auto diffm = modificationTime1 - currTime;
|
|
scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime1, modificationTime1, currTime, diffa, diffm);
|
|
|
|
assert(abs(diffa) <= leeway);
|
|
assert(abs(diffm) <= leeway);
|
|
}
|
|
|
|
version (fullFileTests)
|
|
{
|
|
import core.thread;
|
|
enum sleepTime = dur!"seconds"(2);
|
|
Thread.sleep(sleepTime);
|
|
|
|
currTime = Clock.currTime();
|
|
write(deleteme, "b");
|
|
|
|
SysTime accessTime2 = void;
|
|
SysTime modificationTime2 = void;
|
|
|
|
getTimes(deleteme, accessTime2, modificationTime2);
|
|
|
|
{
|
|
auto diffa = accessTime2 - currTime;
|
|
auto diffm = modificationTime2 - currTime;
|
|
scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime2, modificationTime2, currTime, diffa, diffm);
|
|
|
|
//There is no guarantee that the access time will be updated.
|
|
assert(abs(diffa) <= leeway + sleepTime);
|
|
assert(abs(diffm) <= leeway);
|
|
}
|
|
|
|
assert(accessTime1 <= accessTime2);
|
|
assert(modificationTime1 <= modificationTime2);
|
|
}
|
|
}
|
|
|
|
|
|
version (StdDdoc)
|
|
{
|
|
/++
|
|
$(BLUE This function is Windows-Only.)
|
|
|
|
Get creation/access/modified times of file `name`.
|
|
|
|
This is the same as `getTimes` except that it also gives you the file
|
|
creation time - which isn't possible on POSIX systems.
|
|
|
|
Params:
|
|
name = File _name to get times for.
|
|
fileCreationTime = Time the file was created.
|
|
fileAccessTime = Time the file was last accessed.
|
|
fileModificationTime = Time the file was last modified.
|
|
|
|
Throws:
|
|
$(LREF FileException) on error.
|
|
+/
|
|
void getTimesWin(R)(R name,
|
|
out SysTime fileCreationTime,
|
|
out SysTime fileAccessTime,
|
|
out SysTime fileModificationTime)
|
|
if (isSomeFiniteCharInputRange!R || isConvertibleToString!R);
|
|
// above line contains both constraints for docs
|
|
// (so users know how it can be called)
|
|
}
|
|
else version (Windows)
|
|
{
|
|
void getTimesWin(R)(R name,
|
|
out SysTime fileCreationTime,
|
|
out SysTime fileAccessTime,
|
|
out SysTime fileModificationTime)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
import std.datetime.systime : FILETIMEToSysTime;
|
|
|
|
with (getFileAttributesWin(name))
|
|
{
|
|
fileCreationTime = FILETIMEToSysTime(&ftCreationTime);
|
|
fileAccessTime = FILETIMEToSysTime(&ftLastAccessTime);
|
|
fileModificationTime = FILETIMEToSysTime(&ftLastWriteTime);
|
|
}
|
|
}
|
|
|
|
void getTimesWin(R)(auto ref R name,
|
|
out SysTime fileCreationTime,
|
|
out SysTime fileAccessTime,
|
|
out SysTime fileModificationTime)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
getTimesWin!(StringTypeOf!R)(name, fileCreationTime, fileAccessTime, fileModificationTime);
|
|
}
|
|
}
|
|
|
|
version (Windows) @system unittest
|
|
{
|
|
import std.stdio : writefln;
|
|
auto currTime = Clock.currTime();
|
|
|
|
write(deleteme, "a");
|
|
scope(exit) { assert(exists(deleteme)); remove(deleteme); }
|
|
|
|
SysTime creationTime1 = void;
|
|
SysTime accessTime1 = void;
|
|
SysTime modificationTime1 = void;
|
|
|
|
getTimesWin(deleteme, creationTime1, accessTime1, modificationTime1);
|
|
|
|
enum leeway = dur!"seconds"(5);
|
|
|
|
{
|
|
auto diffc = creationTime1 - currTime;
|
|
auto diffa = accessTime1 - currTime;
|
|
auto diffm = modificationTime1 - currTime;
|
|
scope(failure)
|
|
{
|
|
writefln("[%s] [%s] [%s] [%s] [%s] [%s] [%s]",
|
|
creationTime1, accessTime1, modificationTime1, currTime, diffc, diffa, diffm);
|
|
}
|
|
|
|
// Deleting and recreating a file doesn't seem to always reset the "file creation time"
|
|
//assert(abs(diffc) <= leeway);
|
|
assert(abs(diffa) <= leeway);
|
|
assert(abs(diffm) <= leeway);
|
|
}
|
|
|
|
version (fullFileTests)
|
|
{
|
|
import core.thread;
|
|
Thread.sleep(dur!"seconds"(2));
|
|
|
|
currTime = Clock.currTime();
|
|
write(deleteme, "b");
|
|
|
|
SysTime creationTime2 = void;
|
|
SysTime accessTime2 = void;
|
|
SysTime modificationTime2 = void;
|
|
|
|
getTimesWin(deleteme, creationTime2, accessTime2, modificationTime2);
|
|
|
|
{
|
|
auto diffa = accessTime2 - currTime;
|
|
auto diffm = modificationTime2 - currTime;
|
|
scope(failure)
|
|
{
|
|
writefln("[%s] [%s] [%s] [%s] [%s]",
|
|
accessTime2, modificationTime2, currTime, diffa, diffm);
|
|
}
|
|
|
|
assert(abs(diffa) <= leeway);
|
|
assert(abs(diffm) <= leeway);
|
|
}
|
|
|
|
assert(creationTime1 == creationTime2);
|
|
assert(accessTime1 <= accessTime2);
|
|
assert(modificationTime1 <= modificationTime2);
|
|
}
|
|
|
|
{
|
|
SysTime ctime, atime, mtime;
|
|
static assert(__traits(compiles, getTimesWin(TestAliasedString("foo"), ctime, atime, mtime)));
|
|
}
|
|
}
|
|
|
|
version (Darwin)
|
|
private
|
|
{
|
|
import core.stdc.config : c_ulong;
|
|
enum ATTR_CMN_MODTIME = 0x00000400, ATTR_CMN_ACCTIME = 0x00001000;
|
|
alias attrgroup_t = uint;
|
|
static struct attrlist
|
|
{
|
|
ushort bitmapcount, reserved;
|
|
attrgroup_t commonattr, volattr, dirattr, fileattr, forkattr;
|
|
}
|
|
extern(C) int setattrlist(scope const(char)* path, scope ref attrlist attrs,
|
|
scope void* attrbuf, size_t attrBufSize, c_ulong options) nothrow @nogc @system;
|
|
}
|
|
|
|
/++
|
|
Set access/modified times of file or folder `name`.
|
|
|
|
Params:
|
|
name = File/Folder _name to get times for.
|
|
accessTime = Time the file/folder was last accessed.
|
|
modificationTime = Time the file/folder was last modified.
|
|
|
|
Throws:
|
|
$(LREF FileException) on error.
|
|
+/
|
|
void setTimes(R)(R name,
|
|
SysTime accessTime,
|
|
SysTime modificationTime)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
auto namez = name.tempCString!FSChar();
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias names = name;
|
|
else
|
|
string names = null;
|
|
setTimesImpl(names, namez, accessTime, modificationTime);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.datetime : DateTime, hnsecs, SysTime;
|
|
|
|
scope(exit) deleteme.remove;
|
|
write(deleteme, "a");
|
|
|
|
SysTime accessTime = SysTime(DateTime(2010, 10, 4, 0, 0, 30));
|
|
SysTime modificationTime = SysTime(DateTime(2018, 10, 4, 0, 0, 30));
|
|
setTimes(deleteme, accessTime, modificationTime);
|
|
|
|
SysTime accessTimeResolved, modificationTimeResolved;
|
|
getTimes(deleteme, accessTimeResolved, modificationTimeResolved);
|
|
|
|
assert(accessTime == accessTimeResolved);
|
|
assert(modificationTime == modificationTimeResolved);
|
|
}
|
|
|
|
/// ditto
|
|
void setTimes(R)(auto ref R name,
|
|
SysTime accessTime,
|
|
SysTime modificationTime)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
setTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
|
|
}
|
|
|
|
private void setTimesImpl(scope const(char)[] names, scope const(FSChar)* namez,
|
|
SysTime accessTime, SysTime modificationTime) @trusted
|
|
{
|
|
version (Windows)
|
|
{
|
|
import std.datetime.systime : SysTimeToFILETIME;
|
|
const ta = SysTimeToFILETIME(accessTime);
|
|
const tm = SysTimeToFILETIME(modificationTime);
|
|
alias defaults =
|
|
AliasSeq!(FILE_WRITE_ATTRIBUTES,
|
|
0,
|
|
null,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL |
|
|
FILE_ATTRIBUTE_DIRECTORY |
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
HANDLE.init);
|
|
auto h = CreateFileW(namez, defaults);
|
|
|
|
cenforce(h != INVALID_HANDLE_VALUE, names, namez);
|
|
|
|
scope(exit)
|
|
cenforce(CloseHandle(h), names, namez);
|
|
|
|
cenforce(SetFileTime(h, null, &ta, &tm), names, namez);
|
|
}
|
|
else
|
|
{
|
|
static if (is(typeof(&utimensat)))
|
|
{
|
|
timespec[2] t = void;
|
|
t[0] = accessTime.toTimeSpec();
|
|
t[1] = modificationTime.toTimeSpec();
|
|
cenforce(utimensat(AT_FDCWD, namez, t, 0) == 0, names, namez);
|
|
}
|
|
else
|
|
{
|
|
version (Darwin)
|
|
{
|
|
// Set modification & access times with setattrlist to avoid precision loss.
|
|
attrlist attrs = { bitmapcount: 5, reserved: 0,
|
|
commonattr: ATTR_CMN_MODTIME | ATTR_CMN_ACCTIME,
|
|
volattr: 0, dirattr: 0, fileattr: 0, forkattr: 0 };
|
|
timespec[2] attrbuf = [modificationTime.toTimeSpec(), accessTime.toTimeSpec()];
|
|
if (0 == setattrlist(namez, attrs, &attrbuf, attrbuf.sizeof, 0))
|
|
return;
|
|
if (.errno != ENOTSUP)
|
|
cenforce(false, names, namez);
|
|
// Not all volumes support setattrlist. In such cases
|
|
// fall through to the utimes implementation.
|
|
}
|
|
timeval[2] t = void;
|
|
t[0] = accessTime.toTimeVal();
|
|
t[1] = modificationTime.toTimeVal();
|
|
cenforce(utimes(namez, t) == 0, names, namez);
|
|
}
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
if (false) // Test instatiation
|
|
setTimes(TestAliasedString("foo"), SysTime.init, SysTime.init);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.stdio : File;
|
|
string newdir = deleteme ~ r".dir";
|
|
string dir = newdir ~ r"/a/b/c";
|
|
string file = dir ~ "/file";
|
|
|
|
if (!exists(dir)) mkdirRecurse(dir);
|
|
{ auto f = File(file, "w"); }
|
|
|
|
void testTimes(int hnsecValue)
|
|
{
|
|
foreach (path; [file, dir]) // test file and dir
|
|
{
|
|
SysTime atime = SysTime(DateTime(2010, 10, 4, 0, 0, 30), hnsecs(hnsecValue));
|
|
SysTime mtime = SysTime(DateTime(2011, 10, 4, 0, 0, 30), hnsecs(hnsecValue));
|
|
setTimes(path, atime, mtime);
|
|
|
|
SysTime atime_res;
|
|
SysTime mtime_res;
|
|
getTimes(path, atime_res, mtime_res);
|
|
assert(atime == atime_res);
|
|
assert(mtime == mtime_res);
|
|
}
|
|
}
|
|
|
|
testTimes(0);
|
|
version (linux)
|
|
testTimes(123_456_7);
|
|
|
|
rmdirRecurse(newdir);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=23683
|
|
@safe unittest
|
|
{
|
|
scope(exit) deleteme.remove;
|
|
import std.stdio : File;
|
|
auto f = File(deleteme, "wb");
|
|
SysTime time = SysTime(DateTime(2018, 10, 4, 0, 0, 30));
|
|
setTimes(deleteme, time, time);
|
|
}
|
|
|
|
/++
|
|
Returns the time that the given file was last modified.
|
|
|
|
Params:
|
|
name = the name of the file to check
|
|
Returns:
|
|
A $(REF SysTime,std,datetime,systime).
|
|
Throws:
|
|
$(LREF FileException) if the given file does not exist.
|
|
+/
|
|
SysTime timeLastModified(R)(R name)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
version (Windows)
|
|
{
|
|
SysTime dummy;
|
|
SysTime ftm;
|
|
|
|
getTimesWin(name, dummy, dummy, ftm);
|
|
|
|
return ftm;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
auto namez = name.tempCString!FSChar();
|
|
stat_t statbuf = void;
|
|
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias names = name;
|
|
else
|
|
string names = null;
|
|
cenforce(trustedStat(namez, statbuf) == 0, names, namez);
|
|
|
|
return statTimeToStdTime!'m'(statbuf);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
SysTime timeLastModified(R)(auto ref R name)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return timeLastModified!(StringTypeOf!R)(name);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.datetime : abs, DateTime, hnsecs, SysTime;
|
|
scope(exit) deleteme.remove;
|
|
|
|
import std.datetime : Clock, seconds;
|
|
auto currTime = Clock.currTime();
|
|
enum leeway = 5.seconds;
|
|
deleteme.write("bb");
|
|
assert(abs(deleteme.timeLastModified - currTime) <= leeway);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, timeLastModified(TestAliasedString("foo"))));
|
|
}
|
|
|
|
/++
|
|
Returns the time that the given file was last modified. If the
|
|
file does not exist, returns `returnIfMissing`.
|
|
|
|
A frequent usage pattern occurs in build automation tools such as
|
|
$(HTTP gnu.org/software/make, make) or $(HTTP
|
|
en.wikipedia.org/wiki/Apache_Ant, ant). To check whether file $(D
|
|
target) must be rebuilt from file `source` (i.e., `target` is
|
|
older than `source` or does not exist), use the comparison
|
|
below. The code throws a $(LREF FileException) if `source` does not
|
|
exist (as it should). On the other hand, the `SysTime.min` default
|
|
makes a non-existing `target` seem infinitely old so the test
|
|
correctly prompts building it.
|
|
|
|
Params:
|
|
name = The name of the file to get the modification time for.
|
|
returnIfMissing = The time to return if the given file does not exist.
|
|
Returns:
|
|
A $(REF SysTime,std,datetime,systime).
|
|
|
|
Example:
|
|
--------------------
|
|
if (source.timeLastModified >= target.timeLastModified(SysTime.min))
|
|
{
|
|
// must (re)build
|
|
}
|
|
else
|
|
{
|
|
// target is up-to-date
|
|
}
|
|
--------------------
|
|
+/
|
|
SysTime timeLastModified(R)(R name, SysTime returnIfMissing)
|
|
if (isSomeFiniteCharInputRange!R)
|
|
{
|
|
version (Windows)
|
|
{
|
|
if (!exists(name))
|
|
return returnIfMissing;
|
|
|
|
SysTime dummy;
|
|
SysTime ftm;
|
|
|
|
getTimesWin(name, dummy, dummy, ftm);
|
|
|
|
return ftm;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
auto namez = name.tempCString!FSChar();
|
|
stat_t statbuf = void;
|
|
|
|
return trustedStat(namez, statbuf) != 0 ?
|
|
returnIfMissing :
|
|
statTimeToStdTime!'m'(statbuf);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.datetime : SysTime;
|
|
|
|
assert("file.does.not.exist".timeLastModified(SysTime.min) == SysTime.min);
|
|
|
|
auto source = deleteme ~ "source";
|
|
auto target = deleteme ~ "target";
|
|
scope(exit) source.remove, target.remove;
|
|
|
|
source.write(".");
|
|
assert(target.timeLastModified(SysTime.min) < source.timeLastModified);
|
|
target.write(".");
|
|
assert(target.timeLastModified(SysTime.min) >= source.timeLastModified);
|
|
}
|
|
|
|
version (StdDdoc)
|
|
{
|
|
/++
|
|
$(BLUE This function is POSIX-Only.)
|
|
|
|
Returns the time that the given file was last modified.
|
|
Params:
|
|
statbuf = stat_t retrieved from file.
|
|
+/
|
|
SysTime timeLastModified()(auto ref stat_t statbuf) pure nothrow {assert(false);}
|
|
/++
|
|
$(BLUE This function is POSIX-Only.)
|
|
|
|
Returns the time that the given file was last accessed.
|
|
Params:
|
|
statbuf = stat_t retrieved from file.
|
|
+/
|
|
SysTime timeLastAccessed()(auto ref stat_t statbuf) pure nothrow {assert(false);}
|
|
/++
|
|
$(BLUE This function is POSIX-Only.)
|
|
|
|
Returns the time that the given file was last changed.
|
|
Params:
|
|
statbuf = stat_t retrieved from file.
|
|
+/
|
|
SysTime timeStatusChanged()(auto ref stat_t statbuf) pure nothrow {assert(false);}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
SysTime timeLastModified()(auto ref stat_t statbuf) pure nothrow
|
|
{
|
|
return statTimeToStdTime!'m'(statbuf);
|
|
}
|
|
SysTime timeLastAccessed()(auto ref stat_t statbuf) pure nothrow
|
|
{
|
|
return statTimeToStdTime!'a'(statbuf);
|
|
}
|
|
SysTime timeStatusChanged()(auto ref stat_t statbuf) pure nothrow
|
|
{
|
|
return statTimeToStdTime!'c'(statbuf);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
stat_t statbuf;
|
|
// check that both lvalues and rvalues work
|
|
timeLastAccessed(statbuf);
|
|
cast(void) timeLastAccessed(stat_t.init);
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//std.process.executeShell("echo a > deleteme");
|
|
if (exists(deleteme))
|
|
remove(deleteme);
|
|
|
|
write(deleteme, "a\n");
|
|
|
|
scope(exit)
|
|
{
|
|
assert(exists(deleteme));
|
|
remove(deleteme);
|
|
}
|
|
|
|
// assert(lastModified("deleteme") >
|
|
// lastModified("this file does not exist", SysTime.min));
|
|
//assert(lastModified("deleteme") > lastModified(__FILE__));
|
|
}
|
|
|
|
|
|
// Tests sub-second precision of querying file times.
|
|
// Should pass on most modern systems running on modern filesystems.
|
|
// Exceptions:
|
|
// - FreeBSD, where one would need to first set the
|
|
// vfs.timestamp_precision sysctl to a value greater than zero.
|
|
// - OS X, where the native filesystem (HFS+) stores filesystem
|
|
// timestamps with 1-second precision.
|
|
//
|
|
// Note: on linux systems, although in theory a change to a file date
|
|
// can be tracked with precision of 4 msecs, this test waits 20 msecs
|
|
// to prevent possible problems relative to the CI services the dlang uses,
|
|
// as they may have the HZ setting that controls the software clock set to 100
|
|
// (instead of the more common 250).
|
|
// see https://man7.org/linux/man-pages/man7/time.7.html
|
|
// https://stackoverflow.com/a/14393315,
|
|
// https://issues.dlang.org/show_bug.cgi?id=21148
|
|
version (FreeBSD) {} else
|
|
version (DragonFlyBSD) {} else
|
|
version (OSX) {} else
|
|
@safe unittest
|
|
{
|
|
import core.thread;
|
|
|
|
if (exists(deleteme))
|
|
remove(deleteme);
|
|
|
|
SysTime lastTime;
|
|
foreach (n; 0 .. 3)
|
|
{
|
|
write(deleteme, "a");
|
|
auto time = timeLastModified(deleteme);
|
|
remove(deleteme);
|
|
assert(time != lastTime);
|
|
lastTime = time;
|
|
() @trusted { Thread.sleep(20.msecs); }();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Determine whether the given file (or directory) _exists.
|
|
* Params:
|
|
* name = string or range of characters representing the file _name
|
|
* Returns:
|
|
* true if the file _name specified as input _exists
|
|
*/
|
|
bool exists(R)(R name)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
return existsImpl(name.tempCString!FSChar());
|
|
}
|
|
|
|
/// ditto
|
|
bool exists(R)(auto ref R name)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return exists!(StringTypeOf!R)(name);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto f = deleteme ~ "does.not.exist";
|
|
assert(!f.exists);
|
|
|
|
f.write("hello");
|
|
assert(f.exists);
|
|
|
|
f.remove;
|
|
assert(!f.exists);
|
|
}
|
|
|
|
private bool existsImpl(scope const(FSChar)* namez) @trusted nothrow @nogc
|
|
{
|
|
version (Windows)
|
|
{
|
|
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
|
|
// fileio/base/getfileattributes.asp
|
|
return GetFileAttributesW(namez) != 0xFFFFFFFF;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
/*
|
|
The reason why we use stat (and not access) here is
|
|
the quirky behavior of access for SUID programs: if
|
|
we used access, a file may not appear to "exist",
|
|
despite that the program would be able to open it
|
|
just fine. The behavior in question is described as
|
|
follows in the access man page:
|
|
|
|
> The check is done using the calling process's real
|
|
> UID and GID, rather than the effective IDs as is
|
|
> done when actually attempting an operation (e.g.,
|
|
> open(2)) on the file. This allows set-user-ID
|
|
> programs to easily determine the invoking user's
|
|
> authority.
|
|
|
|
While various operating systems provide eaccess or
|
|
euidaccess functions, these are not part of POSIX -
|
|
so it's safer to use stat instead.
|
|
*/
|
|
|
|
stat_t statbuf = void;
|
|
return lstat(namez, &statbuf) == 0;
|
|
}
|
|
else
|
|
static assert(0);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert(".".exists);
|
|
assert(!"this file does not exist".exists);
|
|
deleteme.write("a\n");
|
|
scope(exit) deleteme.remove;
|
|
assert(deleteme.exists);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=16573
|
|
@safe unittest
|
|
{
|
|
enum S : string { foo = "foo" }
|
|
assert(__traits(compiles, S.foo.exists));
|
|
}
|
|
|
|
/++
|
|
Returns the attributes of the given file.
|
|
|
|
Note that the file attributes on Windows and POSIX systems are
|
|
completely different. On Windows, they're what is returned by
|
|
$(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx,
|
|
GetFileAttributes), whereas on POSIX systems, they're the
|
|
`st_mode` value which is part of the $(D stat struct) gotten by
|
|
calling the $(HTTP en.wikipedia.org/wiki/Stat_%28Unix%29, `stat`)
|
|
function.
|
|
|
|
On POSIX systems, if the given file is a symbolic link, then
|
|
attributes are the attributes of the file pointed to by the symbolic
|
|
link.
|
|
|
|
Params:
|
|
name = The file to get the attributes of.
|
|
Returns:
|
|
The attributes of the file as a `uint`.
|
|
Throws: $(LREF FileException) on error.
|
|
+/
|
|
uint getAttributes(R)(R name)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
version (Windows)
|
|
{
|
|
auto namez = name.tempCString!FSChar();
|
|
static auto trustedGetFileAttributesW(scope const(FSChar)* namez) @trusted
|
|
{
|
|
return GetFileAttributesW(namez);
|
|
}
|
|
immutable result = trustedGetFileAttributesW(namez);
|
|
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias names = name;
|
|
else
|
|
string names = null;
|
|
cenforce(result != INVALID_FILE_ATTRIBUTES, names, namez);
|
|
|
|
return result;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
auto namez = name.tempCString!FSChar();
|
|
stat_t statbuf = void;
|
|
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias names = name;
|
|
else
|
|
string names = null;
|
|
cenforce(trustedStat(namez, statbuf) == 0, names, namez);
|
|
|
|
return statbuf.st_mode;
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
uint getAttributes(R)(auto ref R name)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return getAttributes!(StringTypeOf!R)(name);
|
|
}
|
|
|
|
/// getAttributes with a file
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto f = deleteme ~ "file";
|
|
scope(exit) f.remove;
|
|
|
|
assert(!f.exists);
|
|
assertThrown!FileException(f.getAttributes);
|
|
|
|
f.write(".");
|
|
auto attributes = f.getAttributes;
|
|
assert(!attributes.attrIsDir);
|
|
assert(attributes.attrIsFile);
|
|
}
|
|
|
|
/// getAttributes with a directory
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto dir = deleteme ~ "dir";
|
|
scope(exit) dir.rmdir;
|
|
|
|
assert(!dir.exists);
|
|
assertThrown!FileException(dir.getAttributes);
|
|
|
|
dir.mkdir;
|
|
auto attributes = dir.getAttributes;
|
|
assert(attributes.attrIsDir);
|
|
assert(!attributes.attrIsFile);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, getAttributes(TestAliasedString(null))));
|
|
}
|
|
|
|
/++
|
|
If the given file is a symbolic link, then this returns the attributes of the
|
|
symbolic link itself rather than file that it points to. If the given file
|
|
is $(I not) a symbolic link, then this function returns the same result
|
|
as getAttributes.
|
|
|
|
On Windows, getLinkAttributes is identical to getAttributes. It exists on
|
|
Windows so that you don't have to special-case code for Windows when dealing
|
|
with symbolic links.
|
|
|
|
Params:
|
|
name = The file to get the symbolic link attributes of.
|
|
|
|
Returns:
|
|
the attributes
|
|
|
|
Throws:
|
|
$(LREF FileException) on error.
|
|
+/
|
|
uint getLinkAttributes(R)(R name)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
version (Windows)
|
|
{
|
|
return getAttributes(name);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
auto namez = name.tempCString!FSChar();
|
|
static auto trustedLstat(const(FSChar)* namez, ref stat_t buf) @trusted
|
|
{
|
|
return lstat(namez, &buf);
|
|
}
|
|
stat_t lstatbuf = void;
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias names = name;
|
|
else
|
|
string names = null;
|
|
cenforce(trustedLstat(namez, lstatbuf) == 0, names, namez);
|
|
return lstatbuf.st_mode;
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
uint getLinkAttributes(R)(auto ref R name)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return getLinkAttributes!(StringTypeOf!R)(name);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto source = deleteme ~ "source";
|
|
auto target = deleteme ~ "target";
|
|
|
|
assert(!source.exists);
|
|
assertThrown!FileException(source.getLinkAttributes);
|
|
|
|
// symlinking isn't available on Windows
|
|
version (Posix)
|
|
{
|
|
scope(exit) source.remove, target.remove;
|
|
|
|
target.write("target");
|
|
target.symlink(source);
|
|
assert(source.readText == "target");
|
|
assert(source.isSymlink);
|
|
assert(source.getLinkAttributes.attrIsSymlink);
|
|
}
|
|
}
|
|
|
|
/// if the file is no symlink, getLinkAttributes behaves like getAttributes
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto f = deleteme ~ "file";
|
|
scope(exit) f.remove;
|
|
|
|
assert(!f.exists);
|
|
assertThrown!FileException(f.getLinkAttributes);
|
|
|
|
f.write(".");
|
|
auto attributes = f.getLinkAttributes;
|
|
assert(!attributes.attrIsDir);
|
|
assert(attributes.attrIsFile);
|
|
}
|
|
|
|
/// if the file is no symlink, getLinkAttributes behaves like getAttributes
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto dir = deleteme ~ "dir";
|
|
scope(exit) dir.rmdir;
|
|
|
|
assert(!dir.exists);
|
|
assertThrown!FileException(dir.getLinkAttributes);
|
|
|
|
dir.mkdir;
|
|
auto attributes = dir.getLinkAttributes;
|
|
assert(attributes.attrIsDir);
|
|
assert(!attributes.attrIsFile);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, getLinkAttributes(TestAliasedString(null))));
|
|
}
|
|
|
|
/++
|
|
Set the _attributes of the given file.
|
|
|
|
For example, a programmatic equivalent of Unix's `chmod +x name`
|
|
to make a file executable is
|
|
`name.setAttributes(name.getAttributes | octal!700)`.
|
|
|
|
Params:
|
|
name = the file _name
|
|
attributes = the _attributes to set the file to
|
|
|
|
Throws:
|
|
$(LREF FileException) if the given file does not exist.
|
|
+/
|
|
void setAttributes(R)(R name, uint attributes)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
version (Windows)
|
|
{
|
|
auto namez = name.tempCString!FSChar();
|
|
static auto trustedSetFileAttributesW(scope const(FSChar)* namez, uint dwFileAttributes) @trusted
|
|
{
|
|
return SetFileAttributesW(namez, dwFileAttributes);
|
|
}
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias names = name;
|
|
else
|
|
string names = null;
|
|
cenforce(trustedSetFileAttributesW(namez, attributes), names, namez);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
auto namez = name.tempCString!FSChar();
|
|
static auto trustedChmod(scope const(FSChar)* namez, mode_t mode) @trusted
|
|
{
|
|
return chmod(namez, mode);
|
|
}
|
|
assert(attributes <= mode_t.max);
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias names = name;
|
|
else
|
|
string names = null;
|
|
cenforce(!trustedChmod(namez, cast(mode_t) attributes), names, namez);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
void setAttributes(R)(auto ref R name, uint attributes)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return setAttributes!(StringTypeOf!R)(name, attributes);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, setAttributes(TestAliasedString(null), 0)));
|
|
}
|
|
|
|
/// setAttributes with a file
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
import std.conv : octal;
|
|
|
|
auto f = deleteme ~ "file";
|
|
version (Posix)
|
|
{
|
|
scope(exit) f.remove;
|
|
|
|
assert(!f.exists);
|
|
assertThrown!FileException(f.setAttributes(octal!777));
|
|
|
|
f.write(".");
|
|
auto attributes = f.getAttributes;
|
|
assert(!attributes.attrIsDir);
|
|
assert(attributes.attrIsFile);
|
|
|
|
f.setAttributes(octal!777);
|
|
attributes = f.getAttributes;
|
|
|
|
assert((attributes & 1023) == octal!777);
|
|
}
|
|
}
|
|
|
|
/// setAttributes with a directory
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
import std.conv : octal;
|
|
|
|
auto dir = deleteme ~ "dir";
|
|
version (Posix)
|
|
{
|
|
scope(exit) dir.rmdir;
|
|
|
|
assert(!dir.exists);
|
|
assertThrown!FileException(dir.setAttributes(octal!777));
|
|
|
|
dir.mkdir;
|
|
auto attributes = dir.getAttributes;
|
|
assert(attributes.attrIsDir);
|
|
assert(!attributes.attrIsFile);
|
|
|
|
dir.setAttributes(octal!777);
|
|
attributes = dir.getAttributes;
|
|
|
|
assert((attributes & 1023) == octal!777);
|
|
}
|
|
}
|
|
|
|
/++
|
|
Returns whether the given file is a directory.
|
|
|
|
Params:
|
|
name = The path to the file.
|
|
|
|
Returns:
|
|
true if name specifies a directory
|
|
|
|
Throws:
|
|
$(LREF FileException) if the given file does not exist.
|
|
+/
|
|
@property bool isDir(R)(R name)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
version (Windows)
|
|
{
|
|
return (getAttributes(name) & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
return (getAttributes(name) & S_IFMT) == S_IFDIR;
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
@property bool isDir(R)(auto ref R name)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return name.isDir!(StringTypeOf!R);
|
|
}
|
|
|
|
///
|
|
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto dir = deleteme ~ "dir";
|
|
auto f = deleteme ~ "f";
|
|
scope(exit) dir.rmdir, f.remove;
|
|
|
|
assert(!dir.exists);
|
|
assertThrown!FileException(dir.isDir);
|
|
|
|
dir.mkdir;
|
|
assert(dir.isDir);
|
|
|
|
f.write(".");
|
|
assert(!f.isDir);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, TestAliasedString(null).isDir));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
version (Windows)
|
|
{
|
|
if ("C:\\Program Files\\".exists)
|
|
assert("C:\\Program Files\\".isDir);
|
|
|
|
if ("C:\\Windows\\system.ini".exists)
|
|
assert(!"C:\\Windows\\system.ini".isDir);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
if (system_directory.exists)
|
|
assert(system_directory.isDir);
|
|
|
|
if (system_file.exists)
|
|
assert(!system_file.isDir);
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
version (Windows)
|
|
enum dir = "C:\\Program Files\\";
|
|
else version (Posix)
|
|
enum dir = system_directory;
|
|
|
|
if (dir.exists)
|
|
{
|
|
DirEntry de = DirEntry(dir);
|
|
assert(de.isDir);
|
|
assert(DirEntry(dir).isDir);
|
|
}
|
|
}
|
|
|
|
/++
|
|
Returns whether the given file _attributes are for a directory.
|
|
|
|
Params:
|
|
attributes = The file _attributes.
|
|
|
|
Returns:
|
|
true if attributes specifies a directory
|
|
+/
|
|
bool attrIsDir(uint attributes) @safe pure nothrow @nogc
|
|
{
|
|
version (Windows)
|
|
{
|
|
return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
return (attributes & S_IFMT) == S_IFDIR;
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto dir = deleteme ~ "dir";
|
|
auto f = deleteme ~ "f";
|
|
scope(exit) dir.rmdir, f.remove;
|
|
|
|
assert(!dir.exists);
|
|
assertThrown!FileException(dir.getAttributes.attrIsDir);
|
|
|
|
dir.mkdir;
|
|
assert(dir.isDir);
|
|
assert(dir.getAttributes.attrIsDir);
|
|
|
|
f.write(".");
|
|
assert(!f.isDir);
|
|
assert(!f.getAttributes.attrIsDir);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
version (Windows)
|
|
{
|
|
if ("C:\\Program Files\\".exists)
|
|
{
|
|
assert(attrIsDir(getAttributes("C:\\Program Files\\")));
|
|
assert(attrIsDir(getLinkAttributes("C:\\Program Files\\")));
|
|
}
|
|
|
|
if ("C:\\Windows\\system.ini".exists)
|
|
{
|
|
assert(!attrIsDir(getAttributes("C:\\Windows\\system.ini")));
|
|
assert(!attrIsDir(getLinkAttributes("C:\\Windows\\system.ini")));
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
if (system_directory.exists)
|
|
{
|
|
assert(attrIsDir(getAttributes(system_directory)));
|
|
assert(attrIsDir(getLinkAttributes(system_directory)));
|
|
}
|
|
|
|
if (system_file.exists)
|
|
{
|
|
assert(!attrIsDir(getAttributes(system_file)));
|
|
assert(!attrIsDir(getLinkAttributes(system_file)));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/++
|
|
Returns whether the given file (or directory) is a file.
|
|
|
|
On Windows, if a file is not a directory, then it's a file. So,
|
|
either `isFile` or `isDir` will return true for any given file.
|
|
|
|
On POSIX systems, if `isFile` is `true`, that indicates that the file
|
|
is a regular file (e.g. not a block not device). So, on POSIX systems, it's
|
|
possible for both `isFile` and `isDir` to be `false` for a
|
|
particular file (in which case, it's a special file). You can use
|
|
`getAttributes` to get the attributes to figure out what type of special
|
|
it is, or you can use `DirEntry` to get at its `statBuf`, which is the
|
|
result from `stat`. In either case, see the man page for `stat` for
|
|
more information.
|
|
|
|
Params:
|
|
name = The path to the file.
|
|
|
|
Returns:
|
|
true if name specifies a file
|
|
|
|
Throws:
|
|
$(LREF FileException) if the given file does not exist.
|
|
+/
|
|
@property bool isFile(R)(R name)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
version (Windows)
|
|
return !name.isDir;
|
|
else version (Posix)
|
|
return (getAttributes(name) & S_IFMT) == S_IFREG;
|
|
}
|
|
|
|
/// ditto
|
|
@property bool isFile(R)(auto ref R name)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return isFile!(StringTypeOf!R)(name);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto dir = deleteme ~ "dir";
|
|
auto f = deleteme ~ "f";
|
|
scope(exit) dir.rmdir, f.remove;
|
|
|
|
dir.mkdir;
|
|
assert(!dir.isFile);
|
|
|
|
assert(!f.exists);
|
|
assertThrown!FileException(f.isFile);
|
|
|
|
f.write(".");
|
|
assert(f.isFile);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15658
|
|
@safe unittest
|
|
{
|
|
DirEntry e = DirEntry(".");
|
|
static assert(is(typeof(isFile(e))));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, TestAliasedString(null).isFile));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
version (Windows)
|
|
{
|
|
if ("C:\\Program Files\\".exists)
|
|
assert(!"C:\\Program Files\\".isFile);
|
|
|
|
if ("C:\\Windows\\system.ini".exists)
|
|
assert("C:\\Windows\\system.ini".isFile);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
if (system_directory.exists)
|
|
assert(!system_directory.isFile);
|
|
|
|
if (system_file.exists)
|
|
assert(system_file.isFile);
|
|
}
|
|
}
|
|
|
|
|
|
/++
|
|
Returns whether the given file _attributes are for a file.
|
|
|
|
On Windows, if a file is not a directory, it's a file. So, either
|
|
`attrIsFile` or `attrIsDir` will return `true` for the
|
|
_attributes of any given file.
|
|
|
|
On POSIX systems, if `attrIsFile` is `true`, that indicates that the
|
|
file is a regular file (e.g. not a block not device). So, on POSIX systems,
|
|
it's possible for both `attrIsFile` and `attrIsDir` to be `false`
|
|
for a particular file (in which case, it's a special file). If a file is a
|
|
special file, you can use the _attributes to check what type of special file
|
|
it is (see the man page for `stat` for more information).
|
|
|
|
Params:
|
|
attributes = The file _attributes.
|
|
|
|
Returns:
|
|
true if the given file _attributes are for a file
|
|
|
|
Example:
|
|
--------------------
|
|
assert(attrIsFile(getAttributes("/etc/fonts/fonts.conf")));
|
|
assert(attrIsFile(getLinkAttributes("/etc/fonts/fonts.conf")));
|
|
--------------------
|
|
+/
|
|
bool attrIsFile(uint attributes) @safe pure nothrow @nogc
|
|
{
|
|
version (Windows)
|
|
{
|
|
return (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
return (attributes & S_IFMT) == S_IFREG;
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto dir = deleteme ~ "dir";
|
|
auto f = deleteme ~ "f";
|
|
scope(exit) dir.rmdir, f.remove;
|
|
|
|
dir.mkdir;
|
|
assert(!dir.isFile);
|
|
assert(!dir.getAttributes.attrIsFile);
|
|
|
|
assert(!f.exists);
|
|
assertThrown!FileException(f.getAttributes.attrIsFile);
|
|
|
|
f.write(".");
|
|
assert(f.isFile);
|
|
assert(f.getAttributes.attrIsFile);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
version (Windows)
|
|
{
|
|
if ("C:\\Program Files\\".exists)
|
|
{
|
|
assert(!attrIsFile(getAttributes("C:\\Program Files\\")));
|
|
assert(!attrIsFile(getLinkAttributes("C:\\Program Files\\")));
|
|
}
|
|
|
|
if ("C:\\Windows\\system.ini".exists)
|
|
{
|
|
assert(attrIsFile(getAttributes("C:\\Windows\\system.ini")));
|
|
assert(attrIsFile(getLinkAttributes("C:\\Windows\\system.ini")));
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
if (system_directory.exists)
|
|
{
|
|
assert(!attrIsFile(getAttributes(system_directory)));
|
|
assert(!attrIsFile(getLinkAttributes(system_directory)));
|
|
}
|
|
|
|
if (system_file.exists)
|
|
{
|
|
assert(attrIsFile(getAttributes(system_file)));
|
|
assert(attrIsFile(getLinkAttributes(system_file)));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/++
|
|
Returns whether the given file is a symbolic link.
|
|
|
|
On Windows, returns `true` when the file is either a symbolic link or a
|
|
junction point.
|
|
|
|
Params:
|
|
name = The path to the file.
|
|
|
|
Returns:
|
|
true if name is a symbolic link
|
|
|
|
Throws:
|
|
$(LREF FileException) if the given file does not exist.
|
|
+/
|
|
@property bool isSymlink(R)(R name)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
version (Windows)
|
|
return (getAttributes(name) & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
|
|
else version (Posix)
|
|
return (getLinkAttributes(name) & S_IFMT) == S_IFLNK;
|
|
}
|
|
|
|
/// ditto
|
|
@property bool isSymlink(R)(auto ref R name)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return name.isSymlink!(StringTypeOf!R);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, TestAliasedString(null).isSymlink));
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto source = deleteme ~ "source";
|
|
auto target = deleteme ~ "target";
|
|
|
|
assert(!source.exists);
|
|
assertThrown!FileException(source.isSymlink);
|
|
|
|
// symlinking isn't available on Windows
|
|
version (Posix)
|
|
{
|
|
scope(exit) source.remove, target.remove;
|
|
|
|
target.write("target");
|
|
target.symlink(source);
|
|
assert(source.readText == "target");
|
|
assert(source.isSymlink);
|
|
assert(source.getLinkAttributes.attrIsSymlink);
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
version (Windows)
|
|
{
|
|
if ("C:\\Program Files\\".exists)
|
|
assert(!"C:\\Program Files\\".isSymlink);
|
|
|
|
if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
|
|
assert("C:\\Documents and Settings\\".isSymlink);
|
|
|
|
enum fakeSymFile = "C:\\Windows\\system.ini";
|
|
if (fakeSymFile.exists)
|
|
{
|
|
assert(!fakeSymFile.isSymlink);
|
|
|
|
assert(!fakeSymFile.isSymlink);
|
|
assert(!attrIsSymlink(getAttributes(fakeSymFile)));
|
|
assert(!attrIsSymlink(getLinkAttributes(fakeSymFile)));
|
|
|
|
assert(attrIsFile(getAttributes(fakeSymFile)));
|
|
assert(attrIsFile(getLinkAttributes(fakeSymFile)));
|
|
assert(!attrIsDir(getAttributes(fakeSymFile)));
|
|
assert(!attrIsDir(getLinkAttributes(fakeSymFile)));
|
|
|
|
assert(getAttributes(fakeSymFile) == getLinkAttributes(fakeSymFile));
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
if (system_directory.exists)
|
|
{
|
|
assert(!system_directory.isSymlink);
|
|
|
|
immutable symfile = deleteme ~ "_slink\0";
|
|
scope(exit) if (symfile.exists) symfile.remove();
|
|
|
|
core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
|
|
|
|
assert(symfile.isSymlink);
|
|
assert(!attrIsSymlink(getAttributes(symfile)));
|
|
assert(attrIsSymlink(getLinkAttributes(symfile)));
|
|
|
|
assert(attrIsDir(getAttributes(symfile)));
|
|
assert(!attrIsDir(getLinkAttributes(symfile)));
|
|
|
|
assert(!attrIsFile(getAttributes(symfile)));
|
|
assert(!attrIsFile(getLinkAttributes(symfile)));
|
|
}
|
|
|
|
if (system_file.exists)
|
|
{
|
|
assert(!system_file.isSymlink);
|
|
|
|
immutable symfile = deleteme ~ "_slink\0";
|
|
scope(exit) if (symfile.exists) symfile.remove();
|
|
|
|
core.sys.posix.unistd.symlink(system_file, symfile.ptr);
|
|
|
|
assert(symfile.isSymlink);
|
|
assert(!attrIsSymlink(getAttributes(symfile)));
|
|
assert(attrIsSymlink(getLinkAttributes(symfile)));
|
|
|
|
assert(!attrIsDir(getAttributes(symfile)));
|
|
assert(!attrIsDir(getLinkAttributes(symfile)));
|
|
|
|
assert(attrIsFile(getAttributes(symfile)));
|
|
assert(!attrIsFile(getLinkAttributes(symfile)));
|
|
}
|
|
}
|
|
|
|
static assert(__traits(compiles, () @safe { return "dummy".isSymlink; }));
|
|
}
|
|
|
|
|
|
/++
|
|
Returns whether the given file attributes are for a symbolic link.
|
|
|
|
On Windows, return `true` when the file is either a symbolic link or a
|
|
junction point.
|
|
|
|
Params:
|
|
attributes = The file attributes.
|
|
|
|
Returns:
|
|
true if attributes are for a symbolic link
|
|
|
|
Example:
|
|
--------------------
|
|
core.sys.posix.unistd.symlink("/etc/fonts/fonts.conf", "/tmp/alink");
|
|
|
|
assert(!getAttributes("/tmp/alink").isSymlink);
|
|
assert(getLinkAttributes("/tmp/alink").isSymlink);
|
|
--------------------
|
|
+/
|
|
bool attrIsSymlink(uint attributes) @safe pure nothrow @nogc
|
|
{
|
|
version (Windows)
|
|
return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
|
|
else version (Posix)
|
|
return (attributes & S_IFMT) == S_IFLNK;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto source = deleteme ~ "source";
|
|
auto target = deleteme ~ "target";
|
|
|
|
assert(!source.exists);
|
|
assertThrown!FileException(source.getLinkAttributes.attrIsSymlink);
|
|
|
|
// symlinking isn't available on Windows
|
|
version (Posix)
|
|
{
|
|
scope(exit) source.remove, target.remove;
|
|
|
|
target.write("target");
|
|
target.symlink(source);
|
|
assert(source.readText == "target");
|
|
assert(source.isSymlink);
|
|
assert(source.getLinkAttributes.attrIsSymlink);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Change directory to `pathname`. Equivalent to `cd` on
|
|
Windows and POSIX.
|
|
|
|
Params:
|
|
pathname = the directory to step into
|
|
|
|
Throws: $(LREF FileException) on error.
|
|
*/
|
|
void chdir(R)(R pathname)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
// Place outside of @trusted block
|
|
auto pathz = pathname.tempCString!FSChar();
|
|
|
|
version (Windows)
|
|
{
|
|
static auto trustedChdir(scope const(FSChar)* pathz) @trusted
|
|
{
|
|
return SetCurrentDirectoryW(pathz);
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
static auto trustedChdir(scope const(FSChar)* pathz) @trusted
|
|
{
|
|
return core.sys.posix.unistd.chdir(pathz) == 0;
|
|
}
|
|
}
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias pathStr = pathname;
|
|
else
|
|
string pathStr = null;
|
|
cenforce(trustedChdir(pathz), pathStr, pathz);
|
|
}
|
|
|
|
/// ditto
|
|
void chdir(R)(auto ref R pathname)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return chdir!(StringTypeOf!R)(pathname);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.algorithm.sorting : sort;
|
|
import std.array : array;
|
|
import std.path : buildPath;
|
|
|
|
auto cwd = getcwd;
|
|
auto dir = deleteme ~ "dir";
|
|
dir.mkdir;
|
|
scope(exit) cwd.chdir, dir.rmdirRecurse;
|
|
|
|
dir.buildPath("a").write(".");
|
|
dir.chdir; // step into dir
|
|
"b".write(".");
|
|
assert(dirEntries(".", SpanMode.shallow).array.sort.equal(
|
|
[".".buildPath("a"), ".".buildPath("b")]
|
|
));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, chdir(TestAliasedString(null))));
|
|
}
|
|
|
|
/**
|
|
Make a new directory `pathname`.
|
|
|
|
Params:
|
|
pathname = the path of the directory to make
|
|
|
|
Throws:
|
|
$(LREF FileException) on POSIX or $(LREF WindowsException) on Windows
|
|
if an error occured.
|
|
*/
|
|
void mkdir(R)(R pathname)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
// Place outside of @trusted block
|
|
const pathz = pathname.tempCString!FSChar();
|
|
|
|
version (Windows)
|
|
{
|
|
static auto trustedCreateDirectoryW(scope const(FSChar)* pathz) @trusted
|
|
{
|
|
return CreateDirectoryW(pathz, null);
|
|
}
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias pathStr = pathname;
|
|
else
|
|
string pathStr = null;
|
|
wenforce(trustedCreateDirectoryW(pathz), pathStr, pathz);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
import std.conv : octal;
|
|
|
|
static auto trustedMkdir(scope const(FSChar)* pathz, mode_t mode) @trusted
|
|
{
|
|
return core.sys.posix.sys.stat.mkdir(pathz, mode);
|
|
}
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias pathStr = pathname;
|
|
else
|
|
string pathStr = null;
|
|
cenforce(trustedMkdir(pathz, octal!777) == 0, pathStr, pathz);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
void mkdir(R)(auto ref R pathname)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
return mkdir!(StringTypeOf!R)(pathname);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.file : mkdir;
|
|
static assert(__traits(compiles, mkdir(TestAliasedString(null))));
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.file : mkdir;
|
|
|
|
auto dir = deleteme ~ "dir";
|
|
scope(exit) dir.rmdir;
|
|
|
|
dir.mkdir;
|
|
assert(dir.exists);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
assertThrown("a/b/c/d/e".mkdir);
|
|
}
|
|
|
|
// Same as mkdir but ignores "already exists" errors.
|
|
// Returns: "true" if the directory was created,
|
|
// "false" if it already existed.
|
|
private bool ensureDirExists()(scope const(char)[] pathname)
|
|
{
|
|
import std.exception : enforce;
|
|
const pathz = pathname.tempCString!FSChar();
|
|
|
|
version (Windows)
|
|
{
|
|
if (() @trusted { return CreateDirectoryW(pathz, null); }())
|
|
return true;
|
|
cenforce(GetLastError() == ERROR_ALREADY_EXISTS, pathname.idup);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
import std.conv : octal;
|
|
|
|
if (() @trusted { return core.sys.posix.sys.stat.mkdir(pathz, octal!777); }() == 0)
|
|
return true;
|
|
cenforce(errno == EEXIST || errno == EISDIR, pathname);
|
|
}
|
|
enforce(pathname.isDir, new FileException(pathname.idup));
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Make directory and all parent directories as needed.
|
|
|
|
Does nothing if the directory specified by
|
|
`pathname` already exists.
|
|
|
|
Params:
|
|
pathname = the full path of the directory to create
|
|
|
|
Throws: $(LREF FileException) on error.
|
|
*/
|
|
void mkdirRecurse(scope const(char)[] pathname) @safe
|
|
{
|
|
import std.path : dirName, baseName;
|
|
|
|
const left = dirName(pathname);
|
|
if (left.length != pathname.length && !exists(left))
|
|
{
|
|
mkdirRecurse(left);
|
|
}
|
|
if (!baseName(pathname).empty)
|
|
{
|
|
ensureDirExists(pathname);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.path : buildPath;
|
|
|
|
auto dir = deleteme ~ "dir";
|
|
scope(exit) dir.rmdirRecurse;
|
|
|
|
dir.mkdir;
|
|
assert(dir.exists);
|
|
dir.mkdirRecurse; // does nothing
|
|
|
|
// creates all parent directories as needed
|
|
auto nested = dir.buildPath("a", "b", "c");
|
|
nested.mkdirRecurse;
|
|
assert(nested.exists);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
scope(exit) deleteme.remove;
|
|
deleteme.write("a");
|
|
|
|
// cannot make directory as it's already a file
|
|
assertThrown!FileException(deleteme.mkdirRecurse);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
{
|
|
import std.path : buildPath, buildNormalizedPath;
|
|
|
|
immutable basepath = deleteme ~ "_dir";
|
|
scope(exit) () @trusted { rmdirRecurse(basepath); }();
|
|
|
|
auto path = buildPath(basepath, "a", "..", "b");
|
|
mkdirRecurse(path);
|
|
path = path.buildNormalizedPath;
|
|
assert(path.isDir);
|
|
|
|
path = buildPath(basepath, "c");
|
|
write(path, "");
|
|
assertThrown!FileException(mkdirRecurse(path));
|
|
|
|
path = buildPath(basepath, "d");
|
|
mkdirRecurse(path);
|
|
mkdirRecurse(path); // should not throw
|
|
}
|
|
|
|
version (Windows)
|
|
{
|
|
assertThrown!FileException(mkdirRecurse(`1:\foobar`));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=3570
|
|
{
|
|
immutable basepath = deleteme ~ "_dir";
|
|
version (Windows)
|
|
{
|
|
immutable path = basepath ~ "\\fake\\here\\";
|
|
}
|
|
else version (Posix)
|
|
{
|
|
immutable path = basepath ~ `/fake/here/`;
|
|
}
|
|
|
|
mkdirRecurse(path);
|
|
assert(basepath.exists && basepath.isDir);
|
|
scope(exit) () @trusted { rmdirRecurse(basepath); }();
|
|
assert(path.exists && path.isDir);
|
|
}
|
|
}
|
|
|
|
/****************************************************
|
|
Remove directory `pathname`.
|
|
|
|
Params:
|
|
pathname = Range or string specifying the directory name
|
|
|
|
Throws: $(LREF FileException) on error.
|
|
*/
|
|
void rmdir(R)(R pathname)
|
|
if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
|
|
{
|
|
// Place outside of @trusted block
|
|
auto pathz = pathname.tempCString!FSChar();
|
|
|
|
version (Windows)
|
|
{
|
|
static auto trustedRmdir(scope const(FSChar)* pathz) @trusted
|
|
{
|
|
return RemoveDirectoryW(pathz);
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
static auto trustedRmdir(scope const(FSChar)* pathz) @trusted
|
|
{
|
|
return core.sys.posix.unistd.rmdir(pathz) == 0;
|
|
}
|
|
}
|
|
static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
|
|
alias pathStr = pathname;
|
|
else
|
|
string pathStr = null;
|
|
cenforce(trustedRmdir(pathz), pathStr, pathz);
|
|
}
|
|
|
|
/// ditto
|
|
void rmdir(R)(auto ref R pathname)
|
|
if (isConvertibleToString!R)
|
|
{
|
|
rmdir!(StringTypeOf!R)(pathname);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(__traits(compiles, rmdir(TestAliasedString(null))));
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto dir = deleteme ~ "dir";
|
|
|
|
dir.mkdir;
|
|
assert(dir.exists);
|
|
dir.rmdir;
|
|
assert(!dir.exists);
|
|
}
|
|
|
|
/++
|
|
$(BLUE This function is POSIX-Only.)
|
|
|
|
Creates a symbolic _link (_symlink).
|
|
|
|
Params:
|
|
original = The file that is being linked. This is the target path that's
|
|
stored in the _symlink. A relative path is relative to the created
|
|
_symlink.
|
|
link = The _symlink to create. A relative path is relative to the
|
|
current working directory.
|
|
|
|
Throws:
|
|
$(LREF FileException) on error (which includes if the _symlink already
|
|
exists).
|
|
+/
|
|
version (StdDdoc) void symlink(RO, RL)(RO original, RL link)
|
|
if ((isSomeFiniteCharInputRange!RO || isConvertibleToString!RO) &&
|
|
(isSomeFiniteCharInputRange!RL || isConvertibleToString!RL));
|
|
else version (Posix) void symlink(RO, RL)(RO original, RL link)
|
|
if ((isSomeFiniteCharInputRange!RO || isConvertibleToString!RO) &&
|
|
(isSomeFiniteCharInputRange!RL || isConvertibleToString!RL))
|
|
{
|
|
static if (isConvertibleToString!RO || isConvertibleToString!RL)
|
|
{
|
|
import std.meta : staticMap;
|
|
alias Types = staticMap!(convertToString, RO, RL);
|
|
symlink!Types(original, link);
|
|
}
|
|
else
|
|
{
|
|
import std.conv : text;
|
|
auto oz = original.tempCString();
|
|
auto lz = link.tempCString();
|
|
alias posixSymlink = core.sys.posix.unistd.symlink;
|
|
immutable int result = () @trusted { return posixSymlink(oz, lz); } ();
|
|
cenforce(result == 0, text(link));
|
|
}
|
|
}
|
|
|
|
version (Posix) @safe unittest
|
|
{
|
|
if (system_directory.exists)
|
|
{
|
|
immutable symfile = deleteme ~ "_slink\0";
|
|
scope(exit) if (symfile.exists) symfile.remove();
|
|
|
|
symlink(system_directory, symfile);
|
|
|
|
assert(symfile.exists);
|
|
assert(symfile.isSymlink);
|
|
assert(!attrIsSymlink(getAttributes(symfile)));
|
|
assert(attrIsSymlink(getLinkAttributes(symfile)));
|
|
|
|
assert(attrIsDir(getAttributes(symfile)));
|
|
assert(!attrIsDir(getLinkAttributes(symfile)));
|
|
|
|
assert(!attrIsFile(getAttributes(symfile)));
|
|
assert(!attrIsFile(getLinkAttributes(symfile)));
|
|
}
|
|
|
|
if (system_file.exists)
|
|
{
|
|
assert(!system_file.isSymlink);
|
|
|
|
immutable symfile = deleteme ~ "_slink\0";
|
|
scope(exit) if (symfile.exists) symfile.remove();
|
|
|
|
symlink(system_file, symfile);
|
|
|
|
assert(symfile.exists);
|
|
assert(symfile.isSymlink);
|
|
assert(!attrIsSymlink(getAttributes(symfile)));
|
|
assert(attrIsSymlink(getLinkAttributes(symfile)));
|
|
|
|
assert(!attrIsDir(getAttributes(symfile)));
|
|
assert(!attrIsDir(getLinkAttributes(symfile)));
|
|
|
|
assert(attrIsFile(getAttributes(symfile)));
|
|
assert(!attrIsFile(getLinkAttributes(symfile)));
|
|
}
|
|
}
|
|
|
|
version (Posix) @safe unittest
|
|
{
|
|
static assert(__traits(compiles,
|
|
symlink(TestAliasedString(null), TestAliasedString(null))));
|
|
}
|
|
|
|
|
|
/++
|
|
$(BLUE This function is POSIX-Only.)
|
|
|
|
Returns the path to the file pointed to by a symlink. Note that the
|
|
path could be either relative or absolute depending on the symlink.
|
|
If the path is relative, it's relative to the symlink, not the current
|
|
working directory.
|
|
|
|
Throws:
|
|
$(LREF FileException) on error.
|
|
+/
|
|
version (StdDdoc) string readLink(R)(R link)
|
|
if (isSomeFiniteCharInputRange!R || isConvertibleToString!R);
|
|
else version (Posix) string readLink(R)(R link)
|
|
if (isSomeFiniteCharInputRange!R || isConvertibleToString!R)
|
|
{
|
|
static if (isConvertibleToString!R)
|
|
{
|
|
return readLink!(convertToString!R)(link);
|
|
}
|
|
else
|
|
{
|
|
import std.conv : to;
|
|
import std.exception : assumeUnique;
|
|
alias posixReadlink = core.sys.posix.unistd.readlink;
|
|
enum bufferLen = 2048;
|
|
enum maxCodeUnits = 6;
|
|
char[bufferLen] buffer;
|
|
const linkz = link.tempCString();
|
|
auto size = () @trusted {
|
|
return posixReadlink(linkz, buffer.ptr, buffer.length);
|
|
} ();
|
|
cenforce(size != -1, to!string(link));
|
|
|
|
if (size <= bufferLen - maxCodeUnits)
|
|
return to!string(buffer[0 .. size]);
|
|
|
|
auto dynamicBuffer = new char[](bufferLen * 3 / 2);
|
|
|
|
foreach (i; 0 .. 10)
|
|
{
|
|
size = () @trusted {
|
|
return posixReadlink(linkz, dynamicBuffer.ptr,
|
|
dynamicBuffer.length);
|
|
} ();
|
|
cenforce(size != -1, to!string(link));
|
|
|
|
if (size <= dynamicBuffer.length - maxCodeUnits)
|
|
{
|
|
dynamicBuffer.length = size;
|
|
return () @trusted {
|
|
return assumeUnique(dynamicBuffer);
|
|
} ();
|
|
}
|
|
|
|
dynamicBuffer.length = dynamicBuffer.length * 3 / 2;
|
|
}
|
|
|
|
throw new FileException(to!string(link), "Path is too long to read.");
|
|
}
|
|
}
|
|
|
|
version (Posix) @safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
import std.string;
|
|
|
|
foreach (file; [system_directory, system_file])
|
|
{
|
|
if (file.exists)
|
|
{
|
|
immutable symfile = deleteme ~ "_slink\0";
|
|
scope(exit) if (symfile.exists) symfile.remove();
|
|
|
|
symlink(file, symfile);
|
|
assert(readLink(symfile) == file, format("Failed file: %s", file));
|
|
}
|
|
}
|
|
|
|
assertThrown!FileException(readLink("/doesnotexist"));
|
|
}
|
|
|
|
version (Posix) @safe unittest
|
|
{
|
|
static assert(__traits(compiles, readLink(TestAliasedString("foo"))));
|
|
}
|
|
|
|
version (Posix) @system unittest // input range of dchars
|
|
{
|
|
mkdirRecurse(deleteme);
|
|
scope(exit) if (deleteme.exists) rmdirRecurse(deleteme);
|
|
write(deleteme ~ "/f", "");
|
|
import std.range.interfaces : InputRange, inputRangeObject;
|
|
import std.utf : byChar;
|
|
immutable string link = deleteme ~ "/l";
|
|
symlink("f", link);
|
|
InputRange!(ElementType!string) linkr = inputRangeObject(link);
|
|
alias R = typeof(linkr);
|
|
static assert(isInputRange!R);
|
|
static assert(!isForwardRange!R);
|
|
assert(readLink(linkr) == "f");
|
|
}
|
|
|
|
|
|
/****************************************************
|
|
* Get the current working directory.
|
|
* Throws: $(LREF FileException) on error.
|
|
*/
|
|
version (Windows) string getcwd() @trusted
|
|
{
|
|
import std.conv : to;
|
|
import std.checkedint : checked;
|
|
/* GetCurrentDirectory's return value:
|
|
1. function succeeds: the number of characters that are written to
|
|
the buffer, not including the terminating null character.
|
|
2. function fails: zero
|
|
3. the buffer (lpBuffer) is not large enough: the required size of
|
|
the buffer, in characters, including the null-terminating character.
|
|
*/
|
|
version (StdUnittest)
|
|
enum BUF_SIZE = 10; // trigger reallocation code
|
|
else
|
|
enum BUF_SIZE = 4096; // enough for most common case
|
|
wchar[BUF_SIZE] buffW = void;
|
|
immutable n = cenforce(GetCurrentDirectoryW(to!DWORD(buffW.length), buffW.ptr),
|
|
"getcwd");
|
|
// we can do it because toUTFX always produces a fresh string
|
|
if (n < buffW.length)
|
|
{
|
|
return buffW[0 .. n].to!string;
|
|
}
|
|
else //staticBuff isn't enough
|
|
{
|
|
auto cn = checked(n);
|
|
auto ptr = cast(wchar*) malloc((cn * wchar.sizeof).get);
|
|
scope(exit) free(ptr);
|
|
immutable n2 = GetCurrentDirectoryW(cn.get, ptr);
|
|
cenforce(n2 && n2 < cn, "getcwd");
|
|
return ptr[0 .. n2].to!string;
|
|
}
|
|
}
|
|
else version (Solaris) string getcwd() @trusted
|
|
{
|
|
/* BUF_SIZE >= PATH_MAX */
|
|
enum BUF_SIZE = 4096;
|
|
/* The user should be able to specify any size buffer > 0 */
|
|
auto p = cenforce(core.sys.posix.unistd.getcwd(null, BUF_SIZE),
|
|
"cannot get cwd");
|
|
scope(exit) core.stdc.stdlib.free(p);
|
|
return p[0 .. core.stdc.string.strlen(p)].idup;
|
|
}
|
|
else version (Posix) string getcwd() @trusted
|
|
{
|
|
auto p = cenforce(core.sys.posix.unistd.getcwd(null, 0),
|
|
"cannot get cwd");
|
|
scope(exit) core.stdc.stdlib.free(p);
|
|
return p[0 .. core.stdc.string.strlen(p)].idup;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto s = getcwd();
|
|
assert(s.length);
|
|
}
|
|
|
|
/**
|
|
* Returns the full path of the current executable.
|
|
*
|
|
* Returns:
|
|
* The path of the executable as a `string`.
|
|
*
|
|
* Throws:
|
|
* $(REF1 Exception, object)
|
|
*/
|
|
@trusted string thisExePath()
|
|
{
|
|
version (Darwin)
|
|
{
|
|
import core.sys.darwin.mach.dyld : _NSGetExecutablePath;
|
|
import core.sys.posix.stdlib : realpath;
|
|
import std.conv : to;
|
|
import std.exception : errnoEnforce;
|
|
|
|
uint size;
|
|
|
|
_NSGetExecutablePath(null, &size); // get the length of the path
|
|
auto buffer = new char[size];
|
|
_NSGetExecutablePath(buffer.ptr, &size);
|
|
|
|
auto absolutePath = realpath(buffer.ptr, null); // let the function allocate
|
|
|
|
scope (exit)
|
|
{
|
|
if (absolutePath)
|
|
free(absolutePath);
|
|
}
|
|
|
|
errnoEnforce(absolutePath);
|
|
return to!(string)(absolutePath);
|
|
}
|
|
else version (linux)
|
|
{
|
|
return readLink("/proc/self/exe");
|
|
}
|
|
else version (Windows)
|
|
{
|
|
import std.conv : to;
|
|
import std.exception : enforce;
|
|
|
|
wchar[MAX_PATH] buf;
|
|
wchar[] buffer = buf[];
|
|
|
|
while (true)
|
|
{
|
|
auto len = GetModuleFileNameW(null, buffer.ptr, cast(DWORD) buffer.length);
|
|
wenforce(len);
|
|
if (len != buffer.length)
|
|
return to!(string)(buffer[0 .. len]);
|
|
buffer.length *= 2;
|
|
}
|
|
}
|
|
else version (DragonFlyBSD)
|
|
{
|
|
import core.sys.dragonflybsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME;
|
|
import std.exception : errnoEnforce, assumeUnique;
|
|
|
|
int[4] mib = [CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1];
|
|
size_t len;
|
|
|
|
auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); // get the length of the path
|
|
errnoEnforce(result == 0);
|
|
|
|
auto buffer = new char[len - 1];
|
|
result = sysctl(mib.ptr, mib.length, buffer.ptr, &len, null, 0);
|
|
errnoEnforce(result == 0);
|
|
|
|
return buffer.assumeUnique;
|
|
}
|
|
else version (FreeBSD)
|
|
{
|
|
import core.sys.freebsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME;
|
|
import std.exception : errnoEnforce, assumeUnique;
|
|
|
|
int[4] mib = [CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1];
|
|
size_t len;
|
|
|
|
auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); // get the length of the path
|
|
errnoEnforce(result == 0);
|
|
|
|
auto buffer = new char[len - 1];
|
|
result = sysctl(mib.ptr, mib.length, buffer.ptr, &len, null, 0);
|
|
errnoEnforce(result == 0);
|
|
|
|
return buffer.assumeUnique;
|
|
}
|
|
else version (NetBSD)
|
|
{
|
|
import core.sys.netbsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC_ARGS, KERN_PROC_PATHNAME;
|
|
import std.exception : errnoEnforce, assumeUnique;
|
|
|
|
int[4] mib = [CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME];
|
|
size_t len;
|
|
|
|
auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); // get the length of the path
|
|
errnoEnforce(result == 0);
|
|
|
|
auto buffer = new char[len - 1];
|
|
result = sysctl(mib.ptr, mib.length, buffer.ptr, &len, null, 0);
|
|
errnoEnforce(result == 0);
|
|
|
|
return buffer.assumeUnique;
|
|
}
|
|
else version (OpenBSD)
|
|
{
|
|
import core.sys.openbsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC_ARGS, KERN_PROC_ARGV;
|
|
import core.sys.posix.unistd : getpid;
|
|
import std.conv : to;
|
|
import std.exception : enforce, errnoEnforce;
|
|
import std.process : searchPathFor;
|
|
|
|
int[4] mib = [CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV];
|
|
size_t len;
|
|
|
|
auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0);
|
|
errnoEnforce(result == 0);
|
|
|
|
auto argv = new char*[len - 1];
|
|
result = sysctl(mib.ptr, mib.length, argv.ptr, &len, null, 0);
|
|
errnoEnforce(result == 0);
|
|
|
|
auto argv0 = argv[0];
|
|
if (*argv0 == '/' || *argv0 == '.')
|
|
{
|
|
import core.sys.posix.stdlib : realpath;
|
|
auto absolutePath = realpath(argv0, null);
|
|
scope (exit)
|
|
{
|
|
if (absolutePath)
|
|
free(absolutePath);
|
|
}
|
|
errnoEnforce(absolutePath);
|
|
return to!(string)(absolutePath);
|
|
}
|
|
else
|
|
{
|
|
auto absolutePath = searchPathFor(to!string(argv0));
|
|
errnoEnforce(absolutePath);
|
|
return absolutePath;
|
|
}
|
|
}
|
|
else version (Solaris)
|
|
{
|
|
import core.sys.posix.unistd : getpid;
|
|
import std.string : format;
|
|
|
|
// Only Solaris 10 and later
|
|
return readLink(format("/proc/%d/path/a.out", getpid()));
|
|
}
|
|
else version (Hurd)
|
|
{
|
|
return readLink("/proc/self/exe");
|
|
}
|
|
else
|
|
static assert(0, "thisExePath is not supported on this platform");
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.path : isAbsolute;
|
|
auto path = thisExePath();
|
|
|
|
assert(path.exists);
|
|
assert(path.isAbsolute);
|
|
assert(path.isFile);
|
|
}
|
|
|
|
version (StdDdoc)
|
|
{
|
|
/++
|
|
Info on a file, similar to what you'd get from stat on a POSIX system.
|
|
+/
|
|
struct DirEntry
|
|
{
|
|
@safe:
|
|
/++
|
|
Constructs a `DirEntry` for the given file (or directory).
|
|
|
|
Params:
|
|
path = The file (or directory) to get a DirEntry for.
|
|
|
|
Throws:
|
|
$(LREF FileException) if the file does not exist.
|
|
+/
|
|
this(return scope string path);
|
|
|
|
version (Windows)
|
|
{
|
|
private this(string path, in WIN32_FIND_DATAW *fd);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
private this(string path, core.sys.posix.dirent.dirent* fd);
|
|
}
|
|
|
|
/++
|
|
Returns the path to the file represented by this `DirEntry`.
|
|
|
|
Example:
|
|
--------------------
|
|
auto de1 = DirEntry("/etc/fonts/fonts.conf");
|
|
assert(de1.name == "/etc/fonts/fonts.conf");
|
|
|
|
auto de2 = DirEntry("/usr/share/include");
|
|
assert(de2.name == "/usr/share/include");
|
|
--------------------
|
|
+/
|
|
@property string name() const return scope;
|
|
|
|
|
|
/++
|
|
Returns whether the file represented by this `DirEntry` is a
|
|
directory.
|
|
|
|
Example:
|
|
--------------------
|
|
auto de1 = DirEntry("/etc/fonts/fonts.conf");
|
|
assert(!de1.isDir);
|
|
|
|
auto de2 = DirEntry("/usr/share/include");
|
|
assert(de2.isDir);
|
|
--------------------
|
|
+/
|
|
@property bool isDir() scope;
|
|
|
|
|
|
/++
|
|
Returns whether the file represented by this `DirEntry` is a file.
|
|
|
|
On Windows, if a file is not a directory, then it's a file. So,
|
|
either `isFile` or `isDir` will return `true`.
|
|
|
|
On POSIX systems, if `isFile` is `true`, that indicates that
|
|
the file is a regular file (e.g. not a block not device). So, on
|
|
POSIX systems, it's possible for both `isFile` and `isDir` to
|
|
be `false` for a particular file (in which case, it's a special
|
|
file). You can use `attributes` or `statBuf` to get more
|
|
information about a special file (see the stat man page for more
|
|
details).
|
|
|
|
Example:
|
|
--------------------
|
|
auto de1 = DirEntry("/etc/fonts/fonts.conf");
|
|
assert(de1.isFile);
|
|
|
|
auto de2 = DirEntry("/usr/share/include");
|
|
assert(!de2.isFile);
|
|
--------------------
|
|
+/
|
|
@property bool isFile() scope;
|
|
|
|
/++
|
|
Returns whether the file represented by this `DirEntry` is a
|
|
symbolic link.
|
|
|
|
On Windows, return `true` when the file is either a symbolic
|
|
link or a junction point.
|
|
+/
|
|
@property bool isSymlink() scope;
|
|
|
|
/++
|
|
Returns the size of the file represented by this `DirEntry`
|
|
in bytes.
|
|
+/
|
|
@property ulong size() scope;
|
|
|
|
/++
|
|
$(BLUE This function is Windows-Only.)
|
|
|
|
Returns the creation time of the file represented by this
|
|
`DirEntry`.
|
|
+/
|
|
@property SysTime timeCreated() const scope;
|
|
|
|
/++
|
|
Returns the time that the file represented by this `DirEntry` was
|
|
last accessed.
|
|
|
|
Note that many file systems do not update the access time for files
|
|
(generally for performance reasons), so there's a good chance that
|
|
`timeLastAccessed` will return the same value as
|
|
`timeLastModified`.
|
|
+/
|
|
@property SysTime timeLastAccessed() scope;
|
|
|
|
/++
|
|
Returns the time that the file represented by this `DirEntry` was
|
|
last modified.
|
|
+/
|
|
@property SysTime timeLastModified() scope;
|
|
|
|
/++
|
|
$(BLUE This function is POSIX-Only.)
|
|
|
|
Returns the time that the file represented by this `DirEntry` was
|
|
last changed (not only in contents, but also in permissions or ownership).
|
|
+/
|
|
@property SysTime timeStatusChanged() const scope;
|
|
|
|
/++
|
|
Returns the _attributes of the file represented by this `DirEntry`.
|
|
|
|
Note that the file _attributes on Windows and POSIX systems are
|
|
completely different. On, Windows, they're what is returned by
|
|
`GetFileAttributes`
|
|
$(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx, GetFileAttributes)
|
|
Whereas, an POSIX systems, they're the `st_mode` value which is
|
|
part of the `stat` struct gotten by calling `stat`.
|
|
|
|
On POSIX systems, if the file represented by this `DirEntry` is a
|
|
symbolic link, then _attributes are the _attributes of the file
|
|
pointed to by the symbolic link.
|
|
+/
|
|
@property uint attributes() scope;
|
|
|
|
/++
|
|
On POSIX systems, if the file represented by this `DirEntry` is a
|
|
symbolic link, then `linkAttributes` are the attributes of the
|
|
symbolic link itself. Otherwise, `linkAttributes` is identical to
|
|
`attributes`.
|
|
|
|
On Windows, `linkAttributes` is identical to `attributes`. It
|
|
exists on Windows so that you don't have to special-case code for
|
|
Windows when dealing with symbolic links.
|
|
+/
|
|
@property uint linkAttributes() scope;
|
|
|
|
version (Windows)
|
|
alias stat_t = void*;
|
|
|
|
/++
|
|
$(BLUE This function is POSIX-Only.)
|
|
|
|
The `stat` struct gotten from calling `stat`.
|
|
+/
|
|
@property stat_t statBuf() scope;
|
|
}
|
|
}
|
|
else version (Windows)
|
|
{
|
|
struct DirEntry
|
|
{
|
|
@safe:
|
|
public:
|
|
alias name this;
|
|
|
|
this(return scope string path)
|
|
{
|
|
import std.datetime.systime : FILETIMEToSysTime;
|
|
|
|
if (!path.exists())
|
|
throw new FileException(path, "File does not exist");
|
|
|
|
_name = path;
|
|
|
|
with (getFileAttributesWin(path))
|
|
{
|
|
_size = makeUlong(nFileSizeLow, nFileSizeHigh);
|
|
_timeCreated = FILETIMEToSysTime(&ftCreationTime);
|
|
_timeLastAccessed = FILETIMEToSysTime(&ftLastAccessTime);
|
|
_timeLastModified = FILETIMEToSysTime(&ftLastWriteTime);
|
|
_attributes = dwFileAttributes;
|
|
}
|
|
}
|
|
|
|
private this(string path, WIN32_FIND_DATAW *fd) @trusted
|
|
{
|
|
import core.stdc.wchar_ : wcslen;
|
|
import std.conv : to;
|
|
import std.datetime.systime : FILETIMEToSysTime;
|
|
import std.path : buildPath;
|
|
|
|
fd.cFileName[$ - 1] = 0;
|
|
|
|
size_t clength = wcslen(&fd.cFileName[0]);
|
|
_name = buildPath(path, fd.cFileName[0 .. clength].to!string);
|
|
_size = (cast(ulong) fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
|
|
_timeCreated = FILETIMEToSysTime(&fd.ftCreationTime);
|
|
_timeLastAccessed = FILETIMEToSysTime(&fd.ftLastAccessTime);
|
|
_timeLastModified = FILETIMEToSysTime(&fd.ftLastWriteTime);
|
|
_attributes = fd.dwFileAttributes;
|
|
}
|
|
|
|
@property string name() const pure nothrow return scope
|
|
{
|
|
return _name;
|
|
}
|
|
|
|
@property bool isDir() const pure nothrow scope
|
|
{
|
|
return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
}
|
|
|
|
@property bool isFile() const pure nothrow scope
|
|
{
|
|
//Are there no options in Windows other than directory and file?
|
|
//If there are, then this probably isn't the best way to determine
|
|
//whether this DirEntry is a file or not.
|
|
return !isDir;
|
|
}
|
|
|
|
@property bool isSymlink() const pure nothrow scope
|
|
{
|
|
return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
|
|
}
|
|
|
|
@property ulong size() const pure nothrow scope
|
|
{
|
|
return _size;
|
|
}
|
|
|
|
@property SysTime timeCreated() const pure nothrow return scope
|
|
{
|
|
return cast(SysTime)_timeCreated;
|
|
}
|
|
|
|
@property SysTime timeLastAccessed() const pure nothrow return scope
|
|
{
|
|
return cast(SysTime)_timeLastAccessed;
|
|
}
|
|
|
|
@property SysTime timeLastModified() const pure nothrow return scope
|
|
{
|
|
return cast(SysTime)_timeLastModified;
|
|
}
|
|
|
|
@property uint attributes() const pure nothrow scope
|
|
{
|
|
return _attributes;
|
|
}
|
|
|
|
@property uint linkAttributes() const pure nothrow scope
|
|
{
|
|
return _attributes;
|
|
}
|
|
|
|
private:
|
|
string _name; /// The file or directory represented by this DirEntry.
|
|
|
|
SysTime _timeCreated; /// The time when the file was created.
|
|
SysTime _timeLastAccessed; /// The time when the file was last accessed.
|
|
SysTime _timeLastModified; /// The time when the file was last modified.
|
|
|
|
ulong _size; /// The size of the file in bytes.
|
|
uint _attributes; /// The file attributes from WIN32_FIND_DATAW.
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
struct DirEntry
|
|
{
|
|
@safe:
|
|
public:
|
|
alias name this;
|
|
|
|
this(return scope string path)
|
|
{
|
|
if (!path.exists)
|
|
throw new FileException(path, "File does not exist");
|
|
|
|
_name = path;
|
|
|
|
_didLStat = false;
|
|
_didStat = false;
|
|
_dTypeSet = false;
|
|
}
|
|
|
|
private this(string path, core.sys.posix.dirent.dirent* fd) @safe
|
|
{
|
|
import std.path : buildPath;
|
|
|
|
static if (is(typeof(fd.d_namlen)))
|
|
immutable len = fd.d_namlen;
|
|
else
|
|
immutable len = (() @trusted => core.stdc.string.strlen(fd.d_name.ptr))();
|
|
|
|
_name = buildPath(path, (() @trusted => fd.d_name.ptr[0 .. len])());
|
|
|
|
_didLStat = false;
|
|
_didStat = false;
|
|
|
|
//fd_d_type doesn't work for all file systems,
|
|
//in which case the result is DT_UNKOWN. But we
|
|
//can determine the correct type from lstat, so
|
|
//we'll only set the dtype here if we could
|
|
//correctly determine it (not lstat in the case
|
|
//of DT_UNKNOWN in case we don't ever actually
|
|
//need the dtype, thus potentially avoiding the
|
|
//cost of calling lstat).
|
|
static if (__traits(compiles, fd.d_type != DT_UNKNOWN))
|
|
{
|
|
if (fd.d_type != DT_UNKNOWN)
|
|
{
|
|
_dType = fd.d_type;
|
|
_dTypeSet = true;
|
|
}
|
|
else
|
|
_dTypeSet = false;
|
|
}
|
|
else
|
|
{
|
|
// e.g. Solaris does not have the d_type member
|
|
_dTypeSet = false;
|
|
}
|
|
}
|
|
|
|
@property string name() const pure nothrow return scope
|
|
{
|
|
return _name;
|
|
}
|
|
|
|
@property bool isDir() scope
|
|
{
|
|
_ensureStatOrLStatDone();
|
|
|
|
return (_statBuf.st_mode & S_IFMT) == S_IFDIR;
|
|
}
|
|
|
|
@property bool isFile() scope
|
|
{
|
|
_ensureStatOrLStatDone();
|
|
|
|
return (_statBuf.st_mode & S_IFMT) == S_IFREG;
|
|
}
|
|
|
|
@property bool isSymlink() scope
|
|
{
|
|
_ensureLStatDone();
|
|
|
|
return (_lstatMode & S_IFMT) == S_IFLNK;
|
|
}
|
|
|
|
@property ulong size() scope
|
|
{
|
|
_ensureStatDone();
|
|
return _statBuf.st_size;
|
|
}
|
|
|
|
@property SysTime timeStatusChanged() scope
|
|
{
|
|
_ensureStatDone();
|
|
|
|
return statTimeToStdTime!'c'(_statBuf);
|
|
}
|
|
|
|
@property SysTime timeLastAccessed() scope
|
|
{
|
|
_ensureStatDone();
|
|
|
|
return statTimeToStdTime!'a'(_statBuf);
|
|
}
|
|
|
|
@property SysTime timeLastModified() scope
|
|
{
|
|
_ensureStatDone();
|
|
|
|
return statTimeToStdTime!'m'(_statBuf);
|
|
}
|
|
|
|
@property uint attributes() scope
|
|
{
|
|
_ensureStatDone();
|
|
|
|
return _statBuf.st_mode;
|
|
}
|
|
|
|
@property uint linkAttributes() scope
|
|
{
|
|
_ensureLStatDone();
|
|
|
|
return _lstatMode;
|
|
}
|
|
|
|
@property stat_t statBuf() scope
|
|
{
|
|
_ensureStatDone();
|
|
|
|
return _statBuf;
|
|
}
|
|
|
|
private:
|
|
/++
|
|
This is to support lazy evaluation, because doing stat's is
|
|
expensive and not always needed.
|
|
+/
|
|
void _ensureStatDone() @trusted scope
|
|
{
|
|
if (_didStat)
|
|
return;
|
|
|
|
cenforce(stat(_name.tempCString(), &_statBuf) == 0,
|
|
"Failed to stat file `" ~ _name ~ "'");
|
|
|
|
_didStat = true;
|
|
}
|
|
|
|
/++
|
|
This is to support lazy evaluation, because doing stat's is
|
|
expensive and not always needed.
|
|
|
|
Try both stat and lstat for isFile and isDir
|
|
to detect broken symlinks.
|
|
+/
|
|
void _ensureStatOrLStatDone() @trusted scope
|
|
{
|
|
if (_didStat)
|
|
return;
|
|
|
|
if (stat(_name.tempCString(), &_statBuf) != 0)
|
|
{
|
|
_ensureLStatDone();
|
|
|
|
_statBuf = stat_t.init;
|
|
_statBuf.st_mode = S_IFLNK;
|
|
}
|
|
else
|
|
{
|
|
_didStat = true;
|
|
}
|
|
}
|
|
|
|
/++
|
|
This is to support lazy evaluation, because doing stat's is
|
|
expensive and not always needed.
|
|
+/
|
|
void _ensureLStatDone() @trusted scope
|
|
{
|
|
if (_didLStat)
|
|
return;
|
|
|
|
stat_t statbuf = void;
|
|
cenforce(lstat(_name.tempCString(), &statbuf) == 0,
|
|
"Failed to stat file `" ~ _name ~ "'");
|
|
|
|
_lstatMode = statbuf.st_mode;
|
|
|
|
_dTypeSet = true;
|
|
_didLStat = true;
|
|
}
|
|
|
|
string _name; /// The file or directory represented by this DirEntry.
|
|
|
|
stat_t _statBuf = void; /// The result of stat().
|
|
uint _lstatMode; /// The stat mode from lstat().
|
|
ubyte _dType; /// The type of the file.
|
|
|
|
bool _didLStat = false; /// Whether lstat() has been called for this DirEntry.
|
|
bool _didStat = false; /// Whether stat() has been called for this DirEntry.
|
|
bool _dTypeSet = false; /// Whether the dType of the file has been set.
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
version (Windows)
|
|
{
|
|
if ("C:\\Program Files\\".exists)
|
|
{
|
|
auto de = DirEntry("C:\\Program Files\\");
|
|
assert(!de.isFile);
|
|
assert(de.isDir);
|
|
assert(!de.isSymlink);
|
|
}
|
|
|
|
if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
|
|
{
|
|
auto de = DirEntry("C:\\Documents and Settings\\");
|
|
assert(de.isSymlink);
|
|
}
|
|
|
|
if ("C:\\Windows\\system.ini".exists)
|
|
{
|
|
auto de = DirEntry("C:\\Windows\\system.ini");
|
|
assert(de.isFile);
|
|
assert(!de.isDir);
|
|
assert(!de.isSymlink);
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
if (system_directory.exists)
|
|
{
|
|
{
|
|
auto de = DirEntry(system_directory);
|
|
assert(!de.isFile);
|
|
assert(de.isDir);
|
|
assert(!de.isSymlink);
|
|
}
|
|
|
|
immutable symfile = deleteme ~ "_slink\0";
|
|
scope(exit) if (symfile.exists) symfile.remove();
|
|
|
|
core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
|
|
|
|
{
|
|
auto de = DirEntry(symfile);
|
|
assert(!de.isFile);
|
|
assert(de.isDir);
|
|
assert(de.isSymlink);
|
|
}
|
|
|
|
symfile.remove();
|
|
core.sys.posix.unistd.symlink((deleteme ~ "_broken_symlink\0").ptr, symfile.ptr);
|
|
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=8298
|
|
DirEntry de = DirEntry(symfile);
|
|
|
|
assert(!de.isFile);
|
|
assert(!de.isDir);
|
|
assert(de.isSymlink);
|
|
assertThrown!FileException(de.size);
|
|
assertThrown!FileException(de.timeStatusChanged);
|
|
assertThrown!FileException(de.timeLastAccessed);
|
|
assertThrown!FileException(de.timeLastModified);
|
|
assertThrown!FileException(de.attributes);
|
|
assertThrown!FileException(de.statBuf);
|
|
assert(symfile.exists);
|
|
symfile.remove();
|
|
}
|
|
}
|
|
|
|
if (system_file.exists)
|
|
{
|
|
auto de = DirEntry(system_file);
|
|
assert(de.isFile);
|
|
assert(!de.isDir);
|
|
assert(!de.isSymlink);
|
|
}
|
|
}
|
|
}
|
|
|
|
alias PreserveAttributes = Flag!"preserveAttributes";
|
|
|
|
version (StdDdoc)
|
|
{
|
|
/// Defaults to `Yes.preserveAttributes` on Windows, and the opposite on all other platforms.
|
|
PreserveAttributes preserveAttributesDefault;
|
|
}
|
|
else version (Windows)
|
|
{
|
|
enum preserveAttributesDefault = Yes.preserveAttributes;
|
|
}
|
|
else
|
|
{
|
|
enum preserveAttributesDefault = No.preserveAttributes;
|
|
}
|
|
|
|
/***************************************************
|
|
Copy file `from` _to file `to`. File timestamps are preserved.
|
|
File attributes are preserved, if `preserve` equals `Yes.preserveAttributes`.
|
|
On Windows only `Yes.preserveAttributes` (the default on Windows) is supported.
|
|
If the target file exists, it is overwritten.
|
|
|
|
Params:
|
|
from = string or range of characters representing the existing file name
|
|
to = string or range of characters representing the target file name
|
|
preserve = whether to _preserve the file attributes
|
|
|
|
Throws: $(LREF FileException) on error.
|
|
*/
|
|
void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault)
|
|
if (isSomeFiniteCharInputRange!RF && !isConvertibleToString!RF &&
|
|
isSomeFiniteCharInputRange!RT && !isConvertibleToString!RT)
|
|
{
|
|
// Place outside of @trusted block
|
|
auto fromz = from.tempCString!FSChar();
|
|
auto toz = to.tempCString!FSChar();
|
|
|
|
static if (isNarrowString!RF && is(immutable ElementEncodingType!RF == immutable char))
|
|
alias f = from;
|
|
else
|
|
enum string f = null;
|
|
|
|
static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable char))
|
|
alias t = to;
|
|
else
|
|
enum string t = null;
|
|
|
|
copyImpl(f, t, fromz, toz, preserve);
|
|
}
|
|
|
|
/// ditto
|
|
void copy(RF, RT)(auto ref RF from, auto ref RT to, PreserveAttributes preserve = preserveAttributesDefault)
|
|
if (isConvertibleToString!RF || isConvertibleToString!RT)
|
|
{
|
|
import std.meta : staticMap;
|
|
alias Types = staticMap!(convertToString, RF, RT);
|
|
copy!Types(from, to, preserve);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto source = deleteme ~ "source";
|
|
auto target = deleteme ~ "target";
|
|
auto targetNonExistent = deleteme ~ "target2";
|
|
|
|
scope(exit) source.remove, target.remove, targetNonExistent.remove;
|
|
|
|
source.write("source");
|
|
target.write("target");
|
|
|
|
assert(target.readText == "target");
|
|
|
|
source.copy(target);
|
|
assert(target.readText == "source");
|
|
|
|
source.copy(targetNonExistent);
|
|
assert(targetNonExistent.readText == "source");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15319
|
|
@safe unittest
|
|
{
|
|
assert(__traits(compiles, copy("from.txt", "to.txt")));
|
|
}
|
|
|
|
private void copyImpl(scope const(char)[] f, scope const(char)[] t,
|
|
scope const(FSChar)* fromz, scope const(FSChar)* toz,
|
|
PreserveAttributes preserve) @trusted
|
|
{
|
|
version (Windows)
|
|
{
|
|
assert(preserve == Yes.preserveAttributes);
|
|
immutable result = CopyFileW(fromz, toz, false);
|
|
if (!result)
|
|
{
|
|
import core.stdc.wchar_ : wcslen;
|
|
import std.conv : to;
|
|
import std.format : format;
|
|
|
|
/++
|
|
Reference resources: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew
|
|
Because OS copyfilew handles both source and destination paths,
|
|
the GetLastError does not accurately locate whether the error is for the source or destination.
|
|
+/
|
|
if (!f)
|
|
f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]);
|
|
if (!t)
|
|
t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
|
|
|
|
throw new FileException(format!"Copy from %s to %s"(f, t));
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
static import core.stdc.stdio;
|
|
import std.conv : to, octal;
|
|
|
|
immutable fdr = core.sys.posix.fcntl.open(fromz, O_RDONLY);
|
|
cenforce(fdr != -1, f, fromz);
|
|
scope(exit) core.sys.posix.unistd.close(fdr);
|
|
|
|
stat_t statbufr = void;
|
|
cenforce(fstat(fdr, &statbufr) == 0, f, fromz);
|
|
//cenforce(core.sys.posix.sys.stat.fstat(fdr, &statbufr) == 0, f, fromz);
|
|
|
|
immutable fdw = core.sys.posix.fcntl.open(toz,
|
|
O_CREAT | O_WRONLY, octal!666);
|
|
cenforce(fdw != -1, t, toz);
|
|
{
|
|
scope(failure) core.sys.posix.unistd.close(fdw);
|
|
|
|
stat_t statbufw = void;
|
|
cenforce(fstat(fdw, &statbufw) == 0, t, toz);
|
|
if (statbufr.st_dev == statbufw.st_dev && statbufr.st_ino == statbufw.st_ino)
|
|
throw new FileException(t, "Source and destination are the same file");
|
|
}
|
|
|
|
scope(failure) core.stdc.stdio.remove(toz);
|
|
{
|
|
scope(failure) core.sys.posix.unistd.close(fdw);
|
|
cenforce(ftruncate(fdw, 0) == 0, t, toz);
|
|
|
|
auto BUFSIZ = 4096u * 16;
|
|
auto buf = core.stdc.stdlib.malloc(BUFSIZ);
|
|
if (!buf)
|
|
{
|
|
BUFSIZ = 4096;
|
|
buf = core.stdc.stdlib.malloc(BUFSIZ);
|
|
if (!buf)
|
|
{
|
|
import core.exception : onOutOfMemoryError;
|
|
onOutOfMemoryError();
|
|
}
|
|
}
|
|
scope(exit) core.stdc.stdlib.free(buf);
|
|
|
|
for (auto size = statbufr.st_size; size; )
|
|
{
|
|
immutable toxfer = (size > BUFSIZ) ? BUFSIZ : cast(size_t) size;
|
|
cenforce(
|
|
core.sys.posix.unistd.read(fdr, buf, toxfer) == toxfer
|
|
&& core.sys.posix.unistd.write(fdw, buf, toxfer) == toxfer,
|
|
f, fromz);
|
|
assert(size >= toxfer);
|
|
size -= toxfer;
|
|
}
|
|
if (preserve)
|
|
cenforce(fchmod(fdw, to!mode_t(statbufr.st_mode)) == 0, f, fromz);
|
|
}
|
|
|
|
cenforce(core.sys.posix.unistd.close(fdw) != -1, f, fromz);
|
|
|
|
setTimesImpl(t, toz, statbufr.statTimeToStdTime!'a', statbufr.statTimeToStdTime!'m');
|
|
}
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14817
|
|
@safe unittest
|
|
{
|
|
import std.algorithm, std.file;
|
|
auto t1 = deleteme, t2 = deleteme~"2";
|
|
scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
|
|
write(t1, "11");
|
|
copy(t1, t2);
|
|
assert(readText(t2) == "11");
|
|
write(t1, "2");
|
|
copy(t1, t2);
|
|
assert(readText(t2) == "2");
|
|
|
|
import std.utf : byChar;
|
|
copy(t1.byChar, t2.byChar);
|
|
assert(readText(t2.byChar) == "2");
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=20370
|
|
version (Windows)
|
|
assert(t1.timeLastModified == t2.timeLastModified);
|
|
else static if (is(typeof(&utimensat)) || is(typeof(&setattrlist)))
|
|
assert(t1.timeLastModified == t2.timeLastModified);
|
|
else
|
|
assert(abs(t1.timeLastModified - t2.timeLastModified) < dur!"usecs"(1));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=11434
|
|
@safe version (Posix) @safe unittest
|
|
{
|
|
import std.conv : octal;
|
|
auto t1 = deleteme, t2 = deleteme~"2";
|
|
scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
|
|
write(t1, "1");
|
|
setAttributes(t1, octal!767);
|
|
copy(t1, t2, Yes.preserveAttributes);
|
|
assert(readText(t2) == "1");
|
|
assert(getAttributes(t2) == octal!100767);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15865
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
auto t = deleteme;
|
|
write(t, "a");
|
|
scope(exit) t.remove();
|
|
assertThrown!FileException(copy(t, t));
|
|
assert(readText(t) == "a");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=19834
|
|
version (Windows) @safe unittest
|
|
{
|
|
import std.exception : collectException;
|
|
import std.algorithm.searching : startsWith;
|
|
import std.format : format;
|
|
|
|
auto f = deleteme;
|
|
auto t = f ~ "2";
|
|
auto ex = collectException(copy(f, t));
|
|
assert(ex.msg.startsWith(format!"Copy from %s to %s"(f, t)));
|
|
}
|
|
|
|
/++
|
|
Remove directory and all of its content and subdirectories,
|
|
recursively.
|
|
|
|
Params:
|
|
pathname = the path of the directory to completely remove
|
|
de = The $(LREF DirEntry) to remove
|
|
|
|
Throws:
|
|
$(LREF FileException) if there is an error (including if the given
|
|
file is not a directory).
|
|
+/
|
|
void rmdirRecurse(scope const(char)[] pathname) @safe
|
|
{
|
|
//No references to pathname will be kept after rmdirRecurse,
|
|
//so the cast is safe
|
|
rmdirRecurse(DirEntry((() @trusted => cast(string) pathname)()));
|
|
}
|
|
|
|
/// ditto
|
|
void rmdirRecurse(ref scope DirEntry de) @safe
|
|
{
|
|
if (!de.isDir)
|
|
throw new FileException(de.name, "Not a directory");
|
|
|
|
if (de.isSymlink)
|
|
{
|
|
version (Windows)
|
|
rmdir(de.name);
|
|
else
|
|
remove(de.name);
|
|
}
|
|
else
|
|
{
|
|
// dirEntries is @system without DIP1000 because it uses
|
|
// a DirIterator with a SafeRefCounted variable, but here, no
|
|
// references to the payload are escaped to the outside, so this should
|
|
// be @trusted
|
|
() @trusted {
|
|
// all children, recursively depth-first
|
|
foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false))
|
|
{
|
|
attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name);
|
|
}
|
|
}();
|
|
|
|
// the dir itself
|
|
rmdir(de.name);
|
|
}
|
|
}
|
|
///ditto
|
|
//Note, without this overload, passing an RValue DirEntry still works, but
|
|
//actually fully reconstructs a DirEntry inside the
|
|
//"rmdirRecurse(in char[] pathname)" implementation. That is needlessly
|
|
//expensive.
|
|
//A DirEntry is a bit big (72B), so keeping the "by ref" signature is desirable.
|
|
void rmdirRecurse(scope DirEntry de) @safe
|
|
{
|
|
rmdirRecurse(de);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.path : buildPath;
|
|
|
|
auto dir = deleteme.buildPath("a", "b", "c");
|
|
|
|
dir.mkdirRecurse;
|
|
assert(dir.exists);
|
|
|
|
deleteme.rmdirRecurse;
|
|
assert(!dir.exists);
|
|
assert(!deleteme.exists);
|
|
}
|
|
|
|
version (Windows) @system unittest
|
|
{
|
|
import std.exception : enforce;
|
|
auto d = deleteme ~ r".dir\a\b\c\d\e\f\g";
|
|
mkdirRecurse(d);
|
|
rmdirRecurse(deleteme ~ ".dir");
|
|
enforce(!exists(deleteme ~ ".dir"));
|
|
}
|
|
|
|
version (Posix) @system unittest
|
|
{
|
|
import std.exception : enforce, collectException;
|
|
|
|
collectException(rmdirRecurse(deleteme));
|
|
auto d = deleteme~"/a/b/c/d/e/f/g";
|
|
enforce(collectException(mkdir(d)));
|
|
mkdirRecurse(d);
|
|
core.sys.posix.unistd.symlink((deleteme~"/a/b/c\0").ptr,
|
|
(deleteme~"/link\0").ptr);
|
|
rmdirRecurse(deleteme~"/link");
|
|
enforce(exists(d));
|
|
rmdirRecurse(deleteme);
|
|
enforce(!exists(deleteme));
|
|
|
|
d = deleteme~"/a/b/c/d/e/f/g";
|
|
mkdirRecurse(d);
|
|
const linkTarget = deleteme ~ "/link";
|
|
symlink(deleteme ~ "/a/b/c", linkTarget);
|
|
rmdirRecurse(deleteme);
|
|
enforce(!exists(deleteme));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
ubyte[] buf = new ubyte[10];
|
|
buf[] = 3;
|
|
string unit_file = deleteme ~ "-unittest_write.tmp";
|
|
if (exists(unit_file)) remove(unit_file);
|
|
write(unit_file, cast(void[]) buf);
|
|
void[] buf2 = read(unit_file);
|
|
assert(cast(void[]) buf == buf2);
|
|
|
|
string unit2_file = deleteme ~ "-unittest_write2.tmp";
|
|
copy(unit_file, unit2_file);
|
|
buf2 = read(unit2_file);
|
|
assert(cast(void[]) buf == buf2);
|
|
|
|
remove(unit_file);
|
|
assert(!exists(unit_file));
|
|
remove(unit2_file);
|
|
assert(!exists(unit2_file));
|
|
}
|
|
|
|
/**
|
|
* Dictates directory spanning policy for $(D_PARAM dirEntries) (see below).
|
|
*/
|
|
enum SpanMode
|
|
{
|
|
/** Only spans one directory. */
|
|
shallow,
|
|
/** Spans the directory in
|
|
$(HTTPS en.wikipedia.org/wiki/Tree_traversal#Post-order,
|
|
_depth-first $(B post)-order), i.e. the content of any
|
|
subdirectory is spanned before that subdirectory itself. Useful
|
|
e.g. when recursively deleting files. */
|
|
depth,
|
|
/** Spans the directory in
|
|
$(HTTPS en.wikipedia.org/wiki/Tree_traversal#Pre-order, depth-first
|
|
$(B pre)-order), i.e. the content of any subdirectory is spanned
|
|
right after that subdirectory itself.
|
|
|
|
Note that `SpanMode.breadth` will not result in all directory
|
|
members occurring before any subdirectory members, i.e. it is not
|
|
_true
|
|
$(HTTPS en.wikipedia.org/wiki/Tree_traversal#Breadth-first_search,
|
|
_breadth-first traversal).
|
|
*/
|
|
breadth,
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.algorithm.iteration : map;
|
|
import std.algorithm.sorting : sort;
|
|
import std.array : array;
|
|
import std.path : buildPath, relativePath;
|
|
|
|
auto root = deleteme ~ "root";
|
|
scope(exit) root.rmdirRecurse;
|
|
root.mkdir;
|
|
|
|
root.buildPath("animals").mkdir;
|
|
root.buildPath("animals", "cat").mkdir;
|
|
|
|
alias removeRoot = (return scope e) => e.relativePath(root);
|
|
|
|
assert(root.dirEntries(SpanMode.depth).map!removeRoot.equal(
|
|
[buildPath("animals", "cat"), "animals"]));
|
|
|
|
assert(root.dirEntries(SpanMode.breadth).map!removeRoot.equal(
|
|
["animals", buildPath("animals", "cat")]));
|
|
|
|
root.buildPath("plants").mkdir;
|
|
|
|
assert(root.dirEntries(SpanMode.shallow).array.sort.map!removeRoot.equal(
|
|
["animals", "plants"]));
|
|
}
|
|
|
|
private struct DirIteratorImpl
|
|
{
|
|
@safe:
|
|
SpanMode _mode;
|
|
// Whether we should follow symlinked directories while iterating.
|
|
// It also indicates whether we should avoid functions which call
|
|
// stat (since we should only need lstat in this case and it would
|
|
// be more efficient to not call stat in addition to lstat).
|
|
bool _followSymlink;
|
|
DirEntry _cur;
|
|
DirHandle[] _stack;
|
|
DirEntry[] _stashed; //used in depth first mode
|
|
string _pathPrefix = null;
|
|
|
|
//stack helpers
|
|
void pushExtra(DirEntry de)
|
|
{
|
|
_stashed ~= de;
|
|
}
|
|
|
|
//ditto
|
|
bool hasExtra()
|
|
{
|
|
return _stashed.length != 0;
|
|
}
|
|
|
|
//ditto
|
|
DirEntry popExtra()
|
|
{
|
|
DirEntry de;
|
|
de = _stashed[$-1];
|
|
_stashed.popBack();
|
|
return de;
|
|
}
|
|
|
|
version (Windows)
|
|
{
|
|
WIN32_FIND_DATAW _findinfo;
|
|
struct DirHandle
|
|
{
|
|
string dirpath;
|
|
HANDLE h;
|
|
}
|
|
|
|
bool stepIn(string directory) @safe
|
|
{
|
|
import std.path : chainPath;
|
|
auto searchPattern = chainPath(directory, "*.*");
|
|
|
|
static auto trustedFindFirstFileW(typeof(searchPattern) pattern, scope WIN32_FIND_DATAW* findinfo) @trusted
|
|
{
|
|
return FindFirstFileW(pattern.tempCString!FSChar(), findinfo);
|
|
}
|
|
|
|
HANDLE h = trustedFindFirstFileW(searchPattern, &_findinfo);
|
|
cenforce(h != INVALID_HANDLE_VALUE, directory);
|
|
_stack ~= DirHandle(directory, h);
|
|
return toNext(false, &_findinfo);
|
|
}
|
|
|
|
bool next()
|
|
{
|
|
if (_stack.length == 0)
|
|
return false;
|
|
return toNext(true, &_findinfo);
|
|
}
|
|
|
|
bool toNext(bool fetch, scope WIN32_FIND_DATAW* findinfo) @trusted
|
|
{
|
|
import core.stdc.wchar_ : wcscmp;
|
|
import std.string : chompPrefix;
|
|
|
|
if (fetch)
|
|
{
|
|
if (FindNextFileW(_stack[$-1].h, findinfo) == FALSE)
|
|
{
|
|
popDirStack();
|
|
return false;
|
|
}
|
|
}
|
|
while (wcscmp(&findinfo.cFileName[0], ".") == 0 ||
|
|
wcscmp(&findinfo.cFileName[0], "..") == 0)
|
|
if (FindNextFileW(_stack[$-1].h, findinfo) == FALSE)
|
|
{
|
|
popDirStack();
|
|
return false;
|
|
}
|
|
_cur = DirEntry(_stack[$-1].dirpath.chompPrefix(_pathPrefix), findinfo);
|
|
return true;
|
|
}
|
|
|
|
void popDirStack() @trusted
|
|
{
|
|
assert(_stack.length != 0);
|
|
FindClose(_stack[$-1].h);
|
|
_stack.popBack();
|
|
}
|
|
|
|
void releaseDirStack() @trusted
|
|
{
|
|
foreach (d; _stack)
|
|
FindClose(d.h);
|
|
}
|
|
|
|
bool mayStepIn()
|
|
{
|
|
return _followSymlink ? _cur.isDir : _cur.isDir && !_cur.isSymlink;
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
struct DirHandle
|
|
{
|
|
string dirpath;
|
|
DIR* h;
|
|
}
|
|
|
|
bool stepIn(string directory)
|
|
{
|
|
static auto trustedOpendir(string dir) @trusted
|
|
{
|
|
return opendir(dir.tempCString());
|
|
}
|
|
|
|
auto h = directory.length ? trustedOpendir(directory) : trustedOpendir(".");
|
|
cenforce(h, directory);
|
|
_stack ~= (DirHandle(directory, h));
|
|
return next();
|
|
}
|
|
|
|
bool next() @trusted
|
|
{
|
|
import std.string : chompPrefix;
|
|
|
|
if (_stack.length == 0)
|
|
return false;
|
|
|
|
for (dirent* fdata; (fdata = readdir(_stack[$-1].h)) != null; )
|
|
{
|
|
// Skip "." and ".."
|
|
if (core.stdc.string.strcmp(&fdata.d_name[0], ".") &&
|
|
core.stdc.string.strcmp(&fdata.d_name[0], ".."))
|
|
{
|
|
_cur = DirEntry(_stack[$-1].dirpath.chompPrefix(_pathPrefix), fdata);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
popDirStack();
|
|
return false;
|
|
}
|
|
|
|
void popDirStack() @trusted
|
|
{
|
|
assert(_stack.length != 0);
|
|
closedir(_stack[$-1].h);
|
|
_stack.popBack();
|
|
}
|
|
|
|
void releaseDirStack() @trusted
|
|
{
|
|
foreach (d; _stack)
|
|
closedir(d.h);
|
|
}
|
|
|
|
bool mayStepIn()
|
|
{
|
|
return _followSymlink ? _cur.isDir : attrIsDir(_cur.linkAttributes);
|
|
}
|
|
}
|
|
|
|
this(string pathname, SpanMode mode, bool followSymlink)
|
|
{
|
|
import std.path : absolutePath, isAbsolute;
|
|
|
|
_mode = mode;
|
|
_followSymlink = followSymlink;
|
|
|
|
if (!pathname.isAbsolute)
|
|
{
|
|
const pathnameRel = pathname;
|
|
alias pathnameAbs = pathname;
|
|
pathname = pathname.absolutePath;
|
|
|
|
const offset = pathnameAbs.length - pathnameRel.length;
|
|
_pathPrefix = pathnameAbs[0 .. offset];
|
|
}
|
|
|
|
if (stepIn(pathname))
|
|
{
|
|
if (_mode == SpanMode.depth)
|
|
while (mayStepIn())
|
|
{
|
|
auto thisDir = _cur;
|
|
if (stepIn(_cur.name))
|
|
{
|
|
pushExtra(thisDir);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@property bool empty()
|
|
{
|
|
return _stashed.length == 0 && _stack.length == 0;
|
|
}
|
|
|
|
@property DirEntry front()
|
|
{
|
|
return _cur;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
switch (_mode)
|
|
{
|
|
case SpanMode.depth:
|
|
if (next())
|
|
{
|
|
while (mayStepIn())
|
|
{
|
|
auto thisDir = _cur;
|
|
if (stepIn(_cur.name))
|
|
{
|
|
pushExtra(thisDir);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
else if (hasExtra())
|
|
_cur = popExtra();
|
|
break;
|
|
case SpanMode.breadth:
|
|
if (mayStepIn())
|
|
{
|
|
if (!stepIn(_cur.name))
|
|
while (!empty && !next()){}
|
|
}
|
|
else
|
|
while (!empty && !next()){}
|
|
break;
|
|
default:
|
|
next();
|
|
}
|
|
}
|
|
|
|
~this()
|
|
{
|
|
releaseDirStack();
|
|
}
|
|
}
|
|
|
|
// Must be a template, because the destructor is unsafe or safe depending on
|
|
// whether `-preview=dip1000` is in use. Otherwise, linking errors would
|
|
// result.
|
|
struct _DirIterator(bool useDIP1000)
|
|
{
|
|
static assert(useDIP1000 == dip1000Enabled,
|
|
"Please don't override useDIP1000 to disagree with compiler switch.");
|
|
|
|
private:
|
|
SafeRefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl;
|
|
|
|
this(string pathname, SpanMode mode, bool followSymlink) @trusted
|
|
{
|
|
impl = typeof(impl)(pathname, mode, followSymlink);
|
|
}
|
|
public:
|
|
@property bool empty() @trusted { return impl.empty; }
|
|
@property DirEntry front() @trusted { return impl.front; }
|
|
void popFront() @trusted { impl.popFront(); }
|
|
}
|
|
|
|
// This has the client code to automatically use and link to the correct
|
|
// template instance
|
|
alias DirIterator = _DirIterator!dip1000Enabled;
|
|
|
|
/++
|
|
Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
|
|
of `DirEntry` that lazily iterates a given directory,
|
|
also provides two ways of foreach iteration. The iteration variable can be of
|
|
type `string` if only the name is needed, or `DirEntry`
|
|
if additional details are needed. The span _mode dictates how the
|
|
directory is traversed. The name of each iterated directory entry
|
|
contains the absolute or relative _path (depending on _pathname).
|
|
|
|
Note: The order of returned directory entries is as it is provided by the
|
|
operating system / filesystem, and may not follow any particular sorting.
|
|
|
|
Params:
|
|
useDIP1000 = used to instantiate this function separately for code with
|
|
and without -preview=dip1000 compiler switch, because it
|
|
affects the ABI of this function. Set automatically -
|
|
don't touch.
|
|
|
|
path = The directory to iterate over.
|
|
If empty, the current directory will be iterated.
|
|
|
|
pattern = Optional string with wildcards, such as $(RED
|
|
"*.d"). When present, it is used to filter the
|
|
results by their file name. The supported wildcard
|
|
strings are described under $(REF globMatch,
|
|
std,_path).
|
|
|
|
mode = Whether the directory's sub-directories should be
|
|
iterated in depth-first post-order ($(LREF depth)),
|
|
depth-first pre-order ($(LREF breadth)), or not at all
|
|
($(LREF shallow)).
|
|
|
|
followSymlink = Whether symbolic links which point to directories
|
|
should be treated as directories and their contents
|
|
iterated over.
|
|
|
|
Returns:
|
|
An $(REF_ALTTEXT input range, isInputRange,std,range,primitives) of
|
|
$(LREF DirEntry).
|
|
|
|
Throws:
|
|
$(UL
|
|
$(LI $(LREF FileException) if the $(B path) directory does not exist or read permission is denied.)
|
|
$(LI $(LREF FileException) if $(B mode) is not `shallow` and a subdirectory cannot be read.)
|
|
)
|
|
|
|
Example:
|
|
--------------------
|
|
// Iterate a directory in depth
|
|
foreach (string name; dirEntries("destroy/me", SpanMode.depth))
|
|
{
|
|
remove(name);
|
|
}
|
|
|
|
// Iterate the current directory in breadth
|
|
foreach (string name; dirEntries("", SpanMode.breadth))
|
|
{
|
|
writeln(name);
|
|
}
|
|
|
|
// Iterate a directory and get detailed info about it
|
|
foreach (DirEntry e; dirEntries("dmd-testing", SpanMode.breadth))
|
|
{
|
|
writeln(e.name, "\t", e.size);
|
|
}
|
|
|
|
// Iterate over all *.d files in current directory and all its subdirectories
|
|
auto dFiles = dirEntries("", SpanMode.depth).filter!(f => f.name.endsWith(".d"));
|
|
foreach (d; dFiles)
|
|
writeln(d.name);
|
|
|
|
// Hook it up with std.parallelism to compile them all in parallel:
|
|
foreach (d; parallel(dFiles, 1)) //passes by 1 file to each thread
|
|
{
|
|
string cmd = "dmd -c " ~ d.name;
|
|
writeln(cmd);
|
|
std.process.executeShell(cmd);
|
|
}
|
|
|
|
// Iterate over all D source files in current directory and all its
|
|
// subdirectories
|
|
auto dFiles = dirEntries("","*.{d,di}",SpanMode.depth);
|
|
foreach (d; dFiles)
|
|
writeln(d.name);
|
|
--------------------
|
|
To handle subdirectories with denied read permission, use `SpanMode.shallow`:
|
|
---
|
|
void scan(string path)
|
|
{
|
|
foreach (DirEntry entry; dirEntries(path, SpanMode.shallow))
|
|
{
|
|
try
|
|
{
|
|
writeln(entry.name);
|
|
if (entry.isDir)
|
|
scan(entry.name);
|
|
}
|
|
catch (FileException fe) { continue; } // ignore
|
|
}
|
|
}
|
|
|
|
scan("");
|
|
---
|
|
+/
|
|
|
|
// For some reason, doing the same alias-to-a-template trick as with DirIterator
|
|
// does not work here.
|
|
auto dirEntries(bool useDIP1000 = dip1000Enabled)
|
|
(string path, SpanMode mode, bool followSymlink = true)
|
|
{
|
|
return _DirIterator!useDIP1000(path, mode, followSymlink);
|
|
}
|
|
|
|
/// Duplicate functionality of D1's `std.file.listdir()`:
|
|
@safe unittest
|
|
{
|
|
string[] listdir(string pathname)
|
|
{
|
|
import std.algorithm.iteration : map, filter;
|
|
import std.array : array;
|
|
import std.path : baseName;
|
|
|
|
return dirEntries(pathname, SpanMode.shallow)
|
|
.filter!(a => a.isFile)
|
|
.map!((return a) => baseName(a.name))
|
|
.array;
|
|
}
|
|
|
|
// Can be safe only with -preview=dip1000
|
|
@safe void main(string[] args)
|
|
{
|
|
import std.stdio : writefln;
|
|
|
|
string[] files = listdir(args[1]);
|
|
writefln("%s", files);
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.algorithm.iteration : map;
|
|
import std.algorithm.searching : startsWith;
|
|
import std.array : array;
|
|
import std.conv : to;
|
|
import std.path : buildPath, absolutePath;
|
|
import std.file : dirEntries;
|
|
import std.process : thisProcessID;
|
|
import std.range.primitives : walkLength;
|
|
|
|
version (Android)
|
|
string testdir = deleteme; // This has to be an absolute path when
|
|
// called from a shared library on Android,
|
|
// ie an apk
|
|
else
|
|
string testdir = tempDir.buildPath("deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID));
|
|
mkdirRecurse(buildPath(testdir, "somedir"));
|
|
scope(exit) rmdirRecurse(testdir);
|
|
write(buildPath(testdir, "somefile"), null);
|
|
write(buildPath(testdir, "somedir", "somedeepfile"), null);
|
|
|
|
// testing range interface
|
|
size_t equalEntries(string relpath, SpanMode mode)
|
|
{
|
|
import std.exception : enforce;
|
|
auto len = enforce(walkLength(dirEntries(absolutePath(relpath), mode)));
|
|
assert(walkLength(dirEntries(relpath, mode)) == len);
|
|
assert(equal(
|
|
map!((return a) => absolutePath(a.name))(dirEntries(relpath, mode)),
|
|
map!(a => a.name)(dirEntries(absolutePath(relpath), mode))));
|
|
return len;
|
|
}
|
|
|
|
assert(equalEntries(testdir, SpanMode.shallow) == 2);
|
|
assert(equalEntries(testdir, SpanMode.depth) == 3);
|
|
assert(equalEntries(testdir, SpanMode.breadth) == 3);
|
|
|
|
// testing opApply
|
|
foreach (string name; dirEntries(testdir, SpanMode.breadth))
|
|
{
|
|
//writeln(name);
|
|
assert(name.startsWith(testdir));
|
|
}
|
|
foreach (DirEntry e; dirEntries(absolutePath(testdir), SpanMode.breadth))
|
|
{
|
|
//writeln(name);
|
|
assert(e.isFile || e.isDir, e.name);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=7264
|
|
foreach (string name; dirEntries(testdir, "*.d", SpanMode.breadth))
|
|
{
|
|
|
|
}
|
|
foreach (entry; dirEntries(testdir, SpanMode.breadth))
|
|
{
|
|
static assert(is(typeof(entry) == DirEntry));
|
|
}
|
|
// https://issues.dlang.org/show_bug.cgi?id=7138
|
|
auto a = array(dirEntries(testdir, SpanMode.shallow));
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=11392
|
|
auto dFiles = dirEntries(testdir, SpanMode.shallow);
|
|
foreach (d; dFiles){}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15146
|
|
dirEntries("", SpanMode.shallow).walkLength();
|
|
|
|
// https://github.com/dlang/phobos/issues/9584
|
|
string cwd = getcwd();
|
|
foreach (string entry; dirEntries(testdir, SpanMode.shallow))
|
|
{
|
|
if (entry.isDir)
|
|
chdir(entry);
|
|
}
|
|
chdir(cwd); // needed for the directories to be removed
|
|
}
|
|
|
|
/// Ditto
|
|
auto dirEntries(bool useDIP1000 = dip1000Enabled)
|
|
(string path, string pattern, SpanMode mode,
|
|
bool followSymlink = true)
|
|
{
|
|
import std.algorithm.iteration : filter;
|
|
import std.path : globMatch, baseName;
|
|
|
|
bool f(DirEntry de) { return globMatch(baseName(de.name), pattern); }
|
|
return filter!f(_DirIterator!useDIP1000(path, mode, followSymlink));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.stdio : writefln;
|
|
immutable dpath = deleteme ~ "_dir";
|
|
immutable fpath = deleteme ~ "_file";
|
|
immutable sdpath = deleteme ~ "_sdir";
|
|
immutable sfpath = deleteme ~ "_sfile";
|
|
scope(exit)
|
|
{
|
|
if (dpath.exists) rmdirRecurse(dpath);
|
|
if (fpath.exists) remove(fpath);
|
|
if (sdpath.exists) remove(sdpath);
|
|
if (sfpath.exists) remove(sfpath);
|
|
}
|
|
|
|
mkdir(dpath);
|
|
write(fpath, "hello world");
|
|
version (Posix) () @trusted
|
|
{
|
|
core.sys.posix.unistd.symlink((dpath ~ '\0').ptr, (sdpath ~ '\0').ptr);
|
|
core.sys.posix.unistd.symlink((fpath ~ '\0').ptr, (sfpath ~ '\0').ptr);
|
|
} ();
|
|
|
|
static struct Flags { bool dir, file, link; }
|
|
auto tests = [dpath : Flags(true), fpath : Flags(false, true)];
|
|
version (Posix)
|
|
{
|
|
tests[sdpath] = Flags(true, false, true);
|
|
tests[sfpath] = Flags(false, true, true);
|
|
}
|
|
|
|
auto past = Clock.currTime() - 2.seconds;
|
|
auto future = past + 4.seconds;
|
|
|
|
foreach (path, flags; tests)
|
|
{
|
|
auto de = DirEntry(path);
|
|
assert(de.name == path);
|
|
assert(de.isDir == flags.dir);
|
|
assert(de.isFile == flags.file);
|
|
assert(de.isSymlink == flags.link);
|
|
|
|
assert(de.isDir == path.isDir);
|
|
assert(de.isFile == path.isFile);
|
|
assert(de.isSymlink == path.isSymlink);
|
|
assert(de.size == path.getSize());
|
|
assert(de.attributes == getAttributes(path));
|
|
assert(de.linkAttributes == getLinkAttributes(path));
|
|
|
|
scope(failure) writefln("[%s] [%s] [%s] [%s]", past, de.timeLastAccessed, de.timeLastModified, future);
|
|
assert(de.timeLastAccessed > past);
|
|
assert(de.timeLastAccessed < future);
|
|
assert(de.timeLastModified > past);
|
|
assert(de.timeLastModified < future);
|
|
|
|
assert(attrIsDir(de.attributes) == flags.dir);
|
|
assert(attrIsDir(de.linkAttributes) == (flags.dir && !flags.link));
|
|
assert(attrIsFile(de.attributes) == flags.file);
|
|
assert(attrIsFile(de.linkAttributes) == (flags.file && !flags.link));
|
|
assert(!attrIsSymlink(de.attributes));
|
|
assert(attrIsSymlink(de.linkAttributes) == flags.link);
|
|
|
|
version (Windows)
|
|
{
|
|
assert(de.timeCreated > past);
|
|
assert(de.timeCreated < future);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
assert(de.timeStatusChanged > past);
|
|
assert(de.timeStatusChanged < future);
|
|
assert(de.attributes == de.statBuf.st_mode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure that dirEntries does not butcher Unicode file names
|
|
// https://issues.dlang.org/show_bug.cgi?id=17962
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.algorithm.iteration : map;
|
|
import std.algorithm.sorting : sort;
|
|
import std.array : array;
|
|
import std.path : buildPath;
|
|
import std.uni : normalize;
|
|
|
|
// The Unicode normalization is required to make the tests pass on Mac OS X.
|
|
auto dir = deleteme ~ normalize("𐐷");
|
|
scope(exit) if (dir.exists) rmdirRecurse(dir);
|
|
mkdir(dir);
|
|
auto files = ["Hello World",
|
|
"Ma Chérie.jpeg",
|
|
"さいごの果実.txt"].map!(a => buildPath(dir, normalize(a)))().array();
|
|
sort(files);
|
|
foreach (file; files)
|
|
write(file, "nothing");
|
|
|
|
auto result = dirEntries(dir, SpanMode.shallow).map!((return a) => a.name.normalize()).array();
|
|
sort(result);
|
|
|
|
assert(equal(files, result));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=21250
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
assertThrown!Exception(dirEntries("237f5babd6de21f40915826699582e36", "*.bin", SpanMode.depth));
|
|
}
|
|
|
|
/**
|
|
* Reads a file line by line and parses the line into a single value or a
|
|
* $(REF Tuple, std,typecons) of values depending on the length of `Types`.
|
|
* The lines are parsed using the specified format string. The format string is
|
|
* passed to $(REF formattedRead, std,_format), and therefore must conform to the
|
|
* _format string specification outlined in $(MREF std, _format).
|
|
*
|
|
* Params:
|
|
* Types = the types that each of the elements in the line should be returned as
|
|
* filename = the name of the file to read
|
|
* format = the _format string to use when reading
|
|
*
|
|
* Returns:
|
|
* If only one type is passed, then an array of that type. Otherwise, an
|
|
* array of $(REF Tuple, std,typecons)s.
|
|
*
|
|
* Throws:
|
|
* `Exception` if the format string is malformed. Also, throws `Exception`
|
|
* if any of the lines in the file are not fully consumed by the call
|
|
* to $(REF formattedRead, std,_format). Meaning that no empty lines or lines
|
|
* with extra characters are allowed.
|
|
*/
|
|
Select!(Types.length == 1, Types[0][], Tuple!(Types)[])
|
|
slurp(Types...)(string filename, scope const(char)[] format)
|
|
{
|
|
import std.array : appender;
|
|
import std.conv : text;
|
|
import std.exception : enforce;
|
|
import std.format.read : formattedRead;
|
|
import std.stdio : File;
|
|
import std.string : stripRight;
|
|
|
|
auto app = appender!(typeof(return))();
|
|
ElementType!(typeof(return)) toAdd;
|
|
auto f = File(filename);
|
|
scope(exit) f.close();
|
|
foreach (line; f.byLine())
|
|
{
|
|
formattedRead(line, format, &toAdd);
|
|
enforce(line.stripRight("\r").empty,
|
|
text("Trailing characters at the end of line: `", line,
|
|
"'"));
|
|
app.put(toAdd);
|
|
}
|
|
return app.data;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.typecons : tuple;
|
|
|
|
scope(exit)
|
|
{
|
|
assert(exists(deleteme));
|
|
remove(deleteme);
|
|
}
|
|
|
|
write(deleteme, "12 12.25\n345 1.125"); // deleteme is the name of a temporary file
|
|
|
|
// Load file; each line is an int followed by comma, whitespace and a
|
|
// double.
|
|
auto a = slurp!(int, double)(deleteme, "%s %s");
|
|
assert(a.length == 2);
|
|
assert(a[0] == tuple(12, 12.25));
|
|
assert(a[1] == tuple(345, 1.125));
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.typecons : tuple;
|
|
|
|
scope(exit)
|
|
{
|
|
assert(exists(deleteme));
|
|
remove(deleteme);
|
|
}
|
|
write(deleteme, "10\r\n20");
|
|
assert(slurp!(int)(deleteme, "%d") == [10, 20]);
|
|
}
|
|
|
|
/**
|
|
Returns the path to a directory for temporary files.
|
|
On POSIX platforms, it searches through the following list of directories
|
|
and returns the first one which is found to exist:
|
|
$(OL
|
|
$(LI The directory given by the `TMPDIR` environment variable.)
|
|
$(LI The directory given by the `TEMP` environment variable.)
|
|
$(LI The directory given by the `TMP` environment variable.)
|
|
$(LI `/tmp/`)
|
|
$(LI `/var/tmp/`)
|
|
$(LI `/usr/tmp/`)
|
|
)
|
|
|
|
On all platforms, `tempDir` returns the current working directory on failure.
|
|
|
|
The return value of the function is cached, so the procedures described
|
|
below will only be performed the first time the function is called. All
|
|
subsequent runs will return the same string, regardless of whether
|
|
environment variables and directory structures have changed in the
|
|
meantime.
|
|
|
|
The POSIX `tempDir` algorithm is inspired by Python's
|
|
$(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, `tempfile.tempdir`).
|
|
|
|
Returns:
|
|
On Windows, this function returns the result of calling the Windows API function
|
|
$(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992.aspx, `GetTempPath`).
|
|
|
|
On POSIX platforms, it searches through the following list of directories
|
|
and returns the first one which is found to exist:
|
|
$(OL
|
|
$(LI The directory given by the `TMPDIR` environment variable.)
|
|
$(LI The directory given by the `TEMP` environment variable.)
|
|
$(LI The directory given by the `TMP` environment variable.)
|
|
$(LI `/tmp`)
|
|
$(LI `/var/tmp`)
|
|
$(LI `/usr/tmp`)
|
|
)
|
|
|
|
On all platforms, `tempDir` returns `"."` on failure, representing
|
|
the current working directory.
|
|
*/
|
|
string tempDir() @trusted
|
|
{
|
|
// We must check that the end of a path is not a separator, before adding another
|
|
// If we don't we end up with https://issues.dlang.org/show_bug.cgi?id=22738
|
|
static string addSeparator(string input)
|
|
{
|
|
import std.path : dirSeparator;
|
|
import std.algorithm.searching : endsWith;
|
|
|
|
// It is very rare a directory path will reach this point with a directory separator at the end
|
|
// However on OSX this can happen, so we must verify lest we break user code i.e. https://github.com/dlang/dub/pull/2208
|
|
if (!input.endsWith(dirSeparator))
|
|
return input ~ dirSeparator;
|
|
else
|
|
return input;
|
|
}
|
|
|
|
static string cache;
|
|
if (cache is null)
|
|
{
|
|
version (Windows)
|
|
{
|
|
import std.conv : to;
|
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992(v=vs.85).aspx
|
|
wchar[MAX_PATH + 2] buf;
|
|
DWORD len = GetTempPathW(buf.length, buf.ptr);
|
|
if (len) cache = buf[0 .. len].to!string;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
import std.process : environment;
|
|
// This function looks through the list of alternative directories
|
|
// and returns the first one which exists and is a directory.
|
|
static string findExistingDir(T...)(lazy T alternatives)
|
|
{
|
|
foreach (dir; alternatives)
|
|
if (!dir.empty && exists(dir)) return addSeparator(dir);
|
|
return null;
|
|
}
|
|
|
|
cache = findExistingDir(environment.get("TMPDIR"),
|
|
environment.get("TEMP"),
|
|
environment.get("TMP"),
|
|
"/tmp",
|
|
"/var/tmp",
|
|
"/usr/tmp");
|
|
}
|
|
else static assert(false, "Unsupported platform");
|
|
|
|
if (cache is null)
|
|
{
|
|
cache = addSeparator(getcwd());
|
|
}
|
|
}
|
|
return cache;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.ascii : letters;
|
|
import std.conv : to;
|
|
import std.path : buildPath;
|
|
import std.random : randomSample;
|
|
import std.utf : byCodeUnit;
|
|
|
|
// random id with 20 letters
|
|
auto id = letters.byCodeUnit.randomSample(20).to!string;
|
|
auto myFile = tempDir.buildPath(id ~ "my_tmp_file");
|
|
scope(exit) myFile.remove;
|
|
|
|
myFile.write("hello");
|
|
assert(myFile.readText == "hello");
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.searching : endsWith;
|
|
import std.path : dirSeparator;
|
|
assert(tempDir.endsWith(dirSeparator));
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=22738
|
|
assert(!tempDir.endsWith(dirSeparator ~ dirSeparator));
|
|
}
|
|
|
|
/**
|
|
Returns the available disk space based on a given path.
|
|
On Windows, `path` must be a directory; on POSIX systems, it can be a file or directory.
|
|
|
|
Params:
|
|
path = on Windows, it must be a directory; on POSIX it can be a file or directory
|
|
Returns:
|
|
Available space in bytes
|
|
|
|
Throws:
|
|
$(LREF FileException) in case of failure
|
|
*/
|
|
ulong getAvailableDiskSpace(scope const(char)[] path) @safe
|
|
{
|
|
version (Windows)
|
|
{
|
|
import core.sys.windows.winbase : GetDiskFreeSpaceExW;
|
|
import core.sys.windows.winnt : ULARGE_INTEGER;
|
|
import std.internal.cstring : tempCStringW;
|
|
|
|
ULARGE_INTEGER freeBytesAvailable;
|
|
auto err = () @trusted {
|
|
return GetDiskFreeSpaceExW(path.tempCStringW(), &freeBytesAvailable, null, null);
|
|
} ();
|
|
cenforce(err != 0, "Cannot get available disk space");
|
|
|
|
return freeBytesAvailable.QuadPart;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
import std.internal.cstring : tempCString;
|
|
|
|
version (FreeBSD)
|
|
{
|
|
import core.sys.freebsd.sys.mount : statfs, statfs_t;
|
|
|
|
statfs_t stats;
|
|
auto err = () @trusted {
|
|
return statfs(path.tempCString(), &stats);
|
|
} ();
|
|
cenforce(err == 0, "Cannot get available disk space");
|
|
|
|
return stats.f_bavail * stats.f_bsize;
|
|
}
|
|
else
|
|
{
|
|
import core.sys.posix.sys.statvfs : statvfs, statvfs_t;
|
|
|
|
statvfs_t stats;
|
|
auto err = () @trusted {
|
|
return statvfs(path.tempCString(), &stats);
|
|
} ();
|
|
cenforce(err == 0, "Cannot get available disk space");
|
|
|
|
return stats.f_bavail * stats.f_frsize;
|
|
}
|
|
}
|
|
else static assert(0, "Unsupported platform");
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
auto space = getAvailableDiskSpace(".");
|
|
assert(space > 0);
|
|
|
|
assertThrown!FileException(getAvailableDiskSpace("ThisFileDoesNotExist123123"));
|
|
}
|