1// Written in the D programming language.
2
3/**
4Utilities for manipulating files and scanning directories. Functions
5in this module handle files as a unit, e.g., read or write one file
6at a time. For opening files and manipulating them via handles refer
7to module $(MREF std, stdio).
8
9$(SCRIPT inhibitQuickIndex = 1;)
10$(DIVC quickindex,
11$(BOOKTABLE,
12$(TR $(TH Category) $(TH Functions))
13$(TR $(TD General) $(TD
14          $(LREF exists)
15          $(LREF isDir)
16          $(LREF isFile)
17          $(LREF isSymlink)
18          $(LREF rename)
19          $(LREF thisExePath)
20))
21$(TR $(TD Directories) $(TD
22          $(LREF chdir)
23          $(LREF dirEntries)
24          $(LREF getcwd)
25          $(LREF mkdir)
26          $(LREF mkdirRecurse)
27          $(LREF rmdir)
28          $(LREF rmdirRecurse)
29          $(LREF tempDir)
30))
31$(TR $(TD Files) $(TD
32          $(LREF append)
33          $(LREF copy)
34          $(LREF read)
35          $(LREF readText)
36          $(LREF remove)
37          $(LREF slurp)
38          $(LREF write)
39))
40$(TR $(TD Symlinks) $(TD
41          $(LREF symlink)
42          $(LREF readLink)
43))
44$(TR $(TD Attributes) $(TD
45          $(LREF attrIsDir)
46          $(LREF attrIsFile)
47          $(LREF attrIsSymlink)
48          $(LREF getAttributes)
49          $(LREF getLinkAttributes)
50          $(LREF getSize)
51          $(LREF setAttributes)
52))
53$(TR $(TD Timestamp) $(TD
54          $(LREF getTimes)
55          $(LREF getTimesWin)
56          $(LREF setTimes)
57          $(LREF timeLastModified)
58          $(LREF timeLastAccessed)
59          $(LREF timeStatusChanged)
60))
61$(TR $(TD Other) $(TD
62          $(LREF DirEntry)
63          $(LREF FileException)
64          $(LREF PreserveAttributes)
65          $(LREF SpanMode)
66          $(LREF getAvailableDiskSpace)
67))
68))
69
70
71Copyright: Copyright The D Language Foundation 2007 - 2011.
72See_Also:  The $(HTTP ddili.org/ders/d.en/files.html, official tutorial) for an
73introduction to working with files in D, module
74$(MREF std, stdio) for opening files and manipulating them via handles,
75and module $(MREF std, path) for manipulating path strings.
76
77License:   $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
78Authors:   $(HTTP digitalmars.com, Walter Bright),
79           $(HTTP erdani.org, Andrei Alexandrescu),
80           $(HTTP jmdavisprog.com, Jonathan M Davis)
81Source:    $(PHOBOSSRC std/file.d)
82 */
83module std.file;
84
85import core.stdc.errno, core.stdc.stdlib, core.stdc.string;
86import core.time : abs, dur, hnsecs, seconds;
87
88import std.datetime.date : DateTime;
89import std.datetime.systime : Clock, SysTime, unixTimeToStdTime;
90import std.internal.cstring;
91import std.meta;
92import std.range;
93import std.traits;
94import std.typecons;
95
96version (OSX)
97    version = Darwin;
98else version (iOS)
99    version = Darwin;
100else version (TVOS)
101    version = Darwin;
102else version (WatchOS)
103    version = Darwin;
104
105version (Windows)
106{
107    import core.sys.windows.winbase, core.sys.windows.winnt, std.windows.syserror;
108}
109else version (Posix)
110{
111    import core.sys.posix.dirent, core.sys.posix.fcntl, core.sys.posix.sys.stat,
112        core.sys.posix.sys.time, core.sys.posix.unistd, core.sys.posix.utime;
113}
114else
115    static assert(false, "Module " ~ .stringof ~ " not implemented for this OS.");
116
117// Character type used for operating system filesystem APIs
118version (Windows)
119{
120    private alias FSChar = WCHAR;       // WCHAR can be aliased to wchar or wchar_t
121}
122else version (Posix)
123{
124    private alias FSChar = char;
125}
126else
127    static assert(0);
128
129// Purposefully not documented. Use at your own risk
130@property string deleteme() @safe
131{
132    import std.conv : text;
133    import std.path : buildPath;
134    import std.process : thisProcessID;
135
136    enum base = "deleteme.dmd.unittest.pid";
137    static string fileName;
138
139    if (!fileName)
140        fileName = text(buildPath(tempDir(), base), thisProcessID);
141    return fileName;
142}
143
144version (StdUnittest) private struct TestAliasedString
145{
146    string get() @safe @nogc pure nothrow return scope { return _s; }
147    alias get this;
148    @disable this(this);
149    string _s;
150}
151
152version (Android)
153{
154    package enum system_directory = "/system/etc";
155    package enum system_file      = "/system/etc/hosts";
156}
157else version (Posix)
158{
159    package enum system_directory = "/usr/include";
160    package enum system_file      = "/usr/include/assert.h";
161}
162
163
164/++
165    Exception thrown for file I/O errors.
166 +/
167class FileException : Exception
168{
169    import std.conv : text, to;
170
171    /++
172        OS error code.
173     +/
174    immutable uint errno;
175
176    private this(scope const(char)[] name, scope const(char)[] msg, string file, size_t line, uint errno) @safe pure
177    {
178        if (msg.empty)
179            super(name.idup, file, line);
180        else
181            super(text(name, ": ", msg), file, line);
182
183        this.errno = errno;
184    }
185
186    /++
187        Constructor which takes an error message.
188
189        Params:
190            name = Name of file for which the error occurred.
191            msg  = Message describing the error.
192            file = The file where the error occurred.
193            line = The _line where the error occurred.
194     +/
195    this(scope const(char)[] name, scope const(char)[] msg, string file = __FILE__, size_t line = __LINE__) @safe pure
196    {
197        this(name, msg, file, line, 0);
198    }
199
200    /++
201        Constructor which takes the error number ($(LUCKY GetLastError)
202        in Windows, $(D_PARAM errno) in POSIX).
203
204        Params:
205            name  = Name of file for which the error occurred.
206            errno = The error number.
207            file  = The file where the error occurred.
208                    Defaults to `__FILE__`.
209            line  = The _line where the error occurred.
210                    Defaults to `__LINE__`.
211     +/
212    version (Windows) this(scope const(char)[] name,
213                          uint errno = .GetLastError(),
214                          string file = __FILE__,
215                          size_t line = __LINE__) @safe
216    {
217        this(name, generateSysErrorMsg(errno), file, line, errno);
218    }
219    else version (Posix) this(scope const(char)[] name,
220                             uint errno = .errno,
221                             string file = __FILE__,
222                             size_t line = __LINE__) @trusted
223    {
224        import std.exception : errnoString;
225        this(name, errnoString(errno), file, line, errno);
226    }
227}
228
229///
230@safe unittest
231{
232    import std.exception : assertThrown;
233
234    assertThrown!FileException("non.existing.file.".readText);
235}
236
237private T cenforce(T)(T condition, lazy scope const(char)[] name, string file = __FILE__, size_t line = __LINE__)
238{
239    if (condition)
240        return condition;
241    version (Windows)
242    {
243        throw new FileException(name, .GetLastError(), file, line);
244    }
245    else version (Posix)
246    {
247        throw new FileException(name, .errno, file, line);
248    }
249}
250
251version (Windows)
252@trusted
253private T cenforce(T)(T condition, scope const(char)[] name, scope const(FSChar)* namez,
254    string file = __FILE__, size_t line = __LINE__)
255{
256    if (condition)
257        return condition;
258    if (!name)
259    {
260        import core.stdc.wchar_ : wcslen;
261        import std.conv : to;
262
263        auto len = namez ? wcslen(namez) : 0;
264        name = to!string(namez[0 .. len]);
265    }
266    throw new FileException(name, .GetLastError(), file, line);
267}
268
269version (Posix)
270@trusted
271private T cenforce(T)(T condition, scope const(char)[] name, scope const(FSChar)* namez,
272    string file = __FILE__, size_t line = __LINE__)
273{
274    if (condition)
275        return condition;
276    if (!name)
277    {
278        import core.stdc.string : strlen;
279
280        auto len = namez ? strlen(namez) : 0;
281        name = namez[0 .. len].idup;
282    }
283    throw new FileException(name, .errno, file, line);
284}
285
286// https://issues.dlang.org/show_bug.cgi?id=17102
287@safe unittest
288{
289    try
290    {
291        cenforce(false, null, null,
292                __FILE__, __LINE__);
293    }
294    catch (FileException) {}
295}
296
297/* **********************************
298 * Basic File operations.
299 */
300
301/********************************************
302Read entire contents of file `name` and returns it as an untyped
303array. If the file size is larger than `upTo`, only `upTo`
304bytes are _read.
305
306Params:
307    name = string or range of characters representing the file _name
308    upTo = if present, the maximum number of bytes to _read
309
310Returns: Untyped array of bytes _read.
311
312Throws: $(LREF FileException) on error.
313 */
314
315void[] read(R)(R name, size_t upTo = size_t.max)
316if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
317{
318    static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
319        return readImpl(name, name.tempCString!FSChar(), upTo);
320    else
321        return readImpl(null, name.tempCString!FSChar(), upTo);
322}
323
324///
325@safe unittest
326{
327    import std.utf : byChar;
328    scope(exit)
329    {
330        assert(exists(deleteme));
331        remove(deleteme);
332    }
333
334    std.file.write(deleteme, "1234"); // deleteme is the name of a temporary file
335    assert(read(deleteme, 2) == "12");
336    assert(read(deleteme.byChar) == "1234");
337    assert((cast(const(ubyte)[])read(deleteme)).length == 4);
338}
339
340/// ditto
341void[] read(R)(auto ref R name, size_t upTo = size_t.max)
342if (isConvertibleToString!R)
343{
344    return read!(StringTypeOf!R)(name, upTo);
345}
346
347@safe unittest
348{
349    static assert(__traits(compiles, read(TestAliasedString(null))));
350}
351
352version (Posix) private void[] readImpl(scope const(char)[] name, scope const(FSChar)* namez,
353                                        size_t upTo = size_t.max) @trusted
354{
355    import core.memory : GC;
356    import std.algorithm.comparison : min;
357    import std.conv : to;
358    import std.checkedint : checked;
359
360    // A few internal configuration parameters {
361    enum size_t
362        minInitialAlloc = 1024 * 4,
363        maxInitialAlloc = size_t.max / 2,
364        sizeIncrement = 1024 * 16,
365        maxSlackMemoryAllowed = 1024;
366    // }
367
368    immutable fd = core.sys.posix.fcntl.open(namez,
369            core.sys.posix.fcntl.O_RDONLY);
370    cenforce(fd != -1, name);
371    scope(exit) core.sys.posix.unistd.close(fd);
372
373    stat_t statbuf = void;
374    cenforce(fstat(fd, &statbuf) == 0, name, namez);
375
376    immutable initialAlloc = min(upTo, to!size_t(statbuf.st_size
377        ? min(statbuf.st_size + 1, maxInitialAlloc)
378        : minInitialAlloc));
379    void[] result = GC.malloc(initialAlloc, GC.BlkAttr.NO_SCAN)[0 .. initialAlloc];
380    scope(failure) GC.free(result.ptr);
381
382    auto size = checked(size_t(0));
383
384    for (;;)
385    {
386        immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size.get,
387                (min(result.length, upTo) - size).get);
388        cenforce(actual != -1, name, namez);
389        if (actual == 0) break;
390        size += actual;
391        if (size >= upTo) break;
392        if (size < result.length) continue;
393        immutable newAlloc = size + sizeIncrement;
394        result = GC.realloc(result.ptr, newAlloc.get, GC.BlkAttr.NO_SCAN)[0 .. newAlloc.get];
395    }
396
397    return result.length - size >= maxSlackMemoryAllowed
398        ? GC.realloc(result.ptr, size.get, GC.BlkAttr.NO_SCAN)[0 .. size.get]
399        : result[0 .. size.get];
400}
401
402version (Windows)
403private extern (Windows) @nogc nothrow
404{
405    pragma(mangle, CreateFileW.mangleof)
406    HANDLE trustedCreateFileW(scope const(wchar)* namez, DWORD dwDesiredAccess,
407        DWORD dwShareMode, SECURITY_ATTRIBUTES* lpSecurityAttributes,
408        DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
409        HANDLE hTemplateFile)  @trusted;
410
411    pragma(mangle, CloseHandle.mangleof) BOOL trustedCloseHandle(HANDLE) @trusted;
412}
413
414version (Windows) private void[] readImpl(scope const(char)[] name, scope const(FSChar)* namez,
415                                          size_t upTo = size_t.max) @trusted
416{
417    import core.memory : GC;
418    import std.algorithm.comparison : min;
419    static trustedGetFileSize(HANDLE hFile, out ulong fileSize)
420    {
421        DWORD sizeHigh;
422        DWORD sizeLow = GetFileSize(hFile, &sizeHigh);
423        const bool result = sizeLow != INVALID_FILE_SIZE;
424        if (result)
425            fileSize = makeUlong(sizeLow, sizeHigh);
426        return result;
427    }
428    static trustedReadFile(HANDLE hFile, void *lpBuffer, size_t nNumberOfBytesToRead)
429    {
430        // Read by chunks of size < 4GB (Windows API limit)
431        size_t totalNumRead = 0;
432        while (totalNumRead != nNumberOfBytesToRead)
433        {
434            const uint chunkSize = min(nNumberOfBytesToRead - totalNumRead, 0xffff_0000);
435            DWORD numRead = void;
436            const result = ReadFile(hFile, lpBuffer + totalNumRead, chunkSize, &numRead, null);
437            if (result == 0 || numRead != chunkSize)
438                return false;
439            totalNumRead += chunkSize;
440        }
441        return true;
442    }
443
444    alias defaults =
445        AliasSeq!(GENERIC_READ,
446            FILE_SHARE_READ | FILE_SHARE_WRITE, (SECURITY_ATTRIBUTES*).init,
447            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
448            HANDLE.init);
449    auto h = trustedCreateFileW(namez, defaults);
450
451    cenforce(h != INVALID_HANDLE_VALUE, name, namez);
452    scope(exit) cenforce(trustedCloseHandle(h), name, namez);
453    ulong fileSize = void;
454    cenforce(trustedGetFileSize(h, fileSize), name, namez);
455    size_t size = min(upTo, fileSize);
456    auto buf = () { return GC.malloc(size, GC.BlkAttr.NO_SCAN)[0 .. size]; } ();
457
458    scope(failure)
459    {
460        () { GC.free(buf.ptr); } ();
461    }
462
463    if (size)
464        cenforce(trustedReadFile(h, &buf[0], size), name, namez);
465    return buf[0 .. size];
466}
467
468version (linux) @safe unittest
469{
470    // A file with "zero" length that doesn't have 0 length at all
471    auto s = std.file.readText("/proc/cpuinfo");
472    assert(s.length > 0);
473    //writefln("'%s'", s);
474}
475
476@safe unittest
477{
478    scope(exit) if (exists(deleteme)) remove(deleteme);
479    import std.stdio;
480    auto f = File(deleteme, "w");
481    f.write("abcd"); f.flush();
482    assert(read(deleteme) == "abcd");
483}
484
485/++
486    Reads and validates (using $(REF validate, std, utf)) a text file. S can be
487    an array of any character type. However, no width or endian conversions are
488    performed. So, if the width or endianness of the characters in the given
489    file differ from the width or endianness of the element type of S, then
490    validation will fail.
491
492    Params:
493        S = the string type of the file
494        name = string or range of characters representing the file _name
495
496    Returns: Array of characters read.
497
498    Throws: $(LREF FileException) if there is an error reading the file,
499            $(REF UTFException, std, utf) on UTF decoding error.
500+/
501S readText(S = string, R)(auto ref R name)
502if (isSomeString!S && (isSomeFiniteCharInputRange!R || is(StringTypeOf!R)))
503{
504    import std.algorithm.searching : startsWith;
505    import std.encoding : getBOM, BOM;
506    import std.exception : enforce;
507    import std.format : format;
508    import std.utf : UTFException, validate;
509
510    static if (is(StringTypeOf!R))
511        StringTypeOf!R filename = name;
512    else
513        auto filename = name;
514
515    static auto trustedCast(T)(void[] buf) @trusted { return cast(T) buf; }
516    auto data = trustedCast!(ubyte[])(read(filename));
517
518    immutable bomSeq = getBOM(data);
519    immutable bom = bomSeq.schema;
520
521    static if (is(immutable ElementEncodingType!S == immutable char))
522    {
523        with(BOM) switch (bom)
524        {
525            case utf16be:
526            case utf16le: throw new UTFException("UTF-8 requested. BOM is for UTF-16");
527            case utf32be:
528            case utf32le: throw new UTFException("UTF-8 requested. BOM is for UTF-32");
529            default: break;
530        }
531    }
532    else static if (is(immutable ElementEncodingType!S == immutable wchar))
533    {
534        with(BOM) switch (bom)
535        {
536            case utf8: throw new UTFException("UTF-16 requested. BOM is for UTF-8");
537            case utf16be:
538            {
539                version (BigEndian)
540                    break;
541                else
542                    throw new UTFException("BOM is for UTF-16 LE on Big Endian machine");
543            }
544            case utf16le:
545            {
546                version (BigEndian)
547                    throw new UTFException("BOM is for UTF-16 BE on Little Endian machine");
548                else
549                    break;
550            }
551            case utf32be:
552            case utf32le: throw new UTFException("UTF-8 requested. BOM is for UTF-32");
553            default: break;
554        }
555    }
556    else
557    {
558        with(BOM) switch (bom)
559        {
560            case utf8: throw new UTFException("UTF-16 requested. BOM is for UTF-8");
561            case utf16be:
562            case utf16le: throw new UTFException("UTF-8 requested. BOM is for UTF-16");
563            case utf32be:
564            {
565                version (BigEndian)
566                    break;
567                else
568                    throw new UTFException("BOM is for UTF-32 LE on Big Endian machine");
569            }
570            case utf32le:
571            {
572                version (BigEndian)
573                    throw new UTFException("BOM is for UTF-32 BE on Little Endian machine");
574                else
575                    break;
576            }
577            default: break;
578        }
579    }
580
581    if (data.length % ElementEncodingType!S.sizeof != 0)
582        throw new UTFException(format!"The content of %s is not UTF-%s"(filename, ElementEncodingType!S.sizeof * 8));
583
584    auto result = trustedCast!S(data);
585    validate(result);
586    return result;
587}
588
589/// Read file with UTF-8 text.
590@safe unittest
591{
592    write(deleteme, "abc"); // deleteme is the name of a temporary file
593    scope(exit) remove(deleteme);
594    string content = readText(deleteme);
595    assert(content == "abc");
596}
597
598// Read file with UTF-8 text but try to read it as UTF-16.
599@safe unittest
600{
601    import std.exception : assertThrown;
602    import std.utf : UTFException;
603
604    write(deleteme, "abc");
605    scope(exit) remove(deleteme);
606    // Throws because the file is not valid UTF-16.
607    assertThrown!UTFException(readText!wstring(deleteme));
608}
609
610// Read file with UTF-16 text.
611@safe unittest
612{
613    import std.algorithm.searching : skipOver;
614
615    write(deleteme, "\uFEFFabc"w); // With BOM
616    scope(exit) remove(deleteme);
617    auto content = readText!wstring(deleteme);
618    assert(content == "\uFEFFabc"w);
619    // Strips BOM if present.
620    content.skipOver('\uFEFF');
621    assert(content == "abc"w);
622}
623
624@safe unittest
625{
626    static assert(__traits(compiles, readText(TestAliasedString(null))));
627}
628
629@safe unittest
630{
631    import std.array : appender;
632    import std.bitmanip : append, Endian;
633    import std.exception : assertThrown;
634    import std.path : buildPath;
635    import std.string : representation;
636    import std.utf : UTFException;
637
638    mkdir(deleteme);
639    scope(exit) rmdirRecurse(deleteme);
640
641    immutable none8 = buildPath(deleteme, "none8");
642    immutable none16 = buildPath(deleteme, "none16");
643    immutable utf8 = buildPath(deleteme, "utf8");
644    immutable utf16be = buildPath(deleteme, "utf16be");
645    immutable utf16le = buildPath(deleteme, "utf16le");
646    immutable utf32be = buildPath(deleteme, "utf32be");
647    immutable utf32le = buildPath(deleteme, "utf32le");
648    immutable utf7 = buildPath(deleteme, "utf7");
649
650    write(none8, "���������");
651    write(none16, "���������"w);
652    write(utf8, (cast(char[])[0xEF, 0xBB, 0xBF]) ~ "���������");
653    {
654        auto str = "\uFEFF���������"w;
655        auto arr = appender!(ubyte[])();
656        foreach (c; str)
657            arr.append(c);
658        write(utf16be, arr.data);
659    }
660    {
661        auto str = "\uFEFF���������"w;
662        auto arr = appender!(ubyte[])();
663        foreach (c; str)
664            arr.append!(ushort, Endian.littleEndian)(c);
665        write(utf16le, arr.data);
666    }
667    {
668        auto str = "\U0000FEFF���������"d;
669        auto arr = appender!(ubyte[])();
670        foreach (c; str)
671            arr.append(c);
672        write(utf32be, arr.data);
673    }
674    {
675        auto str = "\U0000FEFF���������"d;
676        auto arr = appender!(ubyte[])();
677        foreach (c; str)
678            arr.append!(uint, Endian.littleEndian)(c);
679        write(utf32le, arr.data);
680    }
681    write(utf7, (cast(ubyte[])[0x2B, 0x2F, 0x76, 0x38, 0x2D]) ~ "foobar".representation);
682
683    assertThrown!UTFException(readText(none16));
684    assert(readText(utf8) == (cast(char[])[0xEF, 0xBB, 0xBF]) ~ "���������");
685    assertThrown!UTFException(readText(utf16be));
686    assertThrown!UTFException(readText(utf16le));
687    assertThrown!UTFException(readText(utf32be));
688    assertThrown!UTFException(readText(utf32le));
689    assert(readText(utf7) == (cast(char[])[0x2B, 0x2F, 0x76, 0x38, 0x2D]) ~ "foobar");
690
691    assertThrown!UTFException(readText!wstring(none8));
692    assert(readText!wstring(none16) == "���������"w);
693    assertThrown!UTFException(readText!wstring(utf8));
694    version (BigEndian)
695    {
696        assert(readText!wstring(utf16be) == "\uFEFF���������"w);
697        assertThrown!UTFException(readText!wstring(utf16le));
698    }
699    else
700    {
701        assertThrown!UTFException(readText!wstring(utf16be));
702        assert(readText!wstring(utf16le) == "\uFEFF���������"w);
703    }
704    assertThrown!UTFException(readText!wstring(utf32be));
705    assertThrown!UTFException(readText!wstring(utf32le));
706    assertThrown!UTFException(readText!wstring(utf7));
707
708    assertThrown!UTFException(readText!dstring(utf8));
709    assertThrown!UTFException(readText!dstring(utf16be));
710    assertThrown!UTFException(readText!dstring(utf16le));
711    version (BigEndian)
712    {
713       assert(readText!dstring(utf32be) == "\U0000FEFF���������"d);
714       assertThrown!UTFException(readText!dstring(utf32le));
715    }
716    else
717    {
718       assertThrown!UTFException(readText!dstring(utf32be));
719       assert(readText!dstring(utf32le) == "\U0000FEFF���������"d);
720    }
721    assertThrown!UTFException(readText!dstring(utf7));
722}
723
724/*********************************************
725Write `buffer` to file `name`.
726
727Creates the file if it does not already exist.
728
729Params:
730    name = string or range of characters representing the file _name
731    buffer = data to be written to file
732
733Throws: $(LREF FileException) on error.
734
735See_also: $(REF toFile, std,stdio)
736 */
737void write(R)(R name, const void[] buffer)
738if ((isSomeFiniteCharInputRange!R || isSomeString!R) && !isConvertibleToString!R)
739{
740    static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
741        writeImpl(name, name.tempCString!FSChar(), buffer, false);
742    else
743        writeImpl(null, name.tempCString!FSChar(), buffer, false);
744}
745
746///
747@safe unittest
748{
749   scope(exit)
750   {
751       assert(exists(deleteme));
752       remove(deleteme);
753   }
754
755   int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
756   write(deleteme, a); // deleteme is the name of a temporary file
757   const bytes = read(deleteme);
758   const fileInts = () @trusted { return cast(int[]) bytes; }();
759   assert(fileInts == a);
760}
761
762/// ditto
763void write(R)(auto ref R name, const void[] buffer)
764if (isConvertibleToString!R)
765{
766    write!(StringTypeOf!R)(name, buffer);
767}
768
769@safe unittest
770{
771    static assert(__traits(compiles, write(TestAliasedString(null), null)));
772}
773
774/*********************************************
775Appends `buffer` to file `name`.
776
777Creates the file if it does not already exist.
778
779Params:
780    name = string or range of characters representing the file _name
781    buffer = data to be appended to file
782
783Throws: $(LREF FileException) on error.
784 */
785void append(R)(R name, const void[] buffer)
786if ((isSomeFiniteCharInputRange!R || isSomeString!R) && !isConvertibleToString!R)
787{
788    static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
789        writeImpl(name, name.tempCString!FSChar(), buffer, true);
790    else
791        writeImpl(null, name.tempCString!FSChar(), buffer, true);
792}
793
794///
795@safe unittest
796{
797   scope(exit)
798   {
799       assert(exists(deleteme));
800       remove(deleteme);
801   }
802
803   int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
804   write(deleteme, a); // deleteme is the name of a temporary file
805   int[] b = [ 13, 21 ];
806   append(deleteme, b);
807   const bytes = read(deleteme);
808   const fileInts = () @trusted { return cast(int[]) bytes; }();
809   assert(fileInts == a ~ b);
810}
811
812/// ditto
813void append(R)(auto ref R name, const void[] buffer)
814if (isConvertibleToString!R)
815{
816    append!(StringTypeOf!R)(name, buffer);
817}
818
819@safe unittest
820{
821    static assert(__traits(compiles, append(TestAliasedString("foo"), [0, 1, 2, 3])));
822}
823
824// POSIX implementation helper for write and append
825
826version (Posix) private void writeImpl(scope const(char)[] name, scope const(FSChar)* namez,
827        scope const(void)[] buffer, bool append) @trusted
828{
829    import std.conv : octal;
830
831    // append or write
832    auto mode = append ? O_CREAT | O_WRONLY | O_APPEND
833                       : O_CREAT | O_WRONLY | O_TRUNC;
834
835    immutable fd = core.sys.posix.fcntl.open(namez, mode, octal!666);
836    cenforce(fd != -1, name, namez);
837    {
838        scope(failure) core.sys.posix.unistd.close(fd);
839
840        immutable size = buffer.length;
841        size_t sum, cnt = void;
842        while (sum != size)
843        {
844            cnt = (size - sum < 2^^30) ? (size - sum) : 2^^30;
845            const numwritten = core.sys.posix.unistd.write(fd, buffer.ptr + sum, cnt);
846            if (numwritten != cnt)
847                break;
848            sum += numwritten;
849        }
850        cenforce(sum == size, name, namez);
851    }
852    cenforce(core.sys.posix.unistd.close(fd) == 0, name, namez);
853}
854
855// Windows implementation helper for write and append
856
857version (Windows) private void writeImpl(scope const(char)[] name, scope const(FSChar)* namez,
858        scope const(void)[] buffer, bool append) @trusted
859{
860    HANDLE h;
861    if (append)
862    {
863        alias defaults =
864            AliasSeq!(GENERIC_WRITE, 0, null, OPEN_ALWAYS,
865                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
866                HANDLE.init);
867
868        h = CreateFileW(namez, defaults);
869        cenforce(h != INVALID_HANDLE_VALUE, name, namez);
870        cenforce(SetFilePointer(h, 0, null, FILE_END) != INVALID_SET_FILE_POINTER,
871            name, namez);
872    }
873    else // write
874    {
875        alias defaults =
876            AliasSeq!(GENERIC_WRITE, 0, null, CREATE_ALWAYS,
877                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
878                HANDLE.init);
879
880        h = CreateFileW(namez, defaults);
881        cenforce(h != INVALID_HANDLE_VALUE, name, namez);
882    }
883    immutable size = buffer.length;
884    size_t sum, cnt = void;
885    DWORD numwritten = void;
886    while (sum != size)
887    {
888        cnt = (size - sum < 2^^30) ? (size - sum) : 2^^30;
889        WriteFile(h, buffer.ptr + sum, cast(uint) cnt, &numwritten, null);
890        if (numwritten != cnt)
891            break;
892        sum += numwritten;
893    }
894    cenforce(sum == size && CloseHandle(h), name, namez);
895}
896
897/***************************************************
898 * Rename file `from` _to `to`, moving it between directories if required.
899 * If the target file exists, it is overwritten.
900 *
901 * It is not possible to rename a file across different mount points
902 * or drives. On POSIX, the operation is atomic. That means, if `to`
903 * already exists there will be no time period during the operation
904 * where `to` is missing. See
905 * $(HTTP man7.org/linux/man-pages/man2/rename.2.html, manpage for rename)
906 * for more details.
907 *
908 * Params:
909 *    from = string or range of characters representing the existing file name
910 *    to = string or range of characters representing the target file name
911 *
912 * Throws: $(LREF FileException) on error.
913 */
914void rename(RF, RT)(RF from, RT to)
915if ((isSomeFiniteCharInputRange!RF || isSomeString!RF) && !isConvertibleToString!RF &&
916    (isSomeFiniteCharInputRange!RT || isSomeString!RT) && !isConvertibleToString!RT)
917{
918    // Place outside of @trusted block
919    auto fromz = from.tempCString!FSChar();
920    auto toz = to.tempCString!FSChar();
921
922    static if (isNarrowString!RF && is(immutable ElementEncodingType!RF == immutable char))
923        alias f = from;
924    else
925        enum string f = null;
926
927    static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable char))
928        alias t = to;
929    else
930        enum string t = null;
931
932    renameImpl(f, t, fromz, toz);
933}
934
935/// ditto
936void rename(RF, RT)(auto ref RF from, auto ref RT to)
937if (isConvertibleToString!RF || isConvertibleToString!RT)
938{
939    import std.meta : staticMap;
940    alias Types = staticMap!(convertToString, RF, RT);
941    rename!Types(from, to);
942}
943
944@safe unittest
945{
946    static assert(__traits(compiles, rename(TestAliasedString(null), TestAliasedString(null))));
947    static assert(__traits(compiles, rename("", TestAliasedString(null))));
948    static assert(__traits(compiles, rename(TestAliasedString(null), "")));
949    import std.utf : byChar;
950    static assert(__traits(compiles, rename(TestAliasedString(null), "".byChar)));
951}
952
953///
954@safe unittest
955{
956    auto t1 = deleteme, t2 = deleteme~"2";
957    scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
958
959    t1.write("1");
960    t1.rename(t2);
961    assert(t2.readText == "1");
962
963    t1.write("2");
964    t1.rename(t2);
965    assert(t2.readText == "2");
966}
967
968private void renameImpl(scope const(char)[] f, scope const(char)[] t,
969                        scope const(FSChar)* fromz, scope const(FSChar)* toz) @trusted
970{
971    version (Windows)
972    {
973        import std.exception : enforce;
974
975        const result = MoveFileExW(fromz, toz, MOVEFILE_REPLACE_EXISTING);
976        if (!result)
977        {
978            import core.stdc.wchar_ : wcslen;
979            import std.conv : to, text;
980
981            if (!f)
982                f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]);
983
984            if (!t)
985                t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
986
987            enforce(false,
988                new FileException(
989                    text("Attempting to rename file ", f, " to ", t)));
990        }
991    }
992    else version (Posix)
993    {
994        static import core.stdc.stdio;
995
996        cenforce(core.stdc.stdio.rename(fromz, toz) == 0, t, toz);
997    }
998}
999
1000@safe unittest
1001{
1002    import std.utf : byWchar;
1003
1004    auto t1 = deleteme, t2 = deleteme~"2";
1005    scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
1006
1007    write(t1, "1");
1008    rename(t1, t2);
1009    assert(readText(t2) == "1");
1010
1011    write(t1, "2");
1012    rename(t1, t2.byWchar);
1013    assert(readText(t2) == "2");
1014}
1015
1016/***************************************************
1017Delete file `name`.
1018
1019Params:
1020    name = string or range of characters representing the file _name
1021
1022Throws: $(LREF FileException) on error.
1023 */
1024void remove(R)(R name)
1025if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1026{
1027    static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
1028        removeImpl(name, name.tempCString!FSChar());
1029    else
1030        removeImpl(null, name.tempCString!FSChar());
1031}
1032
1033/// ditto
1034void remove(R)(auto ref R name)
1035if (isConvertibleToString!R)
1036{
1037    remove!(StringTypeOf!R)(name);
1038}
1039
1040///
1041@safe unittest
1042{
1043    import std.exception : assertThrown;
1044
1045    deleteme.write("Hello");
1046    assert(deleteme.readText == "Hello");
1047
1048    deleteme.remove;
1049    assertThrown!FileException(deleteme.readText);
1050}
1051
1052@safe unittest
1053{
1054    static assert(__traits(compiles, remove(TestAliasedString("foo"))));
1055}
1056
1057private void removeImpl(scope const(char)[] name, scope const(FSChar)* namez) @trusted
1058{
1059    version (Windows)
1060    {
1061        cenforce(DeleteFileW(namez), name, namez);
1062    }
1063    else version (Posix)
1064    {
1065        static import core.stdc.stdio;
1066
1067        if (!name)
1068        {
1069            import core.stdc.string : strlen;
1070            auto len = strlen(namez);
1071            name = namez[0 .. len];
1072        }
1073        cenforce(core.stdc.stdio.remove(namez) == 0,
1074            "Failed to remove file " ~ name);
1075    }
1076}
1077
1078version (Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(R)(R name)
1079if (isSomeFiniteCharInputRange!R)
1080{
1081    auto namez = name.tempCString!FSChar();
1082
1083    WIN32_FILE_ATTRIBUTE_DATA fad = void;
1084
1085    static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
1086    {
1087        static void getFA(scope const(char)[] name, scope const(FSChar)* namez,
1088                          out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
1089        {
1090            import std.exception : enforce;
1091            enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad),
1092                new FileException(name.idup));
1093        }
1094        getFA(name, namez, fad);
1095    }
1096    else
1097    {
1098        static void getFA(scope const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
1099        {
1100            import core.stdc.wchar_ : wcslen;
1101            import std.conv : to;
1102            import std.exception : enforce;
1103
1104            enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad),
1105                new FileException(namez[0 .. wcslen(namez)].to!string));
1106        }
1107        getFA(namez, fad);
1108    }
1109    return fad;
1110}
1111
1112version (Windows) private ulong makeUlong(DWORD dwLow, DWORD dwHigh) @safe pure nothrow @nogc
1113{
1114    ULARGE_INTEGER li;
1115    li.LowPart  = dwLow;
1116    li.HighPart = dwHigh;
1117    return li.QuadPart;
1118}
1119
1120version (Posix) private extern (C) pragma(mangle, stat.mangleof)
1121int trustedStat(scope const(FSChar)* namez, ref stat_t buf) @nogc nothrow @trusted;
1122
1123/**
1124Get size of file `name` in bytes.
1125
1126Params:
1127    name = string or range of characters representing the file _name
1128Returns:
1129    The size of file in bytes.
1130Throws:
1131    $(LREF FileException) on error (e.g., file not found).
1132 */
1133ulong getSize(R)(R name)
1134if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1135{
1136    version (Windows)
1137    {
1138        with (getFileAttributesWin(name))
1139            return makeUlong(nFileSizeLow, nFileSizeHigh);
1140    }
1141    else version (Posix)
1142    {
1143        auto namez = name.tempCString();
1144
1145        static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
1146            alias names = name;
1147        else
1148            string names = null;
1149        stat_t statbuf = void;
1150        cenforce(trustedStat(namez, statbuf) == 0, names, namez);
1151        return statbuf.st_size;
1152    }
1153}
1154
1155/// ditto
1156ulong getSize(R)(auto ref R name)
1157if (isConvertibleToString!R)
1158{
1159    return getSize!(StringTypeOf!R)(name);
1160}
1161
1162@safe unittest
1163{
1164    static assert(__traits(compiles, getSize(TestAliasedString("foo"))));
1165}
1166
1167///
1168@safe unittest
1169{
1170    scope(exit) deleteme.remove;
1171
1172    // create a file of size 1
1173    write(deleteme, "a");
1174    assert(getSize(deleteme) == 1);
1175
1176    // create a file of size 3
1177    write(deleteme, "abc");
1178    assert(getSize(deleteme) == 3);
1179}
1180
1181@safe unittest
1182{
1183    // create a file of size 1
1184    write(deleteme, "a");
1185    scope(exit) deleteme.exists && deleteme.remove;
1186    assert(getSize(deleteme) == 1);
1187    // create a file of size 3
1188    write(deleteme, "abc");
1189    import std.utf : byChar;
1190    assert(getSize(deleteme.byChar) == 3);
1191}
1192
1193// Reads a time field from a stat_t with full precision.
1194version (Posix)
1195private SysTime statTimeToStdTime(char which)(ref const stat_t statbuf)
1196{
1197    auto unixTime = mixin(`statbuf.st_` ~ which ~ `time`);
1198    long stdTime = unixTimeToStdTime(unixTime);
1199
1200    static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `tim`))))
1201        stdTime += mixin(`statbuf.st_` ~ which ~ `tim.tv_nsec`) / 100;
1202    else
1203    static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `timensec`))))
1204        stdTime += mixin(`statbuf.st_` ~ which ~ `timensec`) / 100;
1205    else
1206    static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `time_nsec`))))
1207        stdTime += mixin(`statbuf.st_` ~ which ~ `time_nsec`) / 100;
1208    else
1209    static if (is(typeof(mixin(`statbuf.__st_` ~ which ~ `timensec`))))
1210        stdTime += mixin(`statbuf.__st_` ~ which ~ `timensec`) / 100;
1211
1212    return SysTime(stdTime);
1213}
1214
1215/++
1216    Get the access and modified times of file or folder `name`.
1217
1218    Params:
1219        name             = File/Folder _name to get times for.
1220        accessTime       = Time the file/folder was last accessed.
1221        modificationTime = Time the file/folder was last modified.
1222
1223    Throws:
1224        $(LREF FileException) on error.
1225 +/
1226void getTimes(R)(R name,
1227              out SysTime accessTime,
1228              out SysTime modificationTime)
1229if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1230{
1231    version (Windows)
1232    {
1233        import std.datetime.systime : FILETIMEToSysTime;
1234
1235        with (getFileAttributesWin(name))
1236        {
1237            accessTime = FILETIMEToSysTime(&ftLastAccessTime);
1238            modificationTime = FILETIMEToSysTime(&ftLastWriteTime);
1239        }
1240    }
1241    else version (Posix)
1242    {
1243        auto namez = name.tempCString();
1244
1245        stat_t statbuf = void;
1246
1247        static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
1248            alias names = name;
1249        else
1250            string names = null;
1251        cenforce(trustedStat(namez, statbuf) == 0, names, namez);
1252
1253        accessTime = statTimeToStdTime!'a'(statbuf);
1254        modificationTime = statTimeToStdTime!'m'(statbuf);
1255    }
1256}
1257
1258/// ditto
1259void getTimes(R)(auto ref R name,
1260              out SysTime accessTime,
1261              out SysTime modificationTime)
1262if (isConvertibleToString!R)
1263{
1264    return getTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
1265}
1266
1267///
1268@safe unittest
1269{
1270    import std.datetime : abs, SysTime;
1271
1272    scope(exit) deleteme.remove;
1273    write(deleteme, "a");
1274
1275    SysTime accessTime, modificationTime;
1276
1277    getTimes(deleteme, accessTime, modificationTime);
1278
1279    import std.datetime : Clock, seconds;
1280    auto currTime = Clock.currTime();
1281    enum leeway = 5.seconds;
1282
1283    auto diffAccess = accessTime - currTime;
1284    auto diffModification = modificationTime - currTime;
1285    assert(abs(diffAccess) <= leeway);
1286    assert(abs(diffModification) <= leeway);
1287}
1288
1289@safe unittest
1290{
1291    SysTime atime, mtime;
1292    static assert(__traits(compiles, getTimes(TestAliasedString("foo"), atime, mtime)));
1293}
1294
1295@safe unittest
1296{
1297    import std.stdio : writefln;
1298
1299    auto currTime = Clock.currTime();
1300
1301    write(deleteme, "a");
1302    scope(exit) assert(deleteme.exists), deleteme.remove;
1303
1304    SysTime accessTime1;
1305    SysTime modificationTime1;
1306
1307    getTimes(deleteme, accessTime1, modificationTime1);
1308
1309    enum leeway = 5.seconds;
1310
1311    {
1312        auto diffa = accessTime1 - currTime;
1313        auto diffm = modificationTime1 - currTime;
1314        scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime1, modificationTime1, currTime, diffa, diffm);
1315
1316        assert(abs(diffa) <= leeway);
1317        assert(abs(diffm) <= leeway);
1318    }
1319
1320    version (fullFileTests)
1321    {
1322        import core.thread;
1323        enum sleepTime = dur!"seconds"(2);
1324        Thread.sleep(sleepTime);
1325
1326        currTime = Clock.currTime();
1327        write(deleteme, "b");
1328
1329        SysTime accessTime2 = void;
1330        SysTime modificationTime2 = void;
1331
1332        getTimes(deleteme, accessTime2, modificationTime2);
1333
1334        {
1335            auto diffa = accessTime2 - currTime;
1336            auto diffm = modificationTime2 - currTime;
1337            scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime2, modificationTime2, currTime, diffa, diffm);
1338
1339            //There is no guarantee that the access time will be updated.
1340            assert(abs(diffa) <= leeway + sleepTime);
1341            assert(abs(diffm) <= leeway);
1342        }
1343
1344        assert(accessTime1 <= accessTime2);
1345        assert(modificationTime1 <= modificationTime2);
1346    }
1347}
1348
1349
1350version (StdDdoc)
1351{
1352    /++
1353     $(BLUE This function is Windows-Only.)
1354
1355     Get creation/access/modified times of file `name`.
1356
1357     This is the same as `getTimes` except that it also gives you the file
1358     creation time - which isn't possible on POSIX systems.
1359
1360     Params:
1361     name                 = File _name to get times for.
1362     fileCreationTime     = Time the file was created.
1363     fileAccessTime       = Time the file was last accessed.
1364     fileModificationTime = Time the file was last modified.
1365
1366     Throws:
1367     $(LREF FileException) on error.
1368     +/
1369    void getTimesWin(R)(R name,
1370                        out SysTime fileCreationTime,
1371                        out SysTime fileAccessTime,
1372                        out SysTime fileModificationTime)
1373    if (isSomeFiniteCharInputRange!R || isConvertibleToString!R);
1374    // above line contains both constraints for docs
1375    // (so users know how it can be called)
1376}
1377else version (Windows)
1378{
1379    void getTimesWin(R)(R name,
1380                        out SysTime fileCreationTime,
1381                        out SysTime fileAccessTime,
1382                        out SysTime fileModificationTime)
1383    if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1384    {
1385        import std.datetime.systime : FILETIMEToSysTime;
1386
1387        with (getFileAttributesWin(name))
1388        {
1389            fileCreationTime = FILETIMEToSysTime(&ftCreationTime);
1390            fileAccessTime = FILETIMEToSysTime(&ftLastAccessTime);
1391            fileModificationTime = FILETIMEToSysTime(&ftLastWriteTime);
1392        }
1393    }
1394
1395    void getTimesWin(R)(auto ref R name,
1396                        out SysTime fileCreationTime,
1397                        out SysTime fileAccessTime,
1398                        out SysTime fileModificationTime)
1399    if (isConvertibleToString!R)
1400    {
1401        getTimesWin!(StringTypeOf!R)(name, fileCreationTime, fileAccessTime, fileModificationTime);
1402    }
1403}
1404
1405version (Windows) @system unittest
1406{
1407    import std.stdio : writefln;
1408    auto currTime = Clock.currTime();
1409
1410    write(deleteme, "a");
1411    scope(exit) { assert(exists(deleteme)); remove(deleteme); }
1412
1413    SysTime creationTime1 = void;
1414    SysTime accessTime1 = void;
1415    SysTime modificationTime1 = void;
1416
1417    getTimesWin(deleteme, creationTime1, accessTime1, modificationTime1);
1418
1419    enum leeway = dur!"seconds"(5);
1420
1421    {
1422        auto diffc = creationTime1 - currTime;
1423        auto diffa = accessTime1 - currTime;
1424        auto diffm = modificationTime1 - currTime;
1425        scope(failure)
1426        {
1427            writefln("[%s] [%s] [%s] [%s] [%s] [%s] [%s]",
1428                     creationTime1, accessTime1, modificationTime1, currTime, diffc, diffa, diffm);
1429        }
1430
1431        // Deleting and recreating a file doesn't seem to always reset the "file creation time"
1432        //assert(abs(diffc) <= leeway);
1433        assert(abs(diffa) <= leeway);
1434        assert(abs(diffm) <= leeway);
1435    }
1436
1437    version (fullFileTests)
1438    {
1439        import core.thread;
1440        Thread.sleep(dur!"seconds"(2));
1441
1442        currTime = Clock.currTime();
1443        write(deleteme, "b");
1444
1445        SysTime creationTime2 = void;
1446        SysTime accessTime2 = void;
1447        SysTime modificationTime2 = void;
1448
1449        getTimesWin(deleteme, creationTime2, accessTime2, modificationTime2);
1450
1451        {
1452            auto diffa = accessTime2 - currTime;
1453            auto diffm = modificationTime2 - currTime;
1454            scope(failure)
1455            {
1456                writefln("[%s] [%s] [%s] [%s] [%s]",
1457                         accessTime2, modificationTime2, currTime, diffa, diffm);
1458            }
1459
1460            assert(abs(diffa) <= leeway);
1461            assert(abs(diffm) <= leeway);
1462        }
1463
1464        assert(creationTime1 == creationTime2);
1465        assert(accessTime1 <= accessTime2);
1466        assert(modificationTime1 <= modificationTime2);
1467    }
1468
1469    {
1470        SysTime ctime, atime, mtime;
1471        static assert(__traits(compiles, getTimesWin(TestAliasedString("foo"), ctime, atime, mtime)));
1472    }
1473}
1474
1475version (Darwin)
1476private
1477{
1478    import core.stdc.config : c_ulong;
1479    enum ATTR_CMN_MODTIME  = 0x00000400, ATTR_CMN_ACCTIME  = 0x00001000;
1480    alias attrgroup_t = uint;
1481    static struct attrlist
1482    {
1483        ushort bitmapcount, reserved;
1484        attrgroup_t commonattr, volattr, dirattr, fileattr, forkattr;
1485    }
1486    extern(C) int setattrlist(in char* path, scope ref attrlist attrs,
1487        scope void* attrbuf, size_t attrBufSize, c_ulong options) nothrow @nogc @system;
1488}
1489
1490/++
1491    Set access/modified times of file or folder `name`.
1492
1493    Params:
1494        name             = File/Folder _name to get times for.
1495        accessTime       = Time the file/folder was last accessed.
1496        modificationTime = Time the file/folder was last modified.
1497
1498    Throws:
1499        $(LREF FileException) on error.
1500 +/
1501void setTimes(R)(R name,
1502              SysTime accessTime,
1503              SysTime modificationTime)
1504if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1505{
1506    auto namez = name.tempCString!FSChar();
1507    static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
1508        alias names = name;
1509    else
1510        string names = null;
1511    setTimesImpl(names, namez, accessTime, modificationTime);
1512}
1513
1514///
1515@safe unittest
1516{
1517    import std.datetime : DateTime, hnsecs, SysTime;
1518
1519    scope(exit) deleteme.remove;
1520    write(deleteme, "a");
1521
1522    SysTime accessTime = SysTime(DateTime(2010, 10, 4, 0, 0, 30));
1523    SysTime modificationTime = SysTime(DateTime(2018, 10, 4, 0, 0, 30));
1524    setTimes(deleteme, accessTime, modificationTime);
1525
1526    SysTime accessTimeResolved, modificationTimeResolved;
1527    getTimes(deleteme, accessTimeResolved, modificationTimeResolved);
1528
1529    assert(accessTime == accessTimeResolved);
1530    assert(modificationTime == modificationTimeResolved);
1531}
1532
1533/// ditto
1534void setTimes(R)(auto ref R name,
1535              SysTime accessTime,
1536              SysTime modificationTime)
1537if (isConvertibleToString!R)
1538{
1539    setTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
1540}
1541
1542private void setTimesImpl(scope const(char)[] names, scope const(FSChar)* namez,
1543    SysTime accessTime, SysTime modificationTime) @trusted
1544{
1545    version (Windows)
1546    {
1547        import std.datetime.systime : SysTimeToFILETIME;
1548        const ta = SysTimeToFILETIME(accessTime);
1549        const tm = SysTimeToFILETIME(modificationTime);
1550        alias defaults =
1551            AliasSeq!(GENERIC_WRITE,
1552                      0,
1553                      null,
1554                      OPEN_EXISTING,
1555                      FILE_ATTRIBUTE_NORMAL |
1556                      FILE_ATTRIBUTE_DIRECTORY |
1557                      FILE_FLAG_BACKUP_SEMANTICS,
1558                      HANDLE.init);
1559        auto h = CreateFileW(namez, defaults);
1560
1561        cenforce(h != INVALID_HANDLE_VALUE, names, namez);
1562
1563        scope(exit)
1564            cenforce(CloseHandle(h), names, namez);
1565
1566        cenforce(SetFileTime(h, null, &ta, &tm), names, namez);
1567    }
1568    else
1569    {
1570        static if (is(typeof(&utimensat)))
1571        {
1572            timespec[2] t = void;
1573            t[0] = accessTime.toTimeSpec();
1574            t[1] = modificationTime.toTimeSpec();
1575            cenforce(utimensat(AT_FDCWD, namez, t, 0) == 0, names, namez);
1576        }
1577        else
1578        {
1579            version (Darwin)
1580            {
1581                // Set modification & access times with setattrlist to avoid precision loss.
1582                attrlist attrs = { bitmapcount: 5, reserved: 0,
1583                        commonattr: ATTR_CMN_MODTIME | ATTR_CMN_ACCTIME,
1584                        volattr: 0, dirattr: 0, fileattr: 0, forkattr: 0 };
1585                timespec[2] attrbuf = [modificationTime.toTimeSpec(), accessTime.toTimeSpec()];
1586                if (0 == setattrlist(namez, attrs, &attrbuf, attrbuf.sizeof, 0))
1587                    return;
1588                if (.errno != ENOTSUP)
1589                    cenforce(false, names, namez);
1590                // Not all volumes support setattrlist. In such cases
1591                // fall through to the utimes implementation.
1592            }
1593            timeval[2] t = void;
1594            t[0] = accessTime.toTimeVal();
1595            t[1] = modificationTime.toTimeVal();
1596            cenforce(utimes(namez, t) == 0, names, namez);
1597        }
1598    }
1599}
1600
1601@safe unittest
1602{
1603    if (false) // Test instatiation
1604        setTimes(TestAliasedString("foo"), SysTime.init, SysTime.init);
1605}
1606
1607@safe unittest
1608{
1609    import std.stdio : File;
1610    string newdir = deleteme ~ r".dir";
1611    string dir = newdir ~ r"/a/b/c";
1612    string file = dir ~ "/file";
1613
1614    if (!exists(dir)) mkdirRecurse(dir);
1615    { auto f = File(file, "w"); }
1616
1617    void testTimes(int hnsecValue)
1618    {
1619        foreach (path; [file, dir])  // test file and dir
1620        {
1621            SysTime atime = SysTime(DateTime(2010, 10, 4, 0, 0, 30), hnsecs(hnsecValue));
1622            SysTime mtime = SysTime(DateTime(2011, 10, 4, 0, 0, 30), hnsecs(hnsecValue));
1623            setTimes(path, atime, mtime);
1624
1625            SysTime atime_res;
1626            SysTime mtime_res;
1627            getTimes(path, atime_res, mtime_res);
1628            assert(atime == atime_res);
1629            assert(mtime == mtime_res);
1630        }
1631    }
1632
1633    testTimes(0);
1634    version (linux)
1635        testTimes(123_456_7);
1636
1637    rmdirRecurse(newdir);
1638}
1639
1640/++
1641    Returns the time that the given file was last modified.
1642
1643    Params:
1644        name = the name of the file to check
1645    Returns:
1646        A $(REF SysTime,std,datetime,systime).
1647    Throws:
1648        $(LREF FileException) if the given file does not exist.
1649+/
1650SysTime timeLastModified(R)(R name)
1651if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1652{
1653    version (Windows)
1654    {
1655        SysTime dummy;
1656        SysTime ftm;
1657
1658        getTimesWin(name, dummy, dummy, ftm);
1659
1660        return ftm;
1661    }
1662    else version (Posix)
1663    {
1664        auto namez = name.tempCString!FSChar();
1665        stat_t statbuf = void;
1666
1667        static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
1668            alias names = name;
1669        else
1670            string names = null;
1671        cenforce(trustedStat(namez, statbuf) == 0, names, namez);
1672
1673        return statTimeToStdTime!'m'(statbuf);
1674    }
1675}
1676
1677/// ditto
1678SysTime timeLastModified(R)(auto ref R name)
1679if (isConvertibleToString!R)
1680{
1681    return timeLastModified!(StringTypeOf!R)(name);
1682}
1683
1684///
1685@safe unittest
1686{
1687    import std.datetime : abs, DateTime, hnsecs, SysTime;
1688    scope(exit) deleteme.remove;
1689
1690    import std.datetime : Clock, seconds;
1691    auto currTime = Clock.currTime();
1692    enum leeway = 5.seconds;
1693    deleteme.write("bb");
1694    assert(abs(deleteme.timeLastModified - currTime) <= leeway);
1695}
1696
1697@safe unittest
1698{
1699    static assert(__traits(compiles, timeLastModified(TestAliasedString("foo"))));
1700}
1701
1702/++
1703    Returns the time that the given file was last modified. If the
1704    file does not exist, returns `returnIfMissing`.
1705
1706    A frequent usage pattern occurs in build automation tools such as
1707    $(HTTP gnu.org/software/make, make) or $(HTTP
1708    en.wikipedia.org/wiki/Apache_Ant, ant). To check whether file $(D
1709    target) must be rebuilt from file `source` (i.e., `target` is
1710    older than `source` or does not exist), use the comparison
1711    below. The code throws a $(LREF FileException) if `source` does not
1712    exist (as it should). On the other hand, the `SysTime.min` default
1713    makes a non-existing `target` seem infinitely old so the test
1714    correctly prompts building it.
1715
1716    Params:
1717        name = The name of the file to get the modification time for.
1718        returnIfMissing = The time to return if the given file does not exist.
1719    Returns:
1720        A $(REF SysTime,std,datetime,systime).
1721
1722Example:
1723--------------------
1724if (source.timeLastModified >= target.timeLastModified(SysTime.min))
1725{
1726    // must (re)build
1727}
1728else
1729{
1730    // target is up-to-date
1731}
1732--------------------
1733+/
1734SysTime timeLastModified(R)(R name, SysTime returnIfMissing)
1735if (isSomeFiniteCharInputRange!R)
1736{
1737    version (Windows)
1738    {
1739        if (!exists(name))
1740            return returnIfMissing;
1741
1742        SysTime dummy;
1743        SysTime ftm;
1744
1745        getTimesWin(name, dummy, dummy, ftm);
1746
1747        return ftm;
1748    }
1749    else version (Posix)
1750    {
1751        auto namez = name.tempCString!FSChar();
1752        stat_t statbuf = void;
1753
1754        return trustedStat(namez, statbuf) != 0 ?
1755               returnIfMissing :
1756               statTimeToStdTime!'m'(statbuf);
1757    }
1758}
1759
1760///
1761@safe unittest
1762{
1763    import std.datetime : SysTime;
1764
1765    assert("file.does.not.exist".timeLastModified(SysTime.min) == SysTime.min);
1766
1767    auto source = deleteme ~ "source";
1768    auto target = deleteme ~ "target";
1769    scope(exit) source.remove, target.remove;
1770
1771    source.write(".");
1772    assert(target.timeLastModified(SysTime.min) < source.timeLastModified);
1773    target.write(".");
1774    assert(target.timeLastModified(SysTime.min) >= source.timeLastModified);
1775}
1776
1777version (StdDdoc)
1778{
1779    /++
1780     $(BLUE This function is POSIX-Only.)
1781
1782     Returns the time that the given file was last modified.
1783     Params:
1784        statbuf = stat_t retrieved from file.
1785     +/
1786    SysTime timeLastModified()(auto ref stat_t statbuf) pure nothrow {assert(false);}
1787    /++
1788     $(BLUE This function is POSIX-Only.)
1789
1790     Returns the time that the given file was last accessed.
1791     Params:
1792        statbuf = stat_t retrieved from file.
1793     +/
1794    SysTime timeLastAccessed()(auto ref stat_t statbuf) pure nothrow {assert(false);}
1795    /++
1796     $(BLUE This function is POSIX-Only.)
1797
1798     Returns the time that the given file was last changed.
1799     Params:
1800        statbuf = stat_t retrieved from file.
1801     +/
1802    SysTime timeStatusChanged()(auto ref stat_t statbuf) pure nothrow {assert(false);}
1803}
1804else version (Posix)
1805{
1806    SysTime timeLastModified()(auto ref stat_t statbuf) pure nothrow
1807    {
1808        return statTimeToStdTime!'m'(statbuf);
1809    }
1810    SysTime timeLastAccessed()(auto ref stat_t statbuf) pure nothrow
1811    {
1812        return statTimeToStdTime!'a'(statbuf);
1813    }
1814    SysTime timeStatusChanged()(auto ref stat_t statbuf) pure nothrow
1815    {
1816        return statTimeToStdTime!'c'(statbuf);
1817    }
1818
1819    @safe unittest
1820    {
1821        stat_t statbuf;
1822        // check that both lvalues and rvalues work
1823        timeLastAccessed(statbuf);
1824        cast(void) timeLastAccessed(stat_t.init);
1825    }
1826}
1827
1828@safe unittest
1829{
1830    //std.process.executeShell("echo a > deleteme");
1831    if (exists(deleteme))
1832        remove(deleteme);
1833
1834    write(deleteme, "a\n");
1835
1836    scope(exit)
1837    {
1838        assert(exists(deleteme));
1839        remove(deleteme);
1840    }
1841
1842    // assert(lastModified("deleteme") >
1843    //         lastModified("this file does not exist", SysTime.min));
1844    //assert(lastModified("deleteme") > lastModified(__FILE__));
1845}
1846
1847
1848// Tests sub-second precision of querying file times.
1849// Should pass on most modern systems running on modern filesystems.
1850// Exceptions:
1851// - FreeBSD, where one would need to first set the
1852//   vfs.timestamp_precision sysctl to a value greater than zero.
1853// - OS X, where the native filesystem (HFS+) stores filesystem
1854//   timestamps with 1-second precision.
1855//
1856// Note: on linux systems, although in theory a change to a file date
1857// can be tracked with precision of 4 msecs, this test waits 20 msecs
1858// to prevent possible problems relative to the CI services the dlang uses,
1859// as they may have the HZ setting that controls the software clock set to 100
1860// (instead of the more common 250).
1861// see https://man7.org/linux/man-pages/man7/time.7.html
1862//     https://stackoverflow.com/a/14393315,
1863//     https://issues.dlang.org/show_bug.cgi?id=21148
1864version (FreeBSD) {} else
1865version (DragonFlyBSD) {} else
1866version (OSX) {} else
1867@safe unittest
1868{
1869    import core.thread;
1870
1871    if (exists(deleteme))
1872        remove(deleteme);
1873
1874    SysTime lastTime;
1875    foreach (n; 0 .. 3)
1876    {
1877        write(deleteme, "a");
1878        auto time = timeLastModified(deleteme);
1879        remove(deleteme);
1880        assert(time != lastTime);
1881        lastTime = time;
1882        () @trusted { Thread.sleep(20.msecs); }();
1883    }
1884}
1885
1886
1887/**
1888 * Determine whether the given file (or directory) _exists.
1889 * Params:
1890 *    name = string or range of characters representing the file _name
1891 * Returns:
1892 *    true if the file _name specified as input _exists
1893 */
1894bool exists(R)(R name)
1895if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1896{
1897    return existsImpl(name.tempCString!FSChar());
1898}
1899
1900/// ditto
1901bool exists(R)(auto ref R name)
1902if (isConvertibleToString!R)
1903{
1904    return exists!(StringTypeOf!R)(name);
1905}
1906
1907///
1908@safe unittest
1909{
1910    auto f = deleteme ~ "does.not.exist";
1911    assert(!f.exists);
1912
1913    f.write("hello");
1914    assert(f.exists);
1915
1916    f.remove;
1917    assert(!f.exists);
1918}
1919
1920private bool existsImpl(scope const(FSChar)* namez) @trusted nothrow @nogc
1921{
1922    version (Windows)
1923    {
1924        // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
1925        // fileio/base/getfileattributes.asp
1926        return GetFileAttributesW(namez) != 0xFFFFFFFF;
1927    }
1928    else version (Posix)
1929    {
1930        /*
1931            The reason why we use stat (and not access) here is
1932            the quirky behavior of access for SUID programs: if
1933            we used access, a file may not appear to "exist",
1934            despite that the program would be able to open it
1935            just fine. The behavior in question is described as
1936            follows in the access man page:
1937
1938            > The check is done using the calling process's real
1939            > UID and GID, rather than the effective IDs as is
1940            > done when actually attempting an operation (e.g.,
1941            > open(2)) on the file. This allows set-user-ID
1942            > programs to easily determine the invoking user's
1943            > authority.
1944
1945            While various operating systems provide eaccess or
1946            euidaccess functions, these are not part of POSIX -
1947            so it's safer to use stat instead.
1948        */
1949
1950        stat_t statbuf = void;
1951        return lstat(namez, &statbuf) == 0;
1952    }
1953    else
1954        static assert(0);
1955}
1956
1957///
1958@safe unittest
1959{
1960    assert(".".exists);
1961    assert(!"this file does not exist".exists);
1962    deleteme.write("a\n");
1963    scope(exit) deleteme.remove;
1964    assert(deleteme.exists);
1965}
1966
1967// https://issues.dlang.org/show_bug.cgi?id=16573
1968@safe unittest
1969{
1970    enum S : string { foo = "foo" }
1971    assert(__traits(compiles, S.foo.exists));
1972}
1973
1974/++
1975 Returns the attributes of the given file.
1976
1977 Note that the file attributes on Windows and POSIX systems are
1978 completely different. On Windows, they're what is returned by
1979 $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx,
1980 GetFileAttributes), whereas on POSIX systems, they're the
1981 `st_mode` value which is part of the $(D stat struct) gotten by
1982 calling the $(HTTP en.wikipedia.org/wiki/Stat_%28Unix%29, `stat`)
1983 function.
1984
1985 On POSIX systems, if the given file is a symbolic link, then
1986 attributes are the attributes of the file pointed to by the symbolic
1987 link.
1988
1989 Params:
1990    name = The file to get the attributes of.
1991 Returns:
1992    The attributes of the file as a `uint`.
1993 Throws: $(LREF FileException) on error.
1994  +/
1995uint getAttributes(R)(R name)
1996if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1997{
1998    version (Windows)
1999    {
2000        auto namez = name.tempCString!FSChar();
2001        static auto trustedGetFileAttributesW(scope const(FSChar)* namez) @trusted
2002        {
2003            return GetFileAttributesW(namez);
2004        }
2005        immutable result = trustedGetFileAttributesW(namez);
2006
2007        static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
2008            alias names = name;
2009        else
2010            string names = null;
2011        cenforce(result != INVALID_FILE_ATTRIBUTES, names, namez);
2012
2013        return result;
2014    }
2015    else version (Posix)
2016    {
2017        auto namez = name.tempCString!FSChar();
2018        stat_t statbuf = void;
2019
2020        static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
2021            alias names = name;
2022        else
2023            string names = null;
2024        cenforce(trustedStat(namez, statbuf) == 0, names, namez);
2025
2026        return statbuf.st_mode;
2027    }
2028}
2029
2030/// ditto
2031uint getAttributes(R)(auto ref R name)
2032if (isConvertibleToString!R)
2033{
2034    return getAttributes!(StringTypeOf!R)(name);
2035}
2036
2037/// getAttributes with a file
2038@safe unittest
2039{
2040    import std.exception : assertThrown;
2041
2042    auto f = deleteme ~ "file";
2043    scope(exit) f.remove;
2044
2045    assert(!f.exists);
2046    assertThrown!FileException(f.getAttributes);
2047
2048    f.write(".");
2049    auto attributes = f.getAttributes;
2050    assert(!attributes.attrIsDir);
2051    assert(attributes.attrIsFile);
2052}
2053
2054/// getAttributes with a directory
2055@safe unittest
2056{
2057    import std.exception : assertThrown;
2058
2059    auto dir = deleteme ~ "dir";
2060    scope(exit) dir.rmdir;
2061
2062    assert(!dir.exists);
2063    assertThrown!FileException(dir.getAttributes);
2064
2065    dir.mkdir;
2066    auto attributes = dir.getAttributes;
2067    assert(attributes.attrIsDir);
2068    assert(!attributes.attrIsFile);
2069}
2070
2071@safe unittest
2072{
2073    static assert(__traits(compiles, getAttributes(TestAliasedString(null))));
2074}
2075
2076/++
2077    If the given file is a symbolic link, then this returns the attributes of the
2078    symbolic link itself rather than file that it points to. If the given file
2079    is $(I not) a symbolic link, then this function returns the same result
2080    as getAttributes.
2081
2082    On Windows, getLinkAttributes is identical to getAttributes. It exists on
2083    Windows so that you don't have to special-case code for Windows when dealing
2084    with symbolic links.
2085
2086    Params:
2087        name = The file to get the symbolic link attributes of.
2088
2089    Returns:
2090        the attributes
2091
2092    Throws:
2093        $(LREF FileException) on error.
2094 +/
2095uint getLinkAttributes(R)(R name)
2096if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
2097{
2098    version (Windows)
2099    {
2100        return getAttributes(name);
2101    }
2102    else version (Posix)
2103    {
2104        auto namez = name.tempCString!FSChar();
2105        static auto trustedLstat(const(FSChar)* namez, ref stat_t buf) @trusted
2106        {
2107            return lstat(namez, &buf);
2108        }
2109        stat_t lstatbuf = void;
2110        static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
2111            alias names = name;
2112        else
2113            string names = null;
2114        cenforce(trustedLstat(namez, lstatbuf) == 0, names, namez);
2115        return lstatbuf.st_mode;
2116    }
2117}
2118
2119/// ditto
2120uint getLinkAttributes(R)(auto ref R name)
2121if (isConvertibleToString!R)
2122{
2123    return getLinkAttributes!(StringTypeOf!R)(name);
2124}
2125
2126///
2127@safe unittest
2128{
2129    import std.exception : assertThrown;
2130
2131    auto source = deleteme ~ "source";
2132    auto target = deleteme ~ "target";
2133
2134    assert(!source.exists);
2135    assertThrown!FileException(source.getLinkAttributes);
2136
2137    // symlinking isn't available on Windows
2138    version (Posix)
2139    {
2140        scope(exit) source.remove, target.remove;
2141
2142        target.write("target");
2143        target.symlink(source);
2144        assert(source.readText == "target");
2145        assert(source.isSymlink);
2146        assert(source.getLinkAttributes.attrIsSymlink);
2147    }
2148}
2149
2150/// if the file is no symlink, getLinkAttributes behaves like getAttributes
2151@safe unittest
2152{
2153    import std.exception : assertThrown;
2154
2155    auto f = deleteme ~ "file";
2156    scope(exit) f.remove;
2157
2158    assert(!f.exists);
2159    assertThrown!FileException(f.getLinkAttributes);
2160
2161    f.write(".");
2162    auto attributes = f.getLinkAttributes;
2163    assert(!attributes.attrIsDir);
2164    assert(attributes.attrIsFile);
2165}
2166
2167/// if the file is no symlink, getLinkAttributes behaves like getAttributes
2168@safe unittest
2169{
2170    import std.exception : assertThrown;
2171
2172    auto dir = deleteme ~ "dir";
2173    scope(exit) dir.rmdir;
2174
2175    assert(!dir.exists);
2176    assertThrown!FileException(dir.getLinkAttributes);
2177
2178    dir.mkdir;
2179    auto attributes = dir.getLinkAttributes;
2180    assert(attributes.attrIsDir);
2181    assert(!attributes.attrIsFile);
2182}
2183
2184@safe unittest
2185{
2186    static assert(__traits(compiles, getLinkAttributes(TestAliasedString(null))));
2187}
2188
2189/++
2190    Set the _attributes of the given file.
2191
2192    For example, a programmatic equivalent of Unix's `chmod +x name`
2193    to make a file executable is
2194    `name.setAttributes(name.getAttributes | octal!700)`.
2195
2196    Params:
2197        name = the file _name
2198        attributes = the _attributes to set the file to
2199
2200    Throws:
2201        $(LREF FileException) if the given file does not exist.
2202 +/
2203void setAttributes(R)(R name, uint attributes)
2204if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
2205{
2206    version (Windows)
2207    {
2208        auto namez = name.tempCString!FSChar();
2209        static auto trustedSetFileAttributesW(scope const(FSChar)* namez, uint dwFileAttributes) @trusted
2210        {
2211            return SetFileAttributesW(namez, dwFileAttributes);
2212        }
2213        static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
2214            alias names = name;
2215        else
2216            string names = null;
2217        cenforce(trustedSetFileAttributesW(namez, attributes), names, namez);
2218    }
2219    else version (Posix)
2220    {
2221        auto namez = name.tempCString!FSChar();
2222        static auto trustedChmod(scope const(FSChar)* namez, mode_t mode) @trusted
2223        {
2224            return chmod(namez, mode);
2225        }
2226        assert(attributes <= mode_t.max);
2227        static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
2228            alias names = name;
2229        else
2230            string names = null;
2231        cenforce(!trustedChmod(namez, cast(mode_t) attributes), names, namez);
2232    }
2233}
2234
2235/// ditto
2236void setAttributes(R)(auto ref R name, uint attributes)
2237if (isConvertibleToString!R)
2238{
2239    return setAttributes!(StringTypeOf!R)(name, attributes);
2240}
2241
2242@safe unittest
2243{
2244    static assert(__traits(compiles, setAttributes(TestAliasedString(null), 0)));
2245}
2246
2247/// setAttributes with a file
2248@safe unittest
2249{
2250    import std.exception : assertThrown;
2251    import std.conv : octal;
2252
2253    auto f = deleteme ~ "file";
2254    version (Posix)
2255    {
2256        scope(exit) f.remove;
2257
2258        assert(!f.exists);
2259        assertThrown!FileException(f.setAttributes(octal!777));
2260
2261        f.write(".");
2262        auto attributes = f.getAttributes;
2263        assert(!attributes.attrIsDir);
2264        assert(attributes.attrIsFile);
2265
2266        f.setAttributes(octal!777);
2267        attributes = f.getAttributes;
2268
2269        assert((attributes & 1023) == octal!777);
2270    }
2271}
2272
2273/// setAttributes with a directory
2274@safe unittest
2275{
2276    import std.exception : assertThrown;
2277    import std.conv : octal;
2278
2279    auto dir = deleteme ~ "dir";
2280    version (Posix)
2281    {
2282        scope(exit) dir.rmdir;
2283
2284        assert(!dir.exists);
2285        assertThrown!FileException(dir.setAttributes(octal!777));
2286
2287        dir.mkdir;
2288        auto attributes = dir.getAttributes;
2289        assert(attributes.attrIsDir);
2290        assert(!attributes.attrIsFile);
2291
2292        dir.setAttributes(octal!777);
2293        attributes = dir.getAttributes;
2294
2295        assert((attributes & 1023) == octal!777);
2296    }
2297}
2298
2299/++
2300    Returns whether the given file is a directory.
2301
2302    Params:
2303        name = The path to the file.
2304
2305    Returns:
2306        true if name specifies a directory
2307
2308    Throws:
2309        $(LREF FileException) if the given file does not exist.
2310  +/
2311@property bool isDir(R)(R name)
2312if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
2313{
2314    version (Windows)
2315    {
2316        return (getAttributes(name) & FILE_ATTRIBUTE_DIRECTORY) != 0;
2317    }
2318    else version (Posix)
2319    {
2320        return (getAttributes(name) & S_IFMT) == S_IFDIR;
2321    }
2322}
2323
2324/// ditto
2325@property bool isDir(R)(auto ref R name)
2326if (isConvertibleToString!R)
2327{
2328    return name.isDir!(StringTypeOf!R);
2329}
2330
2331///
2332
2333@safe unittest
2334{
2335    import std.exception : assertThrown;
2336
2337    auto dir = deleteme ~ "dir";
2338    auto f = deleteme ~ "f";
2339    scope(exit) dir.rmdir, f.remove;
2340
2341    assert(!dir.exists);
2342    assertThrown!FileException(dir.isDir);
2343
2344    dir.mkdir;
2345    assert(dir.isDir);
2346
2347    f.write(".");
2348    assert(!f.isDir);
2349}
2350
2351@safe unittest
2352{
2353    static assert(__traits(compiles, TestAliasedString(null).isDir));
2354}
2355
2356@safe unittest
2357{
2358    version (Windows)
2359    {
2360        if ("C:\\Program Files\\".exists)
2361            assert("C:\\Program Files\\".isDir);
2362
2363        if ("C:\\Windows\\system.ini".exists)
2364            assert(!"C:\\Windows\\system.ini".isDir);
2365    }
2366    else version (Posix)
2367    {
2368        if (system_directory.exists)
2369            assert(system_directory.isDir);
2370
2371        if (system_file.exists)
2372            assert(!system_file.isDir);
2373    }
2374}
2375
2376@safe unittest
2377{
2378    version (Windows)
2379        enum dir = "C:\\Program Files\\";
2380    else version (Posix)
2381        enum dir = system_directory;
2382
2383    if (dir.exists)
2384    {
2385        DirEntry de = DirEntry(dir);
2386        assert(de.isDir);
2387        assert(DirEntry(dir).isDir);
2388    }
2389}
2390
2391/++
2392    Returns whether the given file _attributes are for a directory.
2393
2394    Params:
2395        attributes = The file _attributes.
2396
2397    Returns:
2398        true if attributes specifies a directory
2399+/
2400bool attrIsDir(uint attributes) @safe pure nothrow @nogc
2401{
2402    version (Windows)
2403    {
2404        return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
2405    }
2406    else version (Posix)
2407    {
2408        return (attributes & S_IFMT) == S_IFDIR;
2409    }
2410}
2411
2412///
2413@safe unittest
2414{
2415    import std.exception : assertThrown;
2416
2417    auto dir = deleteme ~ "dir";
2418    auto f = deleteme ~ "f";
2419    scope(exit) dir.rmdir, f.remove;
2420
2421    assert(!dir.exists);
2422    assertThrown!FileException(dir.getAttributes.attrIsDir);
2423
2424    dir.mkdir;
2425    assert(dir.isDir);
2426    assert(dir.getAttributes.attrIsDir);
2427
2428    f.write(".");
2429    assert(!f.isDir);
2430    assert(!f.getAttributes.attrIsDir);
2431}
2432
2433@safe unittest
2434{
2435    version (Windows)
2436    {
2437        if ("C:\\Program Files\\".exists)
2438        {
2439            assert(attrIsDir(getAttributes("C:\\Program Files\\")));
2440            assert(attrIsDir(getLinkAttributes("C:\\Program Files\\")));
2441        }
2442
2443        if ("C:\\Windows\\system.ini".exists)
2444        {
2445            assert(!attrIsDir(getAttributes("C:\\Windows\\system.ini")));
2446            assert(!attrIsDir(getLinkAttributes("C:\\Windows\\system.ini")));
2447        }
2448    }
2449    else version (Posix)
2450    {
2451        if (system_directory.exists)
2452        {
2453            assert(attrIsDir(getAttributes(system_directory)));
2454            assert(attrIsDir(getLinkAttributes(system_directory)));
2455        }
2456
2457        if (system_file.exists)
2458        {
2459            assert(!attrIsDir(getAttributes(system_file)));
2460            assert(!attrIsDir(getLinkAttributes(system_file)));
2461        }
2462    }
2463}
2464
2465
2466/++
2467    Returns whether the given file (or directory) is a file.
2468
2469    On Windows, if a file is not a directory, then it's a file. So,
2470    either `isFile` or `isDir` will return true for any given file.
2471
2472    On POSIX systems, if `isFile` is `true`, that indicates that the file
2473    is a regular file (e.g. not a block not device). So, on POSIX systems, it's
2474    possible for both `isFile` and `isDir` to be `false` for a
2475    particular file (in which case, it's a special file). You can use
2476    `getAttributes` to get the attributes to figure out what type of special
2477    it is, or you can use `DirEntry` to get at its `statBuf`, which is the
2478    result from `stat`. In either case, see the man page for `stat` for
2479    more information.
2480
2481    Params:
2482        name = The path to the file.
2483
2484    Returns:
2485        true if name specifies a file
2486
2487    Throws:
2488        $(LREF FileException) if the given file does not exist.
2489+/
2490@property bool isFile(R)(R name)
2491if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
2492{
2493    version (Windows)
2494        return !name.isDir;
2495    else version (Posix)
2496        return (getAttributes(name) & S_IFMT) == S_IFREG;
2497}
2498
2499/// ditto
2500@property bool isFile(R)(auto ref R name)
2501if (isConvertibleToString!R)
2502{
2503    return isFile!(StringTypeOf!R)(name);
2504}
2505
2506///
2507@safe unittest
2508{
2509    import std.exception : assertThrown;
2510
2511    auto dir = deleteme ~ "dir";
2512    auto f = deleteme ~ "f";
2513    scope(exit) dir.rmdir, f.remove;
2514
2515    dir.mkdir;
2516    assert(!dir.isFile);
2517
2518    assert(!f.exists);
2519    assertThrown!FileException(f.isFile);
2520
2521    f.write(".");
2522    assert(f.isFile);
2523}
2524
2525// https://issues.dlang.org/show_bug.cgi?id=15658
2526@safe unittest
2527{
2528    DirEntry e = DirEntry(".");
2529    static assert(is(typeof(isFile(e))));
2530}
2531
2532@safe unittest
2533{
2534    static assert(__traits(compiles, TestAliasedString(null).isFile));
2535}
2536
2537@safe unittest
2538{
2539    version (Windows)
2540    {
2541        if ("C:\\Program Files\\".exists)
2542            assert(!"C:\\Program Files\\".isFile);
2543
2544        if ("C:\\Windows\\system.ini".exists)
2545            assert("C:\\Windows\\system.ini".isFile);
2546    }
2547    else version (Posix)
2548    {
2549        if (system_directory.exists)
2550            assert(!system_directory.isFile);
2551
2552        if (system_file.exists)
2553            assert(system_file.isFile);
2554    }
2555}
2556
2557
2558/++
2559    Returns whether the given file _attributes are for a file.
2560
2561    On Windows, if a file is not a directory, it's a file. So, either
2562    `attrIsFile` or `attrIsDir` will return `true` for the
2563    _attributes of any given file.
2564
2565    On POSIX systems, if `attrIsFile` is `true`, that indicates that the
2566    file is a regular file (e.g. not a block not device). So, on POSIX systems,
2567    it's possible for both `attrIsFile` and `attrIsDir` to be `false`
2568    for a particular file (in which case, it's a special file). If a file is a
2569    special file, you can use the _attributes to check what type of special file
2570    it is (see the man page for `stat` for more information).
2571
2572    Params:
2573        attributes = The file _attributes.
2574
2575    Returns:
2576        true if the given file _attributes are for a file
2577
2578Example:
2579--------------------
2580assert(attrIsFile(getAttributes("/etc/fonts/fonts.conf")));
2581assert(attrIsFile(getLinkAttributes("/etc/fonts/fonts.conf")));
2582--------------------
2583  +/
2584bool attrIsFile(uint attributes) @safe pure nothrow @nogc
2585{
2586    version (Windows)
2587    {
2588        return (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
2589    }
2590    else version (Posix)
2591    {
2592        return (attributes & S_IFMT) == S_IFREG;
2593    }
2594}
2595
2596///
2597@safe unittest
2598{
2599    import std.exception : assertThrown;
2600
2601    auto dir = deleteme ~ "dir";
2602    auto f = deleteme ~ "f";
2603    scope(exit) dir.rmdir, f.remove;
2604
2605    dir.mkdir;
2606    assert(!dir.isFile);
2607    assert(!dir.getAttributes.attrIsFile);
2608
2609    assert(!f.exists);
2610    assertThrown!FileException(f.getAttributes.attrIsFile);
2611
2612    f.write(".");
2613    assert(f.isFile);
2614    assert(f.getAttributes.attrIsFile);
2615}
2616
2617@safe unittest
2618{
2619    version (Windows)
2620    {
2621        if ("C:\\Program Files\\".exists)
2622        {
2623            assert(!attrIsFile(getAttributes("C:\\Program Files\\")));
2624            assert(!attrIsFile(getLinkAttributes("C:\\Program Files\\")));
2625        }
2626
2627        if ("C:\\Windows\\system.ini".exists)
2628        {
2629            assert(attrIsFile(getAttributes("C:\\Windows\\system.ini")));
2630            assert(attrIsFile(getLinkAttributes("C:\\Windows\\system.ini")));
2631        }
2632    }
2633    else version (Posix)
2634    {
2635        if (system_directory.exists)
2636        {
2637            assert(!attrIsFile(getAttributes(system_directory)));
2638            assert(!attrIsFile(getLinkAttributes(system_directory)));
2639        }
2640
2641        if (system_file.exists)
2642        {
2643            assert(attrIsFile(getAttributes(system_file)));
2644            assert(attrIsFile(getLinkAttributes(system_file)));
2645        }
2646    }
2647}
2648
2649
2650/++
2651    Returns whether the given file is a symbolic link.
2652
2653    On Windows, returns `true` when the file is either a symbolic link or a
2654    junction point.
2655
2656    Params:
2657        name = The path to the file.
2658
2659    Returns:
2660        true if name is a symbolic link
2661
2662    Throws:
2663        $(LREF FileException) if the given file does not exist.
2664  +/
2665@property bool isSymlink(R)(R name)
2666if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
2667{
2668    version (Windows)
2669        return (getAttributes(name) & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
2670    else version (Posix)
2671        return (getLinkAttributes(name) & S_IFMT) == S_IFLNK;
2672}
2673
2674/// ditto
2675@property bool isSymlink(R)(auto ref R name)
2676if (isConvertibleToString!R)
2677{
2678    return name.isSymlink!(StringTypeOf!R);
2679}
2680
2681@safe unittest
2682{
2683    static assert(__traits(compiles, TestAliasedString(null).isSymlink));
2684}
2685
2686///
2687@safe unittest
2688{
2689    import std.exception : assertThrown;
2690
2691    auto source = deleteme ~ "source";
2692    auto target = deleteme ~ "target";
2693
2694    assert(!source.exists);
2695    assertThrown!FileException(source.isSymlink);
2696
2697    // symlinking isn't available on Windows
2698    version (Posix)
2699    {
2700        scope(exit) source.remove, target.remove;
2701
2702        target.write("target");
2703        target.symlink(source);
2704        assert(source.readText == "target");
2705        assert(source.isSymlink);
2706        assert(source.getLinkAttributes.attrIsSymlink);
2707    }
2708}
2709
2710@system unittest
2711{
2712    version (Windows)
2713    {
2714        if ("C:\\Program Files\\".exists)
2715            assert(!"C:\\Program Files\\".isSymlink);
2716
2717        if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
2718            assert("C:\\Documents and Settings\\".isSymlink);
2719
2720        enum fakeSymFile = "C:\\Windows\\system.ini";
2721        if (fakeSymFile.exists)
2722        {
2723            assert(!fakeSymFile.isSymlink);
2724
2725            assert(!fakeSymFile.isSymlink);
2726            assert(!attrIsSymlink(getAttributes(fakeSymFile)));
2727            assert(!attrIsSymlink(getLinkAttributes(fakeSymFile)));
2728
2729            assert(attrIsFile(getAttributes(fakeSymFile)));
2730            assert(attrIsFile(getLinkAttributes(fakeSymFile)));
2731            assert(!attrIsDir(getAttributes(fakeSymFile)));
2732            assert(!attrIsDir(getLinkAttributes(fakeSymFile)));
2733
2734            assert(getAttributes(fakeSymFile) == getLinkAttributes(fakeSymFile));
2735        }
2736    }
2737    else version (Posix)
2738    {
2739        if (system_directory.exists)
2740        {
2741            assert(!system_directory.isSymlink);
2742
2743            immutable symfile = deleteme ~ "_slink\0";
2744            scope(exit) if (symfile.exists) symfile.remove();
2745
2746            core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
2747
2748            assert(symfile.isSymlink);
2749            assert(!attrIsSymlink(getAttributes(symfile)));
2750            assert(attrIsSymlink(getLinkAttributes(symfile)));
2751
2752            assert(attrIsDir(getAttributes(symfile)));
2753            assert(!attrIsDir(getLinkAttributes(symfile)));
2754
2755            assert(!attrIsFile(getAttributes(symfile)));
2756            assert(!attrIsFile(getLinkAttributes(symfile)));
2757        }
2758
2759        if (system_file.exists)
2760        {
2761            assert(!system_file.isSymlink);
2762
2763            immutable symfile = deleteme ~ "_slink\0";
2764            scope(exit) if (symfile.exists) symfile.remove();
2765
2766            core.sys.posix.unistd.symlink(system_file, symfile.ptr);
2767
2768            assert(symfile.isSymlink);
2769            assert(!attrIsSymlink(getAttributes(symfile)));
2770            assert(attrIsSymlink(getLinkAttributes(symfile)));
2771
2772            assert(!attrIsDir(getAttributes(symfile)));
2773            assert(!attrIsDir(getLinkAttributes(symfile)));
2774
2775            assert(attrIsFile(getAttributes(symfile)));
2776            assert(!attrIsFile(getLinkAttributes(symfile)));
2777        }
2778    }
2779
2780    static assert(__traits(compiles, () @safe { return "dummy".isSymlink; }));
2781}
2782
2783
2784/++
2785    Returns whether the given file attributes are for a symbolic link.
2786
2787    On Windows, return `true` when the file is either a symbolic link or a
2788    junction point.
2789
2790    Params:
2791        attributes = The file attributes.
2792
2793    Returns:
2794        true if attributes are for a symbolic link
2795
2796Example:
2797--------------------
2798core.sys.posix.unistd.symlink("/etc/fonts/fonts.conf", "/tmp/alink");
2799
2800assert(!getAttributes("/tmp/alink").isSymlink);
2801assert(getLinkAttributes("/tmp/alink").isSymlink);
2802--------------------
2803  +/
2804bool attrIsSymlink(uint attributes) @safe pure nothrow @nogc
2805{
2806    version (Windows)
2807        return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
2808    else version (Posix)
2809        return (attributes & S_IFMT) == S_IFLNK;
2810}
2811
2812///
2813@safe unittest
2814{
2815    import std.exception : assertThrown;
2816
2817    auto source = deleteme ~ "source";
2818    auto target = deleteme ~ "target";
2819
2820    assert(!source.exists);
2821    assertThrown!FileException(source.getLinkAttributes.attrIsSymlink);
2822
2823    // symlinking isn't available on Windows
2824    version (Posix)
2825    {
2826        scope(exit) source.remove, target.remove;
2827
2828        target.write("target");
2829        target.symlink(source);
2830        assert(source.readText == "target");
2831        assert(source.isSymlink);
2832        assert(source.getLinkAttributes.attrIsSymlink);
2833    }
2834}
2835
2836/**
2837Change directory to `pathname`. Equivalent to `cd` on
2838Windows and POSIX.
2839
2840Params:
2841    pathname = the directory to step into
2842
2843Throws: $(LREF FileException) on error.
2844 */
2845void chdir(R)(R pathname)
2846if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
2847{
2848    // Place outside of @trusted block
2849    auto pathz = pathname.tempCString!FSChar();
2850
2851    version (Windows)
2852    {
2853        static auto trustedChdir(scope const(FSChar)* pathz) @trusted
2854        {
2855            return SetCurrentDirectoryW(pathz);
2856        }
2857    }
2858    else version (Posix)
2859    {
2860        static auto trustedChdir(scope const(FSChar)* pathz) @trusted
2861        {
2862            return core.sys.posix.unistd.chdir(pathz) == 0;
2863        }
2864    }
2865    static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
2866        alias pathStr = pathname;
2867    else
2868        string pathStr = null;
2869    cenforce(trustedChdir(pathz), pathStr, pathz);
2870}
2871
2872/// ditto
2873void chdir(R)(auto ref R pathname)
2874if (isConvertibleToString!R)
2875{
2876    return chdir!(StringTypeOf!R)(pathname);
2877}
2878
2879///
2880@system unittest
2881{
2882    import std.algorithm.comparison : equal;
2883    import std.algorithm.sorting : sort;
2884    import std.array : array;
2885    import std.path : buildPath;
2886
2887    auto cwd = getcwd;
2888    auto dir = deleteme ~ "dir";
2889    dir.mkdir;
2890    scope(exit) cwd.chdir, dir.rmdirRecurse;
2891
2892    dir.buildPath("a").write(".");
2893    dir.chdir; // step into dir
2894    "b".write(".");
2895    assert(dirEntries(".", SpanMode.shallow).array.sort.equal(
2896        [".".buildPath("a"), ".".buildPath("b")]
2897    ));
2898}
2899
2900@safe unittest
2901{
2902    static assert(__traits(compiles, chdir(TestAliasedString(null))));
2903}
2904
2905/**
2906Make a new directory `pathname`.
2907
2908Params:
2909    pathname = the path of the directory to make
2910
2911Throws:
2912    $(LREF FileException) on POSIX or $(LREF WindowsException) on Windows
2913    if an error occured.
2914 */
2915void mkdir(R)(R pathname)
2916if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
2917{
2918    // Place outside of @trusted block
2919    const pathz = pathname.tempCString!FSChar();
2920
2921    version (Windows)
2922    {
2923        static auto trustedCreateDirectoryW(scope const(FSChar)* pathz) @trusted
2924        {
2925            return CreateDirectoryW(pathz, null);
2926        }
2927        static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
2928            alias pathStr = pathname;
2929        else
2930            string pathStr = null;
2931        wenforce(trustedCreateDirectoryW(pathz), pathStr, pathz);
2932    }
2933    else version (Posix)
2934    {
2935        import std.conv : octal;
2936
2937        static auto trustedMkdir(scope const(FSChar)* pathz, mode_t mode) @trusted
2938        {
2939            return core.sys.posix.sys.stat.mkdir(pathz, mode);
2940        }
2941        static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
2942            alias pathStr = pathname;
2943        else
2944            string pathStr = null;
2945        cenforce(trustedMkdir(pathz, octal!777) == 0, pathStr, pathz);
2946    }
2947}
2948
2949/// ditto
2950void mkdir(R)(auto ref R pathname)
2951if (isConvertibleToString!R)
2952{
2953    return mkdir!(StringTypeOf!R)(pathname);
2954}
2955
2956@safe unittest
2957{
2958    import std.file : mkdir;
2959    static assert(__traits(compiles, mkdir(TestAliasedString(null))));
2960}
2961
2962///
2963@safe unittest
2964{
2965    import std.file : mkdir;
2966
2967    auto dir = deleteme ~ "dir";
2968    scope(exit) dir.rmdir;
2969
2970    dir.mkdir;
2971    assert(dir.exists);
2972}
2973
2974///
2975@safe unittest
2976{
2977    import std.exception : assertThrown;
2978    assertThrown("a/b/c/d/e".mkdir);
2979}
2980
2981// Same as mkdir but ignores "already exists" errors.
2982// Returns: "true" if the directory was created,
2983//   "false" if it already existed.
2984private bool ensureDirExists()(scope const(char)[] pathname)
2985{
2986    import std.exception : enforce;
2987    const pathz = pathname.tempCString!FSChar();
2988
2989    version (Windows)
2990    {
2991        if (() @trusted { return CreateDirectoryW(pathz, null); }())
2992            return true;
2993        cenforce(GetLastError() == ERROR_ALREADY_EXISTS, pathname.idup);
2994    }
2995    else version (Posix)
2996    {
2997        import std.conv : octal;
2998
2999        if (() @trusted { return core.sys.posix.sys.stat.mkdir(pathz, octal!777); }() == 0)
3000            return true;
3001        cenforce(errno == EEXIST || errno == EISDIR, pathname);
3002    }
3003    enforce(pathname.isDir, new FileException(pathname.idup));
3004    return false;
3005}
3006
3007/**
3008Make directory and all parent directories as needed.
3009
3010Does nothing if the directory specified by
3011`pathname` already exists.
3012
3013Params:
3014    pathname = the full path of the directory to create
3015
3016Throws: $(LREF FileException) on error.
3017 */
3018void mkdirRecurse(scope const(char)[] pathname) @safe
3019{
3020    import std.path : dirName, baseName;
3021
3022    const left = dirName(pathname);
3023    if (left.length != pathname.length && !exists(left))
3024    {
3025        mkdirRecurse(left);
3026    }
3027    if (!baseName(pathname).empty)
3028    {
3029        ensureDirExists(pathname);
3030    }
3031}
3032
3033///
3034@safe unittest
3035{
3036    import std.path : buildPath;
3037
3038    auto dir = deleteme ~ "dir";
3039    scope(exit) dir.rmdirRecurse;
3040
3041    dir.mkdir;
3042    assert(dir.exists);
3043    dir.mkdirRecurse; // does nothing
3044
3045    // creates all parent directories as needed
3046    auto nested = dir.buildPath("a", "b", "c");
3047    nested.mkdirRecurse;
3048    assert(nested.exists);
3049}
3050
3051///
3052@safe unittest
3053{
3054    import std.exception : assertThrown;
3055
3056    scope(exit) deleteme.remove;
3057    deleteme.write("a");
3058
3059    // cannot make directory as it's already a file
3060    assertThrown!FileException(deleteme.mkdirRecurse);
3061}
3062
3063@safe unittest
3064{
3065    import std.exception : assertThrown;
3066    {
3067        import std.path : buildPath, buildNormalizedPath;
3068
3069        immutable basepath = deleteme ~ "_dir";
3070        scope(exit) () @trusted { rmdirRecurse(basepath); }();
3071
3072        auto path = buildPath(basepath, "a", "..", "b");
3073        mkdirRecurse(path);
3074        path = path.buildNormalizedPath;
3075        assert(path.isDir);
3076
3077        path = buildPath(basepath, "c");
3078        write(path, "");
3079        assertThrown!FileException(mkdirRecurse(path));
3080
3081        path = buildPath(basepath, "d");
3082        mkdirRecurse(path);
3083        mkdirRecurse(path); // should not throw
3084    }
3085
3086    version (Windows)
3087    {
3088        assertThrown!FileException(mkdirRecurse(`1:\foobar`));
3089    }
3090
3091    // https://issues.dlang.org/show_bug.cgi?id=3570
3092    {
3093        immutable basepath = deleteme ~ "_dir";
3094        version (Windows)
3095        {
3096            immutable path = basepath ~ "\\fake\\here\\";
3097        }
3098        else version (Posix)
3099        {
3100            immutable path = basepath ~ `/fake/here/`;
3101        }
3102
3103        mkdirRecurse(path);
3104        assert(basepath.exists && basepath.isDir);
3105        scope(exit) () @trusted { rmdirRecurse(basepath); }();
3106        assert(path.exists && path.isDir);
3107    }
3108}
3109
3110/****************************************************
3111Remove directory `pathname`.
3112
3113Params:
3114    pathname = Range or string specifying the directory name
3115
3116Throws: $(LREF FileException) on error.
3117 */
3118void rmdir(R)(R pathname)
3119if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
3120{
3121    // Place outside of @trusted block
3122    auto pathz = pathname.tempCString!FSChar();
3123
3124    version (Windows)
3125    {
3126        static auto trustedRmdir(scope const(FSChar)* pathz) @trusted
3127        {
3128            return RemoveDirectoryW(pathz);
3129        }
3130    }
3131    else version (Posix)
3132    {
3133        static auto trustedRmdir(scope const(FSChar)* pathz) @trusted
3134        {
3135            return core.sys.posix.unistd.rmdir(pathz) == 0;
3136        }
3137    }
3138    static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
3139        alias pathStr = pathname;
3140    else
3141        string pathStr = null;
3142    cenforce(trustedRmdir(pathz), pathStr, pathz);
3143}
3144
3145/// ditto
3146void rmdir(R)(auto ref R pathname)
3147if (isConvertibleToString!R)
3148{
3149    rmdir!(StringTypeOf!R)(pathname);
3150}
3151
3152@safe unittest
3153{
3154    static assert(__traits(compiles, rmdir(TestAliasedString(null))));
3155}
3156
3157///
3158@safe unittest
3159{
3160    auto dir = deleteme ~ "dir";
3161
3162    dir.mkdir;
3163    assert(dir.exists);
3164    dir.rmdir;
3165    assert(!dir.exists);
3166}
3167
3168/++
3169    $(BLUE This function is POSIX-Only.)
3170
3171    Creates a symbolic _link (_symlink).
3172
3173    Params:
3174        original = The file that is being linked. This is the target path that's
3175            stored in the _symlink. A relative path is relative to the created
3176            _symlink.
3177        link = The _symlink to create. A relative path is relative to the
3178            current working directory.
3179
3180    Throws:
3181        $(LREF FileException) on error (which includes if the _symlink already
3182        exists).
3183  +/
3184version (StdDdoc) void symlink(RO, RL)(RO original, RL link)
3185if ((isSomeFiniteCharInputRange!RO || isConvertibleToString!RO) &&
3186    (isSomeFiniteCharInputRange!RL || isConvertibleToString!RL));
3187else version (Posix) void symlink(RO, RL)(RO original, RL link)
3188if ((isSomeFiniteCharInputRange!RO || isConvertibleToString!RO) &&
3189    (isSomeFiniteCharInputRange!RL || isConvertibleToString!RL))
3190{
3191    static if (isConvertibleToString!RO || isConvertibleToString!RL)
3192    {
3193        import std.meta : staticMap;
3194        alias Types = staticMap!(convertToString, RO, RL);
3195        symlink!Types(original, link);
3196    }
3197    else
3198    {
3199        import std.conv : text;
3200        auto oz = original.tempCString();
3201        auto lz = link.tempCString();
3202        alias posixSymlink = core.sys.posix.unistd.symlink;
3203        immutable int result = () @trusted { return posixSymlink(oz, lz); } ();
3204        cenforce(result == 0, text(link));
3205    }
3206}
3207
3208version (Posix) @safe unittest
3209{
3210    if (system_directory.exists)
3211    {
3212        immutable symfile = deleteme ~ "_slink\0";
3213        scope(exit) if (symfile.exists) symfile.remove();
3214
3215        symlink(system_directory, symfile);
3216
3217        assert(symfile.exists);
3218        assert(symfile.isSymlink);
3219        assert(!attrIsSymlink(getAttributes(symfile)));
3220        assert(attrIsSymlink(getLinkAttributes(symfile)));
3221
3222        assert(attrIsDir(getAttributes(symfile)));
3223        assert(!attrIsDir(getLinkAttributes(symfile)));
3224
3225        assert(!attrIsFile(getAttributes(symfile)));
3226        assert(!attrIsFile(getLinkAttributes(symfile)));
3227    }
3228
3229    if (system_file.exists)
3230    {
3231        assert(!system_file.isSymlink);
3232
3233        immutable symfile = deleteme ~ "_slink\0";
3234        scope(exit) if (symfile.exists) symfile.remove();
3235
3236        symlink(system_file, symfile);
3237
3238        assert(symfile.exists);
3239        assert(symfile.isSymlink);
3240        assert(!attrIsSymlink(getAttributes(symfile)));
3241        assert(attrIsSymlink(getLinkAttributes(symfile)));
3242
3243        assert(!attrIsDir(getAttributes(symfile)));
3244        assert(!attrIsDir(getLinkAttributes(symfile)));
3245
3246        assert(attrIsFile(getAttributes(symfile)));
3247        assert(!attrIsFile(getLinkAttributes(symfile)));
3248    }
3249}
3250
3251version (Posix) @safe unittest
3252{
3253    static assert(__traits(compiles,
3254        symlink(TestAliasedString(null), TestAliasedString(null))));
3255}
3256
3257
3258/++
3259    $(BLUE This function is POSIX-Only.)
3260
3261    Returns the path to the file pointed to by a symlink. Note that the
3262    path could be either relative or absolute depending on the symlink.
3263    If the path is relative, it's relative to the symlink, not the current
3264    working directory.
3265
3266    Throws:
3267        $(LREF FileException) on error.
3268  +/
3269version (StdDdoc) string readLink(R)(R link)
3270if (isSomeFiniteCharInputRange!R || isConvertibleToString!R);
3271else version (Posix) string readLink(R)(R link)
3272if (isSomeFiniteCharInputRange!R || isConvertibleToString!R)
3273{
3274    static if (isConvertibleToString!R)
3275    {
3276        return readLink!(convertToString!R)(link);
3277    }
3278    else
3279    {
3280        import std.conv : to;
3281        import std.exception : assumeUnique;
3282        alias posixReadlink = core.sys.posix.unistd.readlink;
3283        enum bufferLen = 2048;
3284        enum maxCodeUnits = 6;
3285        char[bufferLen] buffer;
3286        const linkz = link.tempCString();
3287        auto size = () @trusted {
3288            return posixReadlink(linkz, buffer.ptr, buffer.length);
3289        } ();
3290        cenforce(size != -1, to!string(link));
3291
3292        if (size <= bufferLen - maxCodeUnits)
3293            return to!string(buffer[0 .. size]);
3294
3295        auto dynamicBuffer = new char[](bufferLen * 3 / 2);
3296
3297        foreach (i; 0 .. 10)
3298        {
3299            size = () @trusted {
3300                return posixReadlink(linkz, dynamicBuffer.ptr,
3301                    dynamicBuffer.length);
3302            } ();
3303            cenforce(size != -1, to!string(link));
3304
3305            if (size <= dynamicBuffer.length - maxCodeUnits)
3306            {
3307                dynamicBuffer.length = size;
3308                return () @trusted {
3309                    return assumeUnique(dynamicBuffer);
3310                } ();
3311            }
3312
3313            dynamicBuffer.length = dynamicBuffer.length * 3 / 2;
3314        }
3315
3316        throw new FileException(to!string(link), "Path is too long to read.");
3317    }
3318}
3319
3320version (Posix) @safe unittest
3321{
3322    import std.exception : assertThrown;
3323    import std.string;
3324
3325    foreach (file; [system_directory, system_file])
3326    {
3327        if (file.exists)
3328        {
3329            immutable symfile = deleteme ~ "_slink\0";
3330            scope(exit) if (symfile.exists) symfile.remove();
3331
3332            symlink(file, symfile);
3333            assert(readLink(symfile) == file, format("Failed file: %s", file));
3334        }
3335    }
3336
3337    assertThrown!FileException(readLink("/doesnotexist"));
3338}
3339
3340version (Posix) @safe unittest
3341{
3342    static assert(__traits(compiles, readLink(TestAliasedString("foo"))));
3343}
3344
3345version (Posix) @system unittest // input range of dchars
3346{
3347    mkdirRecurse(deleteme);
3348    scope(exit) if (deleteme.exists) rmdirRecurse(deleteme);
3349    write(deleteme ~ "/f", "");
3350    import std.range.interfaces : InputRange, inputRangeObject;
3351    import std.utf : byChar;
3352    immutable string link = deleteme ~ "/l";
3353    symlink("f", link);
3354    InputRange!(ElementType!string) linkr = inputRangeObject(link);
3355    alias R = typeof(linkr);
3356    static assert(isInputRange!R);
3357    static assert(!isForwardRange!R);
3358    assert(readLink(linkr) == "f");
3359}
3360
3361
3362/****************************************************
3363 * Get the current working directory.
3364 * Throws: $(LREF FileException) on error.
3365 */
3366version (Windows) string getcwd() @trusted
3367{
3368    import std.conv : to;
3369    import std.checkedint : checked;
3370    /* GetCurrentDirectory's return value:
3371        1. function succeeds: the number of characters that are written to
3372    the buffer, not including the terminating null character.
3373        2. function fails: zero
3374        3. the buffer (lpBuffer) is not large enough: the required size of
3375    the buffer, in characters, including the null-terminating character.
3376    */
3377    version (StdUnittest)
3378        enum BUF_SIZE = 10;     // trigger reallocation code
3379    else
3380        enum BUF_SIZE = 4096;   // enough for most common case
3381    wchar[BUF_SIZE] buffW = void;
3382    immutable n = cenforce(GetCurrentDirectoryW(to!DWORD(buffW.length), buffW.ptr),
3383            "getcwd");
3384    // we can do it because toUTFX always produces a fresh string
3385    if (n < buffW.length)
3386    {
3387        return buffW[0 .. n].to!string;
3388    }
3389    else //staticBuff isn't enough
3390    {
3391        auto cn = checked(n);
3392        auto ptr = cast(wchar*) malloc((cn * wchar.sizeof).get);
3393        scope(exit) free(ptr);
3394        immutable n2 = GetCurrentDirectoryW(cn.get, ptr);
3395        cenforce(n2 && n2 < cn, "getcwd");
3396        return ptr[0 .. n2].to!string;
3397    }
3398}
3399else version (Solaris) string getcwd() @trusted
3400{
3401    /* BUF_SIZE >= PATH_MAX */
3402    enum BUF_SIZE = 4096;
3403    /* The user should be able to specify any size buffer > 0 */
3404    auto p = cenforce(core.sys.posix.unistd.getcwd(null, BUF_SIZE),
3405            "cannot get cwd");
3406    scope(exit) core.stdc.stdlib.free(p);
3407    return p[0 .. core.stdc.string.strlen(p)].idup;
3408}
3409else version (Posix) string getcwd() @trusted
3410{
3411    auto p = cenforce(core.sys.posix.unistd.getcwd(null, 0),
3412            "cannot get cwd");
3413    scope(exit) core.stdc.stdlib.free(p);
3414    return p[0 .. core.stdc.string.strlen(p)].idup;
3415}
3416
3417///
3418@safe unittest
3419{
3420    auto s = getcwd();
3421    assert(s.length);
3422}
3423
3424/**
3425 * Returns the full path of the current executable.
3426 *
3427 * Returns:
3428 *     The path of the executable as a `string`.
3429 *
3430 * Throws:
3431 * $(REF1 Exception, object)
3432 */
3433@trusted string thisExePath()
3434{
3435    version (Darwin)
3436    {
3437        import core.sys.darwin.mach.dyld : _NSGetExecutablePath;
3438        import core.sys.posix.stdlib : realpath;
3439        import std.conv : to;
3440        import std.exception : errnoEnforce;
3441
3442        uint size;
3443
3444        _NSGetExecutablePath(null, &size); // get the length of the path
3445        auto buffer = new char[size];
3446        _NSGetExecutablePath(buffer.ptr, &size);
3447
3448        auto absolutePath = realpath(buffer.ptr, null); // let the function allocate
3449
3450        scope (exit)
3451        {
3452            if (absolutePath)
3453                free(absolutePath);
3454        }
3455
3456        errnoEnforce(absolutePath);
3457        return to!(string)(absolutePath);
3458    }
3459    else version (linux)
3460    {
3461        return readLink("/proc/self/exe");
3462    }
3463    else version (Windows)
3464    {
3465        import std.conv : to;
3466        import std.exception : enforce;
3467
3468        wchar[MAX_PATH] buf;
3469        wchar[] buffer = buf[];
3470
3471        while (true)
3472        {
3473            auto len = GetModuleFileNameW(null, buffer.ptr, cast(DWORD) buffer.length);
3474            wenforce(len);
3475            if (len != buffer.length)
3476                return to!(string)(buffer[0 .. len]);
3477            buffer.length *= 2;
3478        }
3479    }
3480    else version (DragonFlyBSD)
3481    {
3482        import core.sys.dragonflybsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME;
3483        import std.exception : errnoEnforce, assumeUnique;
3484
3485        int[4] mib = [CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1];
3486        size_t len;
3487
3488        auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); // get the length of the path
3489        errnoEnforce(result == 0);
3490
3491        auto buffer = new char[len - 1];
3492        result = sysctl(mib.ptr, mib.length, buffer.ptr, &len, null, 0);
3493        errnoEnforce(result == 0);
3494
3495        return buffer.assumeUnique;
3496    }
3497    else version (FreeBSD)
3498    {
3499        import core.sys.freebsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME;
3500        import std.exception : errnoEnforce, assumeUnique;
3501
3502        int[4] mib = [CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1];
3503        size_t len;
3504
3505        auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); // get the length of the path
3506        errnoEnforce(result == 0);
3507
3508        auto buffer = new char[len - 1];
3509        result = sysctl(mib.ptr, mib.length, buffer.ptr, &len, null, 0);
3510        errnoEnforce(result == 0);
3511
3512        return buffer.assumeUnique;
3513    }
3514    else version (NetBSD)
3515    {
3516        import core.sys.netbsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC_ARGS, KERN_PROC_PATHNAME;
3517        import std.exception : errnoEnforce, assumeUnique;
3518
3519        int[4] mib = [CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME];
3520        size_t len;
3521
3522        auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); // get the length of the path
3523        errnoEnforce(result == 0);
3524
3525        auto buffer = new char[len - 1];
3526        result = sysctl(mib.ptr, mib.length, buffer.ptr, &len, null, 0);
3527        errnoEnforce(result == 0);
3528
3529        return buffer.assumeUnique;
3530    }
3531    else version (OpenBSD)
3532    {
3533        import core.sys.openbsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC_ARGS, KERN_PROC_ARGV;
3534        import core.sys.posix.unistd : getpid;
3535        import std.conv : to;
3536        import std.exception : enforce, errnoEnforce;
3537        import std.process : searchPathFor;
3538
3539        int[4] mib = [CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV];
3540        size_t len;
3541
3542        auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0);
3543        errnoEnforce(result == 0);
3544
3545        auto argv = new char*[len - 1];
3546        result = sysctl(mib.ptr, mib.length, argv.ptr, &len, null, 0);
3547        errnoEnforce(result == 0);
3548
3549        auto argv0 = argv[0];
3550        if (*argv0 == '/' || *argv0 == '.')
3551        {
3552            import core.sys.posix.stdlib : realpath;
3553            auto absolutePath = realpath(argv0, null);
3554            scope (exit)
3555            {
3556                if (absolutePath)
3557                    free(absolutePath);
3558            }
3559            errnoEnforce(absolutePath);
3560            return to!(string)(absolutePath);
3561        }
3562        else
3563        {
3564            auto absolutePath = searchPathFor(to!string(argv0));
3565            errnoEnforce(absolutePath);
3566            return absolutePath;
3567        }
3568    }
3569    else version (Solaris)
3570    {
3571        import core.sys.posix.unistd : getpid;
3572        import std.string : format;
3573
3574        // Only Solaris 10 and later
3575        return readLink(format("/proc/%d/path/a.out", getpid()));
3576    }
3577    else version (Hurd)
3578    {
3579        return readLink("/proc/self/exe");
3580    }
3581    else
3582        static assert(0, "thisExePath is not supported on this platform");
3583}
3584
3585///
3586@safe unittest
3587{
3588    import std.path : isAbsolute;
3589    auto path = thisExePath();
3590
3591    assert(path.exists);
3592    assert(path.isAbsolute);
3593    assert(path.isFile);
3594}
3595
3596version (StdDdoc)
3597{
3598    /++
3599        Info on a file, similar to what you'd get from stat on a POSIX system.
3600      +/
3601    struct DirEntry
3602    {
3603        @safe:
3604        /++
3605            Constructs a `DirEntry` for the given file (or directory).
3606
3607            Params:
3608                path = The file (or directory) to get a DirEntry for.
3609
3610            Throws:
3611                $(LREF FileException) if the file does not exist.
3612        +/
3613        this(string path);
3614
3615        version (Windows)
3616        {
3617            private this(string path, in WIN32_FIND_DATAW *fd);
3618        }
3619        else version (Posix)
3620        {
3621            private this(string path, core.sys.posix.dirent.dirent* fd);
3622        }
3623
3624        /++
3625            Returns the path to the file represented by this `DirEntry`.
3626
3627Example:
3628--------------------
3629auto de1 = DirEntry("/etc/fonts/fonts.conf");
3630assert(de1.name == "/etc/fonts/fonts.conf");
3631
3632auto de2 = DirEntry("/usr/share/include");
3633assert(de2.name == "/usr/share/include");
3634--------------------
3635          +/
3636        @property string name() const return scope;
3637
3638
3639        /++
3640            Returns whether the file represented by this `DirEntry` is a
3641            directory.
3642
3643Example:
3644--------------------
3645auto de1 = DirEntry("/etc/fonts/fonts.conf");
3646assert(!de1.isDir);
3647
3648auto de2 = DirEntry("/usr/share/include");
3649assert(de2.isDir);
3650--------------------
3651          +/
3652        @property bool isDir() scope;
3653
3654
3655        /++
3656            Returns whether the file represented by this `DirEntry` is a file.
3657
3658            On Windows, if a file is not a directory, then it's a file. So,
3659            either `isFile` or `isDir` will return `true`.
3660
3661            On POSIX systems, if `isFile` is `true`, that indicates that
3662            the file is a regular file (e.g. not a block not device). So, on
3663            POSIX systems, it's possible for both `isFile` and `isDir` to
3664            be `false` for a particular file (in which case, it's a special
3665            file). You can use `attributes` or `statBuf` to get more
3666            information about a special file (see the stat man page for more
3667            details).
3668
3669Example:
3670--------------------
3671auto de1 = DirEntry("/etc/fonts/fonts.conf");
3672assert(de1.isFile);
3673
3674auto de2 = DirEntry("/usr/share/include");
3675assert(!de2.isFile);
3676--------------------
3677          +/
3678        @property bool isFile() scope;
3679
3680        /++
3681            Returns whether the file represented by this `DirEntry` is a
3682            symbolic link.
3683
3684            On Windows, return `true` when the file is either a symbolic
3685            link or a junction point.
3686          +/
3687        @property bool isSymlink() scope;
3688
3689        /++
3690            Returns the size of the the file represented by this `DirEntry`
3691            in bytes.
3692          +/
3693        @property ulong size() scope;
3694
3695        /++
3696            $(BLUE This function is Windows-Only.)
3697
3698            Returns the creation time of the file represented by this
3699            `DirEntry`.
3700          +/
3701        @property SysTime timeCreated() const scope;
3702
3703        /++
3704            Returns the time that the file represented by this `DirEntry` was
3705            last accessed.
3706
3707            Note that many file systems do not update the access time for files
3708            (generally for performance reasons), so there's a good chance that
3709            `timeLastAccessed` will return the same value as
3710            `timeLastModified`.
3711          +/
3712        @property SysTime timeLastAccessed() scope;
3713
3714        /++
3715            Returns the time that the file represented by this `DirEntry` was
3716            last modified.
3717          +/
3718        @property SysTime timeLastModified() scope;
3719
3720        /++
3721            $(BLUE This function is POSIX-Only.)
3722
3723            Returns the time that the file represented by this `DirEntry` was
3724            last changed (not only in contents, but also in permissions or ownership).
3725          +/
3726        @property SysTime timeStatusChanged() const scope;
3727
3728        /++
3729            Returns the _attributes of the file represented by this `DirEntry`.
3730
3731            Note that the file _attributes on Windows and POSIX systems are
3732            completely different. On, Windows, they're what is returned by
3733            `GetFileAttributes`
3734            $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx, GetFileAttributes)
3735            Whereas, an POSIX systems, they're the `st_mode` value which is
3736            part of the `stat` struct gotten by calling `stat`.
3737
3738            On POSIX systems, if the file represented by this `DirEntry` is a
3739            symbolic link, then _attributes are the _attributes of the file
3740            pointed to by the symbolic link.
3741          +/
3742        @property uint attributes() scope;
3743
3744        /++
3745            On POSIX systems, if the file represented by this `DirEntry` is a
3746            symbolic link, then `linkAttributes` are the attributes of the
3747            symbolic link itself. Otherwise, `linkAttributes` is identical to
3748            `attributes`.
3749
3750            On Windows, `linkAttributes` is identical to `attributes`. It
3751            exists on Windows so that you don't have to special-case code for
3752            Windows when dealing with symbolic links.
3753          +/
3754        @property uint linkAttributes() scope;
3755
3756        version (Windows)
3757            alias stat_t = void*;
3758
3759        /++
3760            $(BLUE This function is POSIX-Only.)
3761
3762            The `stat` struct gotten from calling `stat`.
3763          +/
3764        @property stat_t statBuf() scope;
3765    }
3766}
3767else version (Windows)
3768{
3769    struct DirEntry
3770    {
3771    @safe:
3772    public:
3773        alias name this;
3774
3775        this(string path)
3776        {
3777            import std.datetime.systime : FILETIMEToSysTime;
3778
3779            if (!path.exists())
3780                throw new FileException(path, "File does not exist");
3781
3782            _name = path;
3783
3784            with (getFileAttributesWin(path))
3785            {
3786                _size = makeUlong(nFileSizeLow, nFileSizeHigh);
3787                _timeCreated = FILETIMEToSysTime(&ftCreationTime);
3788                _timeLastAccessed = FILETIMEToSysTime(&ftLastAccessTime);
3789                _timeLastModified = FILETIMEToSysTime(&ftLastWriteTime);
3790                _attributes = dwFileAttributes;
3791            }
3792        }
3793
3794        private this(string path, WIN32_FIND_DATAW *fd) @trusted
3795        {
3796            import core.stdc.wchar_ : wcslen;
3797            import std.conv : to;
3798            import std.datetime.systime : FILETIMEToSysTime;
3799            import std.path : buildPath;
3800
3801            fd.cFileName[$ - 1] = 0;
3802
3803            size_t clength = wcslen(&fd.cFileName[0]);
3804            _name = buildPath(path, fd.cFileName[0 .. clength].to!string);
3805            _size = (cast(ulong) fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
3806            _timeCreated = FILETIMEToSysTime(&fd.ftCreationTime);
3807            _timeLastAccessed = FILETIMEToSysTime(&fd.ftLastAccessTime);
3808            _timeLastModified = FILETIMEToSysTime(&fd.ftLastWriteTime);
3809            _attributes = fd.dwFileAttributes;
3810        }
3811
3812        @property string name() const pure nothrow return scope
3813        {
3814            return _name;
3815        }
3816
3817        @property bool isDir() const pure nothrow scope
3818        {
3819            return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
3820        }
3821
3822        @property bool isFile() const pure nothrow scope
3823        {
3824            //Are there no options in Windows other than directory and file?
3825            //If there are, then this probably isn't the best way to determine
3826            //whether this DirEntry is a file or not.
3827            return !isDir;
3828        }
3829
3830        @property bool isSymlink() const pure nothrow scope
3831        {
3832            return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
3833        }
3834
3835        @property ulong size() const pure nothrow scope
3836        {
3837            return _size;
3838        }
3839
3840        @property SysTime timeCreated() const pure nothrow return scope
3841        {
3842            return cast(SysTime)_timeCreated;
3843        }
3844
3845        @property SysTime timeLastAccessed() const pure nothrow return scope
3846        {
3847            return cast(SysTime)_timeLastAccessed;
3848        }
3849
3850        @property SysTime timeLastModified() const pure nothrow return scope
3851        {
3852            return cast(SysTime)_timeLastModified;
3853        }
3854
3855        @property uint attributes() const pure nothrow scope
3856        {
3857            return _attributes;
3858        }
3859
3860        @property uint linkAttributes() const pure nothrow scope
3861        {
3862            return _attributes;
3863        }
3864
3865    private:
3866        string _name; /// The file or directory represented by this DirEntry.
3867
3868        SysTime _timeCreated;      /// The time when the file was created.
3869        SysTime _timeLastAccessed; /// The time when the file was last accessed.
3870        SysTime _timeLastModified; /// The time when the file was last modified.
3871
3872        ulong _size;       /// The size of the file in bytes.
3873        uint  _attributes; /// The file attributes from WIN32_FIND_DATAW.
3874    }
3875}
3876else version (Posix)
3877{
3878    struct DirEntry
3879    {
3880    @safe:
3881    public:
3882        alias name this;
3883
3884        this(string path)
3885        {
3886            if (!path.exists)
3887                throw new FileException(path, "File does not exist");
3888
3889            _name = path;
3890
3891            _didLStat = false;
3892            _didStat = false;
3893            _dTypeSet = false;
3894        }
3895
3896        private this(string path, core.sys.posix.dirent.dirent* fd) @safe
3897        {
3898            import std.path : buildPath;
3899
3900            static if (is(typeof(fd.d_namlen)))
3901                immutable len = fd.d_namlen;
3902            else
3903                immutable len = (() @trusted => core.stdc.string.strlen(fd.d_name.ptr))();
3904
3905            _name = buildPath(path, (() @trusted => fd.d_name.ptr[0 .. len])());
3906
3907            _didLStat = false;
3908            _didStat = false;
3909
3910            //fd_d_type doesn't work for all file systems,
3911            //in which case the result is DT_UNKOWN. But we
3912            //can determine the correct type from lstat, so
3913            //we'll only set the dtype here if we could
3914            //correctly determine it (not lstat in the case
3915            //of DT_UNKNOWN in case we don't ever actually
3916            //need the dtype, thus potentially avoiding the
3917            //cost of calling lstat).
3918            static if (__traits(compiles, fd.d_type != DT_UNKNOWN))
3919            {
3920                if (fd.d_type != DT_UNKNOWN)
3921                {
3922                    _dType = fd.d_type;
3923                    _dTypeSet = true;
3924                }
3925                else
3926                    _dTypeSet = false;
3927            }
3928            else
3929            {
3930                // e.g. Solaris does not have the d_type member
3931                _dTypeSet = false;
3932            }
3933        }
3934
3935        @property string name() const pure nothrow return scope
3936        {
3937            return _name;
3938        }
3939
3940        @property bool isDir() scope
3941        {
3942            _ensureStatOrLStatDone();
3943
3944            return (_statBuf.st_mode & S_IFMT) == S_IFDIR;
3945        }
3946
3947        @property bool isFile() scope
3948        {
3949            _ensureStatOrLStatDone();
3950
3951            return (_statBuf.st_mode & S_IFMT) == S_IFREG;
3952        }
3953
3954        @property bool isSymlink() scope
3955        {
3956            _ensureLStatDone();
3957
3958            return (_lstatMode & S_IFMT) == S_IFLNK;
3959        }
3960
3961        @property ulong size() scope
3962        {
3963            _ensureStatDone();
3964            return _statBuf.st_size;
3965        }
3966
3967        @property SysTime timeStatusChanged() scope
3968        {
3969            _ensureStatDone();
3970
3971            return statTimeToStdTime!'c'(_statBuf);
3972        }
3973
3974        @property SysTime timeLastAccessed() scope
3975        {
3976            _ensureStatDone();
3977
3978            return statTimeToStdTime!'a'(_statBuf);
3979        }
3980
3981        @property SysTime timeLastModified() scope
3982        {
3983            _ensureStatDone();
3984
3985            return statTimeToStdTime!'m'(_statBuf);
3986        }
3987
3988        @property uint attributes() scope
3989        {
3990            _ensureStatDone();
3991
3992            return _statBuf.st_mode;
3993        }
3994
3995        @property uint linkAttributes() scope
3996        {
3997            _ensureLStatDone();
3998
3999            return _lstatMode;
4000        }
4001
4002        @property stat_t statBuf() scope
4003        {
4004            _ensureStatDone();
4005
4006            return _statBuf;
4007        }
4008
4009    private:
4010        /++
4011            This is to support lazy evaluation, because doing stat's is
4012            expensive and not always needed.
4013         +/
4014        void _ensureStatDone() @trusted scope
4015        {
4016            import std.exception : enforce;
4017
4018            if (_didStat)
4019                return;
4020
4021            enforce(stat(_name.tempCString(), &_statBuf) == 0,
4022                    "Failed to stat file `" ~ _name ~ "'");
4023
4024            _didStat = true;
4025        }
4026
4027        /++
4028            This is to support lazy evaluation, because doing stat's is
4029            expensive and not always needed.
4030
4031            Try both stat and lstat for isFile and isDir
4032            to detect broken symlinks.
4033         +/
4034        void _ensureStatOrLStatDone() @trusted scope
4035        {
4036            if (_didStat)
4037                return;
4038
4039            if (stat(_name.tempCString(), &_statBuf) != 0)
4040            {
4041                _ensureLStatDone();
4042
4043                _statBuf = stat_t.init;
4044                _statBuf.st_mode = S_IFLNK;
4045            }
4046            else
4047            {
4048                _didStat = true;
4049            }
4050        }
4051
4052        /++
4053            This is to support lazy evaluation, because doing stat's is
4054            expensive and not always needed.
4055         +/
4056        void _ensureLStatDone() @trusted scope
4057        {
4058            import std.exception : enforce;
4059
4060            if (_didLStat)
4061                return;
4062
4063            stat_t statbuf = void;
4064            enforce(lstat(_name.tempCString(), &statbuf) == 0,
4065                "Failed to stat file `" ~ _name ~ "'");
4066
4067            _lstatMode = statbuf.st_mode;
4068
4069            _dTypeSet = true;
4070            _didLStat = true;
4071        }
4072
4073        string _name; /// The file or directory represented by this DirEntry.
4074
4075        stat_t _statBuf = void;   /// The result of stat().
4076        uint  _lstatMode;         /// The stat mode from lstat().
4077        ubyte _dType;             /// The type of the file.
4078
4079        bool _didLStat = false;   /// Whether lstat() has been called for this DirEntry.
4080        bool _didStat = false;    /// Whether stat() has been called for this DirEntry.
4081        bool _dTypeSet = false;   /// Whether the dType of the file has been set.
4082    }
4083}
4084
4085@system unittest
4086{
4087    version (Windows)
4088    {
4089        if ("C:\\Program Files\\".exists)
4090        {
4091            auto de = DirEntry("C:\\Program Files\\");
4092            assert(!de.isFile);
4093            assert(de.isDir);
4094            assert(!de.isSymlink);
4095        }
4096
4097        if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
4098        {
4099            auto de = DirEntry("C:\\Documents and Settings\\");
4100            assert(de.isSymlink);
4101        }
4102
4103        if ("C:\\Windows\\system.ini".exists)
4104        {
4105            auto de = DirEntry("C:\\Windows\\system.ini");
4106            assert(de.isFile);
4107            assert(!de.isDir);
4108            assert(!de.isSymlink);
4109        }
4110    }
4111    else version (Posix)
4112    {
4113        import std.exception : assertThrown;
4114
4115        if (system_directory.exists)
4116        {
4117            {
4118                auto de = DirEntry(system_directory);
4119                assert(!de.isFile);
4120                assert(de.isDir);
4121                assert(!de.isSymlink);
4122            }
4123
4124            immutable symfile = deleteme ~ "_slink\0";
4125            scope(exit) if (symfile.exists) symfile.remove();
4126
4127            core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
4128
4129            {
4130                auto de = DirEntry(symfile);
4131                assert(!de.isFile);
4132                assert(de.isDir);
4133                assert(de.isSymlink);
4134            }
4135
4136            symfile.remove();
4137            core.sys.posix.unistd.symlink((deleteme ~ "_broken_symlink\0").ptr, symfile.ptr);
4138
4139            {
4140                // https://issues.dlang.org/show_bug.cgi?id=8298
4141                DirEntry de = DirEntry(symfile);
4142
4143                assert(!de.isFile);
4144                assert(!de.isDir);
4145                assert(de.isSymlink);
4146                assertThrown(de.size);
4147                assertThrown(de.timeStatusChanged);
4148                assertThrown(de.timeLastAccessed);
4149                assertThrown(de.timeLastModified);
4150                assertThrown(de.attributes);
4151                assertThrown(de.statBuf);
4152                assert(symfile.exists);
4153                symfile.remove();
4154            }
4155        }
4156
4157        if (system_file.exists)
4158        {
4159            auto de = DirEntry(system_file);
4160            assert(de.isFile);
4161            assert(!de.isDir);
4162            assert(!de.isSymlink);
4163        }
4164    }
4165}
4166
4167alias PreserveAttributes = Flag!"preserveAttributes";
4168
4169version (StdDdoc)
4170{
4171    /// Defaults to `Yes.preserveAttributes` on Windows, and the opposite on all other platforms.
4172    PreserveAttributes preserveAttributesDefault;
4173}
4174else version (Windows)
4175{
4176    enum preserveAttributesDefault = Yes.preserveAttributes;
4177}
4178else
4179{
4180    enum preserveAttributesDefault = No.preserveAttributes;
4181}
4182
4183/***************************************************
4184Copy file `from` _to file `to`. File timestamps are preserved.
4185File attributes are preserved, if `preserve` equals `Yes.preserveAttributes`.
4186On Windows only `Yes.preserveAttributes` (the default on Windows) is supported.
4187If the target file exists, it is overwritten.
4188
4189Params:
4190    from = string or range of characters representing the existing file name
4191    to = string or range of characters representing the target file name
4192    preserve = whether to _preserve the file attributes
4193
4194Throws: $(LREF FileException) on error.
4195 */
4196void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault)
4197if (isSomeFiniteCharInputRange!RF && !isConvertibleToString!RF &&
4198    isSomeFiniteCharInputRange!RT && !isConvertibleToString!RT)
4199{
4200    // Place outside of @trusted block
4201    auto fromz = from.tempCString!FSChar();
4202    auto toz = to.tempCString!FSChar();
4203
4204    static if (isNarrowString!RF && is(immutable ElementEncodingType!RF == immutable char))
4205        alias f = from;
4206    else
4207        enum string f = null;
4208
4209    static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable char))
4210        alias t = to;
4211    else
4212        enum string t = null;
4213
4214    copyImpl(f, t, fromz, toz, preserve);
4215}
4216
4217/// ditto
4218void copy(RF, RT)(auto ref RF from, auto ref RT to, PreserveAttributes preserve = preserveAttributesDefault)
4219if (isConvertibleToString!RF || isConvertibleToString!RT)
4220{
4221    import std.meta : staticMap;
4222    alias Types = staticMap!(convertToString, RF, RT);
4223    copy!Types(from, to, preserve);
4224}
4225
4226///
4227@safe unittest
4228{
4229    auto source = deleteme ~ "source";
4230    auto target = deleteme ~ "target";
4231    auto targetNonExistent = deleteme ~ "target2";
4232
4233    scope(exit) source.remove, target.remove, targetNonExistent.remove;
4234
4235    source.write("source");
4236    target.write("target");
4237
4238    assert(target.readText == "target");
4239
4240    source.copy(target);
4241    assert(target.readText == "source");
4242
4243    source.copy(targetNonExistent);
4244    assert(targetNonExistent.readText == "source");
4245}
4246
4247// https://issues.dlang.org/show_bug.cgi?id=15319
4248@safe unittest
4249{
4250    assert(__traits(compiles, copy("from.txt", "to.txt")));
4251}
4252
4253private void copyImpl(scope const(char)[] f, scope const(char)[] t,
4254                      scope const(FSChar)* fromz, scope const(FSChar)* toz,
4255                      PreserveAttributes preserve) @trusted
4256{
4257    version (Windows)
4258    {
4259        assert(preserve == Yes.preserveAttributes);
4260        immutable result = CopyFileW(fromz, toz, false);
4261        if (!result)
4262        {
4263            import core.stdc.wchar_ : wcslen;
4264            import std.conv : to;
4265            import std.format : format;
4266
4267            /++
4268            Reference resources: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew
4269            Because OS copyfilew handles both source and destination paths,
4270            the GetLastError does not accurately locate whether the error is for the source or destination.
4271            +/
4272            if (!f)
4273                f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]);
4274            if (!t)
4275                t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
4276
4277            throw new FileException(format!"Copy from %s to %s"(f, t));
4278        }
4279    }
4280    else version (Posix)
4281    {
4282        static import core.stdc.stdio;
4283        import std.conv : to, octal;
4284
4285        immutable fdr = core.sys.posix.fcntl.open(fromz, O_RDONLY);
4286        cenforce(fdr != -1, f, fromz);
4287        scope(exit) core.sys.posix.unistd.close(fdr);
4288
4289        stat_t statbufr = void;
4290        cenforce(fstat(fdr, &statbufr) == 0, f, fromz);
4291        //cenforce(core.sys.posix.sys.stat.fstat(fdr, &statbufr) == 0, f, fromz);
4292
4293        immutable fdw = core.sys.posix.fcntl.open(toz,
4294                O_CREAT | O_WRONLY, octal!666);
4295        cenforce(fdw != -1, t, toz);
4296        {
4297            scope(failure) core.sys.posix.unistd.close(fdw);
4298
4299            stat_t statbufw = void;
4300            cenforce(fstat(fdw, &statbufw) == 0, t, toz);
4301            if (statbufr.st_dev == statbufw.st_dev && statbufr.st_ino == statbufw.st_ino)
4302                throw new FileException(t, "Source and destination are the same file");
4303        }
4304
4305        scope(failure) core.stdc.stdio.remove(toz);
4306        {
4307            scope(failure) core.sys.posix.unistd.close(fdw);
4308            cenforce(ftruncate(fdw, 0) == 0, t, toz);
4309
4310            auto BUFSIZ = 4096u * 16;
4311            auto buf = core.stdc.stdlib.malloc(BUFSIZ);
4312            if (!buf)
4313            {
4314                BUFSIZ = 4096;
4315                buf = core.stdc.stdlib.malloc(BUFSIZ);
4316                if (!buf)
4317                {
4318                    import core.exception : onOutOfMemoryError;
4319                    onOutOfMemoryError();
4320                }
4321            }
4322            scope(exit) core.stdc.stdlib.free(buf);
4323
4324            for (auto size = statbufr.st_size; size; )
4325            {
4326                immutable toxfer = (size > BUFSIZ) ? BUFSIZ : cast(size_t) size;
4327                cenforce(
4328                    core.sys.posix.unistd.read(fdr, buf, toxfer) == toxfer
4329                    && core.sys.posix.unistd.write(fdw, buf, toxfer) == toxfer,
4330                    f, fromz);
4331                assert(size >= toxfer);
4332                size -= toxfer;
4333            }
4334            if (preserve)
4335                cenforce(fchmod(fdw, to!mode_t(statbufr.st_mode)) == 0, f, fromz);
4336        }
4337
4338        cenforce(core.sys.posix.unistd.close(fdw) != -1, f, fromz);
4339
4340        setTimesImpl(t, toz, statbufr.statTimeToStdTime!'a', statbufr.statTimeToStdTime!'m');
4341    }
4342}
4343
4344// https://issues.dlang.org/show_bug.cgi?id=14817
4345@safe unittest
4346{
4347    import std.algorithm, std.file;
4348    auto t1 = deleteme, t2 = deleteme~"2";
4349    scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
4350    write(t1, "11");
4351    copy(t1, t2);
4352    assert(readText(t2) == "11");
4353    write(t1, "2");
4354    copy(t1, t2);
4355    assert(readText(t2) == "2");
4356
4357    import std.utf : byChar;
4358    copy(t1.byChar, t2.byChar);
4359    assert(readText(t2.byChar) == "2");
4360
4361// https://issues.dlang.org/show_bug.cgi?id=20370
4362    version (Windows)
4363        assert(t1.timeLastModified == t2.timeLastModified);
4364    else static if (is(typeof(&utimensat)) || is(typeof(&setattrlist)))
4365        assert(t1.timeLastModified == t2.timeLastModified);
4366    else
4367        assert(abs(t1.timeLastModified - t2.timeLastModified) < dur!"usecs"(1));
4368}
4369
4370// https://issues.dlang.org/show_bug.cgi?id=11434
4371@safe version (Posix) @safe unittest
4372{
4373    import std.conv : octal;
4374    auto t1 = deleteme, t2 = deleteme~"2";
4375    scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
4376    write(t1, "1");
4377    setAttributes(t1, octal!767);
4378    copy(t1, t2, Yes.preserveAttributes);
4379    assert(readText(t2) == "1");
4380    assert(getAttributes(t2) == octal!100767);
4381}
4382
4383// https://issues.dlang.org/show_bug.cgi?id=15865
4384@safe unittest
4385{
4386    import std.exception : assertThrown;
4387    auto t = deleteme;
4388    write(t, "a");
4389    scope(exit) t.remove();
4390    assertThrown!FileException(copy(t, t));
4391    assert(readText(t) == "a");
4392}
4393
4394// https://issues.dlang.org/show_bug.cgi?id=19834
4395version (Windows) @safe unittest
4396{
4397    import std.exception : collectException;
4398    import std.algorithm.searching : startsWith;
4399    import std.format : format;
4400
4401    auto f = deleteme;
4402    auto t = f ~ "2";
4403    auto ex = collectException(copy(f, t));
4404    assert(ex.msg.startsWith(format!"Copy from %s to %s"(f, t)));
4405}
4406
4407/++
4408    Remove directory and all of its content and subdirectories,
4409    recursively.
4410
4411    Params:
4412        pathname = the path of the directory to completely remove
4413        de = The $(LREF DirEntry) to remove
4414
4415    Throws:
4416        $(LREF FileException) if there is an error (including if the given
4417        file is not a directory).
4418 +/
4419void rmdirRecurse(scope const(char)[] pathname) @safe
4420{
4421    //No references to pathname will be kept after rmdirRecurse,
4422    //so the cast is safe
4423    rmdirRecurse(DirEntry((() @trusted => cast(string) pathname)()));
4424}
4425
4426/// ditto
4427void rmdirRecurse(ref DirEntry de) @safe
4428{
4429    if (!de.isDir)
4430        throw new FileException(de.name, "Not a directory");
4431
4432    if (de.isSymlink)
4433    {
4434        version (Windows)
4435            rmdir(de.name);
4436        else
4437            remove(de.name);
4438    }
4439    else
4440    {
4441        // dirEntries is @system because it uses a DirIterator with a
4442        // RefCounted variable, but here, no references to the payload is
4443        // escaped to the outside, so this should be @trusted
4444        () @trusted {
4445            // all children, recursively depth-first
4446            foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false))
4447            {
4448                attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name);
4449            }
4450        }();
4451
4452        // the dir itself
4453        rmdir(de.name);
4454    }
4455}
4456///ditto
4457//Note, without this overload, passing an RValue DirEntry still works, but
4458//actually fully reconstructs a DirEntry inside the
4459//"rmdirRecurse(in char[] pathname)" implementation. That is needlessly
4460//expensive.
4461//A DirEntry is a bit big (72B), so keeping the "by ref" signature is desirable.
4462void rmdirRecurse(DirEntry de) @safe
4463{
4464    rmdirRecurse(de);
4465}
4466
4467///
4468@system unittest
4469{
4470    import std.path : buildPath;
4471
4472    auto dir = deleteme.buildPath("a", "b", "c");
4473
4474    dir.mkdirRecurse;
4475    assert(dir.exists);
4476
4477    deleteme.rmdirRecurse;
4478    assert(!dir.exists);
4479    assert(!deleteme.exists);
4480}
4481
4482version (Windows) @system unittest
4483{
4484    import std.exception : enforce;
4485    auto d = deleteme ~ r".dir\a\b\c\d\e\f\g";
4486    mkdirRecurse(d);
4487    rmdirRecurse(deleteme ~ ".dir");
4488    enforce(!exists(deleteme ~ ".dir"));
4489}
4490
4491version (Posix) @system unittest
4492{
4493    import std.exception : enforce, collectException;
4494
4495    collectException(rmdirRecurse(deleteme));
4496    auto d = deleteme~"/a/b/c/d/e/f/g";
4497    enforce(collectException(mkdir(d)));
4498    mkdirRecurse(d);
4499    core.sys.posix.unistd.symlink((deleteme~"/a/b/c\0").ptr,
4500            (deleteme~"/link\0").ptr);
4501    rmdirRecurse(deleteme~"/link");
4502    enforce(exists(d));
4503    rmdirRecurse(deleteme);
4504    enforce(!exists(deleteme));
4505
4506    d = deleteme~"/a/b/c/d/e/f/g";
4507    mkdirRecurse(d);
4508    const linkTarget = deleteme ~ "/link";
4509    symlink(deleteme ~ "/a/b/c", linkTarget);
4510    rmdirRecurse(deleteme);
4511    enforce(!exists(deleteme));
4512}
4513
4514@system unittest
4515{
4516    void[] buf;
4517
4518    buf = new void[10];
4519    (cast(byte[]) buf)[] = 3;
4520    string unit_file = deleteme ~ "-unittest_write.tmp";
4521    if (exists(unit_file)) remove(unit_file);
4522    write(unit_file, buf);
4523    void[] buf2 = read(unit_file);
4524    assert(buf == buf2);
4525
4526    string unit2_file = deleteme ~ "-unittest_write2.tmp";
4527    copy(unit_file, unit2_file);
4528    buf2 = read(unit2_file);
4529    assert(buf == buf2);
4530
4531    remove(unit_file);
4532    assert(!exists(unit_file));
4533    remove(unit2_file);
4534    assert(!exists(unit2_file));
4535}
4536
4537/**
4538 * Dictates directory spanning policy for $(D_PARAM dirEntries) (see below).
4539 */
4540enum SpanMode
4541{
4542    /** Only spans one directory. */
4543    shallow,
4544    /** Spans the directory in
4545     $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Post-order,
4546     _depth-first $(B post)-order), i.e. the content of any
4547     subdirectory is spanned before that subdirectory itself. Useful
4548     e.g. when recursively deleting files.  */
4549    depth,
4550    /** Spans the directory in
4551    $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Pre-order, depth-first
4552    $(B pre)-order), i.e. the content of any subdirectory is spanned
4553    right after that subdirectory itself.
4554
4555    Note that `SpanMode.breadth` will not result in all directory
4556    members occurring before any subdirectory members, i.e. it is not
4557    _true
4558    $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Breadth-first_search,
4559    _breadth-first traversal).
4560    */
4561    breadth,
4562}
4563
4564///
4565@system unittest
4566{
4567    import std.algorithm.comparison : equal;
4568    import std.algorithm.iteration : map;
4569    import std.algorithm.sorting : sort;
4570    import std.array : array;
4571    import std.path : buildPath, relativePath;
4572
4573    auto root = deleteme ~ "root";
4574    scope(exit) root.rmdirRecurse;
4575    root.mkdir;
4576
4577    root.buildPath("animals").mkdir;
4578    root.buildPath("animals", "cat").mkdir;
4579
4580    alias removeRoot = (return scope e) => e.relativePath(root);
4581
4582    assert(root.dirEntries(SpanMode.depth).map!removeRoot.equal(
4583        [buildPath("animals", "cat"), "animals"]));
4584
4585    assert(root.dirEntries(SpanMode.breadth).map!removeRoot.equal(
4586        ["animals", buildPath("animals", "cat")]));
4587
4588    root.buildPath("plants").mkdir;
4589
4590    assert(root.dirEntries(SpanMode.shallow).array.sort.map!removeRoot.equal(
4591        ["animals", "plants"]));
4592}
4593
4594private struct DirIteratorImpl
4595{
4596  @safe:
4597    SpanMode _mode;
4598    // Whether we should follow symlinked directories while iterating.
4599    // It also indicates whether we should avoid functions which call
4600    // stat (since we should only need lstat in this case and it would
4601    // be more efficient to not call stat in addition to lstat).
4602    bool _followSymlink;
4603    DirEntry _cur;
4604    DirHandle[] _stack;
4605    DirEntry[] _stashed; //used in depth first mode
4606
4607    //stack helpers
4608    void pushExtra(DirEntry de)
4609    {
4610        _stashed ~= de;
4611    }
4612
4613    //ditto
4614    bool hasExtra()
4615    {
4616        return _stashed.length != 0;
4617    }
4618
4619    //ditto
4620    DirEntry popExtra()
4621    {
4622        DirEntry de;
4623        de = _stashed[$-1];
4624        _stashed.popBack();
4625        return de;
4626    }
4627
4628    version (Windows)
4629    {
4630        WIN32_FIND_DATAW _findinfo;
4631        struct DirHandle
4632        {
4633            string dirpath;
4634            HANDLE h;
4635        }
4636
4637        bool stepIn(string directory) @safe
4638        {
4639            import std.path : chainPath;
4640            auto searchPattern = chainPath(directory, "*.*");
4641
4642            static auto trustedFindFirstFileW(typeof(searchPattern) pattern, scope WIN32_FIND_DATAW* findinfo) @trusted
4643            {
4644                return FindFirstFileW(pattern.tempCString!FSChar(), findinfo);
4645            }
4646
4647            HANDLE h = trustedFindFirstFileW(searchPattern, &_findinfo);
4648            cenforce(h != INVALID_HANDLE_VALUE, directory);
4649            _stack ~= DirHandle(directory, h);
4650            return toNext(false, &_findinfo);
4651        }
4652
4653        bool next()
4654        {
4655            if (_stack.length == 0)
4656                return false;
4657            return toNext(true, &_findinfo);
4658        }
4659
4660        bool toNext(bool fetch, scope WIN32_FIND_DATAW* findinfo) @trusted
4661        {
4662            import core.stdc.wchar_ : wcscmp;
4663
4664            if (fetch)
4665            {
4666                if (FindNextFileW(_stack[$-1].h, findinfo) == FALSE)
4667                {
4668                    popDirStack();
4669                    return false;
4670                }
4671            }
4672            while (wcscmp(&findinfo.cFileName[0], ".") == 0 ||
4673                   wcscmp(&findinfo.cFileName[0], "..") == 0)
4674                if (FindNextFileW(_stack[$-1].h, findinfo) == FALSE)
4675                {
4676                    popDirStack();
4677                    return false;
4678                }
4679            _cur = DirEntry(_stack[$-1].dirpath, findinfo);
4680            return true;
4681        }
4682
4683        void popDirStack() @trusted
4684        {
4685            assert(_stack.length != 0);
4686            FindClose(_stack[$-1].h);
4687            _stack.popBack();
4688        }
4689
4690        void releaseDirStack() @trusted
4691        {
4692            foreach (d; _stack)
4693                FindClose(d.h);
4694        }
4695
4696        bool mayStepIn()
4697        {
4698            return _followSymlink ? _cur.isDir : _cur.isDir && !_cur.isSymlink;
4699        }
4700    }
4701    else version (Posix)
4702    {
4703        struct DirHandle
4704        {
4705            string dirpath;
4706            DIR*   h;
4707        }
4708
4709        bool stepIn(string directory)
4710        {
4711            static auto trustedOpendir(string dir) @trusted
4712            {
4713                return opendir(dir.tempCString());
4714            }
4715
4716            auto h = directory.length ? trustedOpendir(directory) : trustedOpendir(".");
4717            cenforce(h, directory);
4718            _stack ~= (DirHandle(directory, h));
4719            return next();
4720        }
4721
4722        bool next() @trusted
4723        {
4724            if (_stack.length == 0)
4725                return false;
4726
4727            for (dirent* fdata; (fdata = readdir(_stack[$-1].h)) != null; )
4728            {
4729                // Skip "." and ".."
4730                if (core.stdc.string.strcmp(&fdata.d_name[0], ".") &&
4731                    core.stdc.string.strcmp(&fdata.d_name[0], ".."))
4732                {
4733                    _cur = DirEntry(_stack[$-1].dirpath, fdata);
4734                    return true;
4735                }
4736            }
4737
4738            popDirStack();
4739            return false;
4740        }
4741
4742        void popDirStack() @trusted
4743        {
4744            assert(_stack.length != 0);
4745            closedir(_stack[$-1].h);
4746            _stack.popBack();
4747        }
4748
4749        void releaseDirStack() @trusted
4750        {
4751            foreach (d; _stack)
4752                closedir(d.h);
4753        }
4754
4755        bool mayStepIn()
4756        {
4757            return _followSymlink ? _cur.isDir : attrIsDir(_cur.linkAttributes);
4758        }
4759    }
4760
4761    this(R)(R pathname, SpanMode mode, bool followSymlink)
4762        if (isSomeFiniteCharInputRange!R)
4763    {
4764        _mode = mode;
4765        _followSymlink = followSymlink;
4766
4767        static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
4768            alias pathnameStr = pathname;
4769        else
4770        {
4771            import std.array : array;
4772            string pathnameStr = pathname.array;
4773        }
4774        if (stepIn(pathnameStr))
4775        {
4776            if (_mode == SpanMode.depth)
4777                while (mayStepIn())
4778                {
4779                    auto thisDir = _cur;
4780                    if (stepIn(_cur.name))
4781                    {
4782                        pushExtra(thisDir);
4783                    }
4784                    else
4785                        break;
4786                }
4787        }
4788    }
4789
4790    @property bool empty()
4791    {
4792        return _stashed.length == 0 && _stack.length == 0;
4793    }
4794
4795    @property DirEntry front()
4796    {
4797        return _cur;
4798    }
4799
4800    void popFront()
4801    {
4802        switch (_mode)
4803        {
4804        case SpanMode.depth:
4805            if (next())
4806            {
4807                while (mayStepIn())
4808                {
4809                    auto thisDir = _cur;
4810                    if (stepIn(_cur.name))
4811                    {
4812                        pushExtra(thisDir);
4813                    }
4814                    else
4815                        break;
4816                }
4817            }
4818            else if (hasExtra())
4819                _cur = popExtra();
4820            break;
4821        case SpanMode.breadth:
4822            if (mayStepIn())
4823            {
4824                if (!stepIn(_cur.name))
4825                    while (!empty && !next()){}
4826            }
4827            else
4828                while (!empty && !next()){}
4829            break;
4830        default:
4831            next();
4832        }
4833    }
4834
4835    ~this()
4836    {
4837        releaseDirStack();
4838    }
4839}
4840
4841struct DirIterator
4842{
4843@safe:
4844private:
4845    RefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl;
4846    this(string pathname, SpanMode mode, bool followSymlink) @trusted
4847    {
4848        impl = typeof(impl)(pathname, mode, followSymlink);
4849    }
4850public:
4851    @property bool empty() { return impl.empty; }
4852    @property DirEntry front() { return impl.front; }
4853    void popFront() { impl.popFront(); }
4854}
4855/++
4856    Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
4857    of `DirEntry` that lazily iterates a given directory,
4858    also provides two ways of foreach iteration. The iteration variable can be of
4859    type `string` if only the name is needed, or `DirEntry`
4860    if additional details are needed. The span _mode dictates how the
4861    directory is traversed. The name of each iterated directory entry
4862    contains the absolute or relative _path (depending on _pathname).
4863
4864    Note: The order of returned directory entries is as it is provided by the
4865    operating system / filesystem, and may not follow any particular sorting.
4866
4867    Params:
4868        path = The directory to iterate over.
4869               If empty, the current directory will be iterated.
4870
4871        pattern = Optional string with wildcards, such as $(RED
4872                  "*.d"). When present, it is used to filter the
4873                  results by their file name. The supported wildcard
4874                  strings are described under $(REF globMatch,
4875                  std,_path).
4876
4877        mode = Whether the directory's sub-directories should be
4878               iterated in depth-first post-order ($(LREF depth)),
4879               depth-first pre-order ($(LREF breadth)), or not at all
4880               ($(LREF shallow)).
4881
4882        followSymlink = Whether symbolic links which point to directories
4883                         should be treated as directories and their contents
4884                         iterated over.
4885
4886    Returns:
4887        An $(REF_ALTTEXT input range, isInputRange,std,range,primitives) of
4888        $(LREF DirEntry).
4889
4890    Throws:
4891        $(LREF FileException) if the directory does not exist.
4892
4893Example:
4894--------------------
4895// Iterate a directory in depth
4896foreach (string name; dirEntries("destroy/me", SpanMode.depth))
4897{
4898    remove(name);
4899}
4900
4901// Iterate the current directory in breadth
4902foreach (string name; dirEntries("", SpanMode.breadth))
4903{
4904    writeln(name);
4905}
4906
4907// Iterate a directory and get detailed info about it
4908foreach (DirEntry e; dirEntries("dmd-testing", SpanMode.breadth))
4909{
4910    writeln(e.name, "\t", e.size);
4911}
4912
4913// Iterate over all *.d files in current directory and all its subdirectories
4914auto dFiles = dirEntries("", SpanMode.depth).filter!(f => f.name.endsWith(".d"));
4915foreach (d; dFiles)
4916    writeln(d.name);
4917
4918// Hook it up with std.parallelism to compile them all in parallel:
4919foreach (d; parallel(dFiles, 1)) //passes by 1 file to each thread
4920{
4921    string cmd = "dmd -c "  ~ d.name;
4922    writeln(cmd);
4923    std.process.executeShell(cmd);
4924}
4925
4926// Iterate over all D source files in current directory and all its
4927// subdirectories
4928auto dFiles = dirEntries("","*.{d,di}",SpanMode.depth);
4929foreach (d; dFiles)
4930    writeln(d.name);
4931--------------------
4932 +/
4933auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
4934{
4935    return DirIterator(path, mode, followSymlink);
4936}
4937
4938/// Duplicate functionality of D1's `std.file.listdir()`:
4939@safe unittest
4940{
4941    string[] listdir(string pathname)
4942    {
4943        import std.algorithm;
4944        import std.array;
4945        import std.file;
4946        import std.path;
4947
4948        return std.file.dirEntries(pathname, SpanMode.shallow)
4949            .filter!(a => a.isFile)
4950            .map!((return a) => std.path.baseName(a.name))
4951            .array;
4952    }
4953
4954    void main(string[] args)
4955    {
4956        import std.stdio;
4957
4958        string[] files = listdir(args[1]);
4959        writefln("%s", files);
4960     }
4961}
4962
4963@system unittest
4964{
4965    import std.algorithm.comparison : equal;
4966    import std.algorithm.iteration : map;
4967    import std.algorithm.searching : startsWith;
4968    import std.array : array;
4969    import std.conv : to;
4970    import std.path : buildPath, absolutePath;
4971    import std.file : dirEntries;
4972    import std.process : thisProcessID;
4973    import std.range.primitives : walkLength;
4974
4975    version (Android)
4976        string testdir = deleteme; // This has to be an absolute path when
4977                                   // called from a shared library on Android,
4978                                   // ie an apk
4979    else
4980        string testdir = tempDir.buildPath("deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID));
4981    mkdirRecurse(buildPath(testdir, "somedir"));
4982    scope(exit) rmdirRecurse(testdir);
4983    write(buildPath(testdir, "somefile"), null);
4984    write(buildPath(testdir, "somedir", "somedeepfile"), null);
4985
4986    // testing range interface
4987    size_t equalEntries(string relpath, SpanMode mode)
4988    {
4989        import std.exception : enforce;
4990        auto len = enforce(walkLength(dirEntries(absolutePath(relpath), mode)));
4991        assert(walkLength(dirEntries(relpath, mode)) == len);
4992        assert(equal(
4993                   map!((return a) => absolutePath(a.name))(dirEntries(relpath, mode)),
4994                   map!(a => a.name)(dirEntries(absolutePath(relpath), mode))));
4995        return len;
4996    }
4997
4998    assert(equalEntries(testdir, SpanMode.shallow) == 2);
4999    assert(equalEntries(testdir, SpanMode.depth) == 3);
5000    assert(equalEntries(testdir, SpanMode.breadth) == 3);
5001
5002    // testing opApply
5003    foreach (string name; dirEntries(testdir, SpanMode.breadth))
5004    {
5005        //writeln(name);
5006        assert(name.startsWith(testdir));
5007    }
5008    foreach (DirEntry e; dirEntries(absolutePath(testdir), SpanMode.breadth))
5009    {
5010        //writeln(name);
5011        assert(e.isFile || e.isDir, e.name);
5012    }
5013
5014    // https://issues.dlang.org/show_bug.cgi?id=7264
5015    foreach (string name; dirEntries(testdir, "*.d", SpanMode.breadth))
5016    {
5017
5018    }
5019    foreach (entry; dirEntries(testdir, SpanMode.breadth))
5020    {
5021        static assert(is(typeof(entry) == DirEntry));
5022    }
5023    // https://issues.dlang.org/show_bug.cgi?id=7138
5024    auto a = array(dirEntries(testdir, SpanMode.shallow));
5025
5026    // https://issues.dlang.org/show_bug.cgi?id=11392
5027    auto dFiles = dirEntries(testdir, SpanMode.shallow);
5028    foreach (d; dFiles){}
5029
5030    // https://issues.dlang.org/show_bug.cgi?id=15146
5031    dirEntries("", SpanMode.shallow).walkLength();
5032}
5033
5034/// Ditto
5035auto dirEntries(string path, string pattern, SpanMode mode,
5036    bool followSymlink = true)
5037{
5038    import std.algorithm.iteration : filter;
5039    import std.path : globMatch, baseName;
5040
5041    bool f(DirEntry de) { return globMatch(baseName(de.name), pattern); }
5042    return filter!f(DirIterator(path, mode, followSymlink));
5043}
5044
5045@system unittest
5046{
5047    import std.stdio : writefln;
5048    immutable dpath = deleteme ~ "_dir";
5049    immutable fpath = deleteme ~ "_file";
5050    immutable sdpath = deleteme ~ "_sdir";
5051    immutable sfpath = deleteme ~ "_sfile";
5052    scope(exit)
5053    {
5054        if (dpath.exists) rmdirRecurse(dpath);
5055        if (fpath.exists) remove(fpath);
5056        if (sdpath.exists) remove(sdpath);
5057        if (sfpath.exists) remove(sfpath);
5058    }
5059
5060    mkdir(dpath);
5061    write(fpath, "hello world");
5062    version (Posix)
5063    {
5064        core.sys.posix.unistd.symlink((dpath ~ '\0').ptr, (sdpath ~ '\0').ptr);
5065        core.sys.posix.unistd.symlink((fpath ~ '\0').ptr, (sfpath ~ '\0').ptr);
5066    }
5067
5068    static struct Flags { bool dir, file, link; }
5069    auto tests = [dpath : Flags(true), fpath : Flags(false, true)];
5070    version (Posix)
5071    {
5072        tests[sdpath] = Flags(true, false, true);
5073        tests[sfpath] = Flags(false, true, true);
5074    }
5075
5076    auto past = Clock.currTime() - 2.seconds;
5077    auto future = past + 4.seconds;
5078
5079    foreach (path, flags; tests)
5080    {
5081        auto de = DirEntry(path);
5082        assert(de.name == path);
5083        assert(de.isDir == flags.dir);
5084        assert(de.isFile == flags.file);
5085        assert(de.isSymlink == flags.link);
5086
5087        assert(de.isDir == path.isDir);
5088        assert(de.isFile == path.isFile);
5089        assert(de.isSymlink == path.isSymlink);
5090        assert(de.size == path.getSize());
5091        assert(de.attributes == getAttributes(path));
5092        assert(de.linkAttributes == getLinkAttributes(path));
5093
5094        scope(failure) writefln("[%s] [%s] [%s] [%s]", past, de.timeLastAccessed, de.timeLastModified, future);
5095        assert(de.timeLastAccessed > past);
5096        assert(de.timeLastAccessed < future);
5097        assert(de.timeLastModified > past);
5098        assert(de.timeLastModified < future);
5099
5100        assert(attrIsDir(de.attributes) == flags.dir);
5101        assert(attrIsDir(de.linkAttributes) == (flags.dir && !flags.link));
5102        assert(attrIsFile(de.attributes) == flags.file);
5103        assert(attrIsFile(de.linkAttributes) == (flags.file && !flags.link));
5104        assert(!attrIsSymlink(de.attributes));
5105        assert(attrIsSymlink(de.linkAttributes) == flags.link);
5106
5107        version (Windows)
5108        {
5109            assert(de.timeCreated > past);
5110            assert(de.timeCreated < future);
5111        }
5112        else version (Posix)
5113        {
5114            assert(de.timeStatusChanged > past);
5115            assert(de.timeStatusChanged < future);
5116            assert(de.attributes == de.statBuf.st_mode);
5117        }
5118    }
5119}
5120
5121// Make sure that dirEntries does not butcher Unicode file names
5122// https://issues.dlang.org/show_bug.cgi?id=17962
5123@system unittest
5124{
5125    import std.algorithm.comparison : equal;
5126    import std.algorithm.iteration : map;
5127    import std.algorithm.sorting : sort;
5128    import std.array : array;
5129    import std.path : buildPath;
5130    import std.uni : normalize;
5131
5132    // The Unicode normalization is required to make the tests pass on Mac OS X.
5133    auto dir = deleteme ~ normalize("����");
5134    scope(exit) if (dir.exists) rmdirRecurse(dir);
5135    mkdir(dir);
5136    auto files = ["Hello World",
5137                  "Ma Ch��rie.jpeg",
5138                  "������������������.txt"].map!(a => buildPath(dir, normalize(a)))().array();
5139    sort(files);
5140    foreach (file; files)
5141        write(file, "nothing");
5142
5143    auto result = dirEntries(dir, SpanMode.shallow).map!((return a) => a.name.normalize()).array();
5144    sort(result);
5145
5146    assert(equal(files, result));
5147}
5148
5149// https://issues.dlang.org/show_bug.cgi?id=21250
5150@system unittest
5151{
5152    import std.exception : assertThrown;
5153    assertThrown!Exception(dirEntries("237f5babd6de21f40915826699582e36", "*.bin", SpanMode.depth));
5154}
5155
5156/**
5157 * Reads a file line by line and parses the line into a single value or a
5158 * $(REF Tuple, std,typecons) of values depending on the length of `Types`.
5159 * The lines are parsed using the specified format string. The format string is
5160 * passed to $(REF formattedRead, std,_format), and therefore must conform to the
5161 * _format string specification outlined in $(MREF std, _format).
5162 *
5163 * Params:
5164 *     Types = the types that each of the elements in the line should be returned as
5165 *     filename = the name of the file to read
5166 *     format = the _format string to use when reading
5167 *
5168 * Returns:
5169 *     If only one type is passed, then an array of that type. Otherwise, an
5170 *     array of $(REF Tuple, std,typecons)s.
5171 *
5172 * Throws:
5173 *     `Exception` if the format string is malformed. Also, throws `Exception`
5174 *     if any of the lines in the file are not fully consumed by the call
5175 *     to $(REF formattedRead, std,_format). Meaning that no empty lines or lines
5176 *     with extra characters are allowed.
5177 */
5178Select!(Types.length == 1, Types[0][], Tuple!(Types)[])
5179slurp(Types...)(string filename, scope const(char)[] format)
5180{
5181    import std.array : appender;
5182    import std.conv : text;
5183    import std.exception : enforce;
5184    import std.format.read : formattedRead;
5185    import std.stdio : File;
5186    import std.string : stripRight;
5187
5188    auto app = appender!(typeof(return))();
5189    ElementType!(typeof(return)) toAdd;
5190    auto f = File(filename);
5191    scope(exit) f.close();
5192    foreach (line; f.byLine())
5193    {
5194        formattedRead(line, format, &toAdd);
5195        enforce(line.stripRight("\r").empty,
5196                text("Trailing characters at the end of line: `", line,
5197                        "'"));
5198        app.put(toAdd);
5199    }
5200    return app.data;
5201}
5202
5203///
5204@system unittest
5205{
5206    import std.typecons : tuple;
5207
5208    scope(exit)
5209    {
5210        assert(exists(deleteme));
5211        remove(deleteme);
5212    }
5213
5214    write(deleteme, "12 12.25\n345 1.125"); // deleteme is the name of a temporary file
5215
5216    // Load file; each line is an int followed by comma, whitespace and a
5217    // double.
5218    auto a = slurp!(int, double)(deleteme, "%s %s");
5219    assert(a.length == 2);
5220    assert(a[0] == tuple(12, 12.25));
5221    assert(a[1] == tuple(345, 1.125));
5222}
5223
5224@system unittest
5225{
5226    import std.typecons : tuple;
5227
5228    scope(exit)
5229    {
5230        assert(exists(deleteme));
5231        remove(deleteme);
5232    }
5233    write(deleteme, "10\r\n20");
5234    assert(slurp!(int)(deleteme, "%d") == [10, 20]);
5235}
5236
5237/**
5238Returns the path to a directory for temporary files.
5239On POSIX platforms, it searches through the following list of directories
5240and returns the first one which is found to exist:
5241$(OL
5242    $(LI The directory given by the `TMPDIR` environment variable.)
5243    $(LI The directory given by the `TEMP` environment variable.)
5244    $(LI The directory given by the `TMP` environment variable.)
5245    $(LI `/tmp/`)
5246    $(LI `/var/tmp/`)
5247    $(LI `/usr/tmp/`)
5248)
5249
5250On all platforms, `tempDir` returns the current working directory on failure.
5251
5252The return value of the function is cached, so the procedures described
5253below will only be performed the first time the function is called.  All
5254subsequent runs will return the same string, regardless of whether
5255environment variables and directory structures have changed in the
5256meantime.
5257
5258The POSIX `tempDir` algorithm is inspired by Python's
5259$(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, `tempfile.tempdir`).
5260
5261Returns:
5262    On Windows, this function returns the result of calling the Windows API function
5263    $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992.aspx, `GetTempPath`).
5264
5265    On POSIX platforms, it searches through the following list of directories
5266    and returns the first one which is found to exist:
5267    $(OL
5268        $(LI The directory given by the `TMPDIR` environment variable.)
5269        $(LI The directory given by the `TEMP` environment variable.)
5270        $(LI The directory given by the `TMP` environment variable.)
5271        $(LI `/tmp`)
5272        $(LI `/var/tmp`)
5273        $(LI `/usr/tmp`)
5274    )
5275
5276    On all platforms, `tempDir` returns `"."` on failure, representing
5277    the current working directory.
5278*/
5279string tempDir() @trusted
5280{
5281    // We must check that the end of a path is not a separator, before adding another
5282    // If we don't we end up with https://issues.dlang.org/show_bug.cgi?id=22738
5283    static string addSeparator(string input)
5284    {
5285        import std.path : dirSeparator;
5286        import std.algorithm.searching : endsWith;
5287
5288        // It is very rare a directory path will reach this point with a directory separator at the end
5289        // However on OSX this can happen, so we must verify lest we break user code i.e. https://github.com/dlang/dub/pull/2208
5290        if (!input.endsWith(dirSeparator))
5291            return input ~ dirSeparator;
5292        else
5293            return input;
5294    }
5295
5296    static string cache;
5297    if (cache is null)
5298    {
5299        version (Windows)
5300        {
5301            import std.conv : to;
5302            // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992(v=vs.85).aspx
5303            wchar[MAX_PATH + 2] buf;
5304            DWORD len = GetTempPathW(buf.length, buf.ptr);
5305            if (len) cache = buf[0 .. len].to!string;
5306        }
5307        else version (Posix)
5308        {
5309            import std.process : environment;
5310            // This function looks through the list of alternative directories
5311            // and returns the first one which exists and is a directory.
5312            static string findExistingDir(T...)(lazy T alternatives)
5313            {
5314                foreach (dir; alternatives)
5315                    if (!dir.empty && exists(dir)) return addSeparator(dir);
5316                return null;
5317            }
5318
5319            cache = findExistingDir(environment.get("TMPDIR"),
5320                                    environment.get("TEMP"),
5321                                    environment.get("TMP"),
5322                                    "/tmp",
5323                                    "/var/tmp",
5324                                    "/usr/tmp");
5325        }
5326        else static assert(false, "Unsupported platform");
5327
5328        if (cache is null)
5329        {
5330            cache = addSeparator(getcwd());
5331        }
5332    }
5333    return cache;
5334}
5335
5336///
5337@safe unittest
5338{
5339    import std.ascii : letters;
5340    import std.conv : to;
5341    import std.path : buildPath;
5342    import std.random : randomSample;
5343    import std.utf : byCodeUnit;
5344
5345    // random id with 20 letters
5346    auto id = letters.byCodeUnit.randomSample(20).to!string;
5347    auto myFile = tempDir.buildPath(id ~ "my_tmp_file");
5348    scope(exit) myFile.remove;
5349
5350    myFile.write("hello");
5351    assert(myFile.readText == "hello");
5352}
5353
5354@safe unittest
5355{
5356    import std.algorithm.searching : endsWith;
5357    import std.path : dirSeparator;
5358    assert(tempDir.endsWith(dirSeparator));
5359
5360    // https://issues.dlang.org/show_bug.cgi?id=22738
5361    assert(!tempDir.endsWith(dirSeparator ~ dirSeparator));
5362}
5363
5364/**
5365Returns the available disk space based on a given path.
5366On Windows, `path` must be a directory; on POSIX systems, it can be a file or directory.
5367
5368Params:
5369    path = on Windows, it must be a directory; on POSIX it can be a file or directory
5370Returns:
5371    Available space in bytes
5372
5373Throws:
5374    $(LREF FileException) in case of failure
5375*/
5376ulong getAvailableDiskSpace(scope const(char)[] path) @safe
5377{
5378    version (Windows)
5379    {
5380        import core.sys.windows.winbase : GetDiskFreeSpaceExW;
5381        import core.sys.windows.winnt : ULARGE_INTEGER;
5382        import std.internal.cstring : tempCStringW;
5383
5384        ULARGE_INTEGER freeBytesAvailable;
5385        auto err = () @trusted {
5386            return GetDiskFreeSpaceExW(path.tempCStringW(), &freeBytesAvailable, null, null);
5387        } ();
5388        cenforce(err != 0, "Cannot get available disk space");
5389
5390        return freeBytesAvailable.QuadPart;
5391    }
5392    else version (Posix)
5393    {
5394        import std.internal.cstring : tempCString;
5395
5396        version (FreeBSD)
5397        {
5398            import core.sys.freebsd.sys.mount : statfs, statfs_t;
5399
5400            statfs_t stats;
5401            auto err = () @trusted {
5402                return statfs(path.tempCString(), &stats);
5403            } ();
5404            cenforce(err == 0, "Cannot get available disk space");
5405
5406            return stats.f_bavail * stats.f_bsize;
5407        }
5408        else
5409        {
5410            import core.sys.posix.sys.statvfs : statvfs, statvfs_t;
5411
5412            statvfs_t stats;
5413            auto err = () @trusted {
5414                return statvfs(path.tempCString(), &stats);
5415            } ();
5416            cenforce(err == 0, "Cannot get available disk space");
5417
5418            return stats.f_bavail * stats.f_frsize;
5419        }
5420    }
5421    else static assert(0, "Unsupported platform");
5422}
5423
5424///
5425@safe unittest
5426{
5427    import std.exception : assertThrown;
5428
5429    auto space = getAvailableDiskSpace(".");
5430    assert(space > 0);
5431
5432    assertThrown!FileException(getAvailableDiskSpace("ThisFileDoesNotExist123123"));
5433}
5434