xref: /netbsd-src/external/gpl3/gcc/dist/libphobos/src/std/file.d (revision b1e838363e3c6fc78a55519254d99869742dd33c)
1 // Written in the D programming language.
2 
3 /**
4 Utilities for manipulating files and scanning directories. Functions
5 in this module handle files as a unit, e.g., read or write one file
6 at a time. For opening files and manipulating them via handles refer
7 to 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 
71 Copyright: Copyright The D Language Foundation 2007 - 2011.
72 See_Also:  The $(HTTP ddili.org/ders/d.en/files.html, official tutorial) for an
73 introduction to working with files in D, module
74 $(MREF std, stdio) for opening files and manipulating them via handles,
75 and module $(MREF std, path) for manipulating path strings.
76 
77 License:   $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
78 Authors:   $(HTTP digitalmars.com, Walter Bright),
79            $(HTTP erdani.org, Andrei Alexandrescu),
80            $(HTTP jmdavisprog.com, Jonathan M Davis)
81 Source:    $(PHOBOSSRC std/file.d)
82  */
83 module std.file;
84 
85 import core.stdc.errno, core.stdc.stdlib, core.stdc.string;
86 import core.time : abs, dur, hnsecs, seconds;
87 
88 import std.datetime.date : DateTime;
89 import std.datetime.systime : Clock, SysTime, unixTimeToStdTime;
90 import std.internal.cstring;
91 import std.meta;
92 import std.range;
93 import std.traits;
94 import std.typecons;
95 
96 version (OSX)
97     version = Darwin;
98 else version (iOS)
99     version = Darwin;
100 else version (TVOS)
101     version = Darwin;
102 else version (WatchOS)
103     version = Darwin;
104 
version(Windows)105 version (Windows)
106 {
107     import core.sys.windows.winbase, core.sys.windows.winnt, std.windows.syserror;
108 }
version(Posix)109 else 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 }
114 else
115     static assert(false, "Module " ~ .stringof ~ " not implemented for this OS.");
116 
117 // Character type used for operating system filesystem APIs
version(Windows)118 version (Windows)
119 {
120     private alias FSChar = WCHAR;       // WCHAR can be aliased to wchar or wchar_t
121 }
version(Posix)122 else version (Posix)
123 {
124     private alias FSChar = char;
125 }
126 else
127     static assert(0);
128 
129 // Purposefully not documented. Use at your own risk
deleteme()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 
144 version (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 
version(Android)152 version (Android)
153 {
154     package enum system_directory = "/system/etc";
155     package enum system_file      = "/system/etc/hosts";
156 }
version(Posix)157 else 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  +/
167 class FileException : Exception
168 {
169     import std.conv : text, to;
170 
171     /++
172         OS error code.
173      +/
174     immutable uint errno;
175 
this(scope const (char)[]name,scope const (char)[]msg,string file,size_t line,uint errno)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 
cenforce(T)237 private 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 
version(Windows)251 version (Windows)
252 @trusted
253 private 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 
version(Posix)269 version (Posix)
270 @trusted
271 private 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     }
catch(FileException)294     catch (FileException) {}
295 }
296 
297 /* **********************************
298  * Basic File operations.
299  */
300 
301 /********************************************
302 Read entire contents of file `name` and returns it as an untyped
303 array. If the file size is larger than `upTo`, only `upTo`
304 bytes are _read.
305 
306 Params:
307     name = string or range of characters representing the file _name
308     upTo = if present, the maximum number of bytes to _read
309 
310 Returns: Untyped array of bytes _read.
311 
312 Throws: $(LREF FileException) on error.
313  */
314 
315 void[] read(R)(R name, size_t upTo = size_t.max)
316 if (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;
scope(exit)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
341 void[] read(R)(auto ref R name, size_t upTo = size_t.max)
342 if (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 
version(Posix)352 version (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 
402 version (Windows)
private(Windows)403 private 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 
version(Windows)414 version (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 
version(linux)468 version (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 +/
501 S readText(S = string, R)(auto ref R name)
502 if (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 
trustedCast(T)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));
version(BigEndian)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));
version(BigEndian)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 /*********************************************
725 Write `buffer` to file `name`.
726 
727 Creates the file if it does not already exist.
728 
729 Params:
730     name = string or range of characters representing the file _name
731     buffer = data to be written to file
732 
733 Throws: $(LREF FileException) on error.
734 
735 See_also: $(REF toFile, std,stdio)
736  */
737 void write(R)(R name, const void[] buffer)
738 if ((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 {
scope(exit)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
763 void write(R)(auto ref R name, const void[] buffer)
764 if (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 /*********************************************
775 Appends `buffer` to file `name`.
776 
777 Creates the file if it does not already exist.
778 
779 Params:
780     name = string or range of characters representing the file _name
781     buffer = data to be appended to file
782 
783 Throws: $(LREF FileException) on error.
784  */
785 void append(R)(R name, const void[] buffer)
786 if ((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 {
scope(exit)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
813 void append(R)(auto ref R name, const void[] buffer)
814 if (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 
version(Posix)826 version (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 
version(Windows)857 version (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  */
914 void rename(RF, RT)(RF from, RT to)
915 if ((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
936 void rename(RF, RT)(auto ref RF from, auto ref RT to)
937 if (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 
renameImpl(scope const (char)[]f,scope const (char)[]t,scope const (FSChar)* fromz,scope const (FSChar)* toz)968 private 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 /***************************************************
1017 Delete file `name`.
1018 
1019 Params:
1020     name = string or range of characters representing the file _name
1021 
1022 Throws: $(LREF FileException) on error.
1023  */
1024 void remove(R)(R name)
1025 if (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
1034 void remove(R)(auto ref R name)
1035 if (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 
removeImpl(scope const (char)[]name,scope const (FSChar)* namez)1057 private 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 
1078 version (Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(R)(R name)
1079 if (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     {
getFA(scope const (char)[]name,scope const (FSChar)* namez,out WIN32_FILE_ATTRIBUTE_DATA fad)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     {
getFA(scope const (FSChar)* namez,out WIN32_FILE_ATTRIBUTE_DATA fad)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 
version(Windows)1112 version (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 
1120 version (Posix) private extern (C) pragma(mangle, stat.mangleof)
1121 int trustedStat(scope const(FSChar)* namez, ref stat_t buf) @nogc nothrow @trusted;
1122 
1123 /**
1124 Get size of file `name` in bytes.
1125 
1126 Params:
1127     name = string or range of characters representing the file _name
1128 Returns:
1129     The size of file in bytes.
1130 Throws:
1131     $(LREF FileException) on error (e.g., file not found).
1132  */
1133 ulong getSize(R)(R name)
1134 if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1135 {
version(Windows)1136     version (Windows)
1137     {
1138         with (getFileAttributesWin(name))
1139             return makeUlong(nFileSizeLow, nFileSizeHigh);
1140     }
version(Posix)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
1156 ulong getSize(R)(auto ref R name)
1157 if (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.
version(Posix)1194 version (Posix)
1195 private 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  +/
1226 void getTimes(R)(R name,
1227               out SysTime accessTime,
1228               out SysTime modificationTime)
1229 if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1230 {
version(Windows)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     }
version(Posix)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
1259 void getTimes(R)(auto ref R name,
1260               out SysTime accessTime,
1261               out SysTime modificationTime)
1262 if (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 
version(fullFileTests)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 
version(StdDdoc)1350 version (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 }
1377 else 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 
1405 version (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 
version(Darwin)1475 version (Darwin)
1476 private
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  +/
1501 void setTimes(R)(R name,
1502               SysTime accessTime,
1503               SysTime modificationTime)
1504 if (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
1534 void setTimes(R)(auto ref R name,
1535               SysTime accessTime,
1536               SysTime modificationTime)
1537 if (isConvertibleToString!R)
1538 {
1539     setTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
1540 }
1541 
setTimesImpl(scope const (char)[]names,scope const (FSChar)* namez,SysTime accessTime,SysTime modificationTime)1542 private 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 
testTimes(int hnsecValue)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 +/
1650 SysTime timeLastModified(R)(R name)
1651 if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1652 {
version(Windows)1653     version (Windows)
1654     {
1655         SysTime dummy;
1656         SysTime ftm;
1657 
1658         getTimesWin(name, dummy, dummy, ftm);
1659 
1660         return ftm;
1661     }
version(Posix)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
1678 SysTime timeLastModified(R)(auto ref R name)
1679 if (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 
1722 Example:
1723 --------------------
1724 if (source.timeLastModified >= target.timeLastModified(SysTime.min))
1725 {
1726     // must (re)build
1727 }
1728 else
1729 {
1730     // target is up-to-date
1731 }
1732 --------------------
1733 +/
1734 SysTime timeLastModified(R)(R name, SysTime returnIfMissing)
1735 if (isSomeFiniteCharInputRange!R)
1736 {
version(Windows)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     }
version(Posix)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 
version(StdDdoc)1777 version (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 }
version(Posix)1804 else 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 
scope(exit)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
version(FreeBSD)1864 version (FreeBSD) {} else
version(DragonFlyBSD)1865 version (DragonFlyBSD) {} else
version(OSX)1866 version (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  */
1894 bool exists(R)(R name)
1895 if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1896 {
1897     return existsImpl(name.tempCString!FSChar());
1898 }
1899 
1900 /// ditto
1901 bool exists(R)(auto ref R name)
1902 if (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 
existsImpl(scope const (FSChar)* namez)1920 private 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   +/
1995 uint getAttributes(R)(R name)
1996 if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
1997 {
version(Windows)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     }
version(Posix)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
2031 uint getAttributes(R)(auto ref R name)
2032 if (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  +/
2095 uint getLinkAttributes(R)(R name)
2096 if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
2097 {
version(Windows)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
2120 uint getLinkAttributes(R)(auto ref R name)
2121 if (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  +/
2203 void setAttributes(R)(R name, uint attributes)
2204 if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
2205 {
version(Windows)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
2236 void setAttributes(R)(auto ref R name, uint attributes)
2237 if (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)
2312 if (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)
2326 if (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 +/
2400 bool 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)
2491 if (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)
2501 if (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 {
version(Windows)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     }
version(Posix)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 
2578 Example:
2579 --------------------
2580 assert(attrIsFile(getAttributes("/etc/fonts/fonts.conf")));
2581 assert(attrIsFile(getLinkAttributes("/etc/fonts/fonts.conf")));
2582 --------------------
2583   +/
attrIsFile(uint attributes)2584 bool 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)
2666 if (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)
2676 if (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 {
version(Windows)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     }
version(Posix)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 
2796 Example:
2797 --------------------
2798 core.sys.posix.unistd.symlink("/etc/fonts/fonts.conf", "/tmp/alink");
2799 
2800 assert(!getAttributes("/tmp/alink").isSymlink);
2801 assert(getLinkAttributes("/tmp/alink").isSymlink);
2802 --------------------
2803   +/
attrIsSymlink(uint attributes)2804 bool 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
version(Posix)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 /**
2837 Change directory to `pathname`. Equivalent to `cd` on
2838 Windows and POSIX.
2839 
2840 Params:
2841     pathname = the directory to step into
2842 
2843 Throws: $(LREF FileException) on error.
2844  */
2845 void chdir(R)(R pathname)
2846 if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
2847 {
2848     // Place outside of @trusted block
2849     auto pathz = pathname.tempCString!FSChar();
2850 
version(Windows)2851     version (Windows)
2852     {
2853         static auto trustedChdir(scope const(FSChar)* pathz) @trusted
2854         {
2855             return SetCurrentDirectoryW(pathz);
2856         }
2857     }
version(Posix)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
2873 void chdir(R)(auto ref R pathname)
2874 if (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 /**
2906 Make a new directory `pathname`.
2907 
2908 Params:
2909     pathname = the path of the directory to make
2910 
2911 Throws:
2912     $(LREF FileException) on POSIX or $(LREF WindowsException) on Windows
2913     if an error occured.
2914  */
2915 void mkdir(R)(R pathname)
2916 if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
2917 {
2918     // Place outside of @trusted block
2919     const pathz = pathname.tempCString!FSChar();
2920 
version(Windows)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     }
version(Posix)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
2950 void mkdir(R)(auto ref R pathname)
2951 if (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.
ensureDirExists()2984 private 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 /**
3008 Make directory and all parent directories as needed.
3009 
3010 Does nothing if the directory specified by
3011 `pathname` already exists.
3012 
3013 Params:
3014     pathname = the full path of the directory to create
3015 
3016 Throws: $(LREF FileException) on error.
3017  */
mkdirRecurse(scope const (char)[]pathname)3018 void 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";
scope(exit)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 
version(Windows)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";
version(Windows)3094         version (Windows)
3095         {
3096             immutable path = basepath ~ "\\fake\\here\\";
3097         }
version(Posix)3098         else version (Posix)
3099         {
3100             immutable path = basepath ~ `/fake/here/`;
3101         }
3102 
3103         mkdirRecurse(path);
3104         assert(basepath.exists && basepath.isDir);
scope(exit)3105         scope(exit) () @trusted { rmdirRecurse(basepath); }();
3106         assert(path.exists && path.isDir);
3107     }
3108 }
3109 
3110 /****************************************************
3111 Remove directory `pathname`.
3112 
3113 Params:
3114     pathname = Range or string specifying the directory name
3115 
3116 Throws: $(LREF FileException) on error.
3117  */
3118 void rmdir(R)(R pathname)
3119 if (isSomeFiniteCharInputRange!R && !isConvertibleToString!R)
3120 {
3121     // Place outside of @trusted block
3122     auto pathz = pathname.tempCString!FSChar();
3123 
version(Windows)3124     version (Windows)
3125     {
3126         static auto trustedRmdir(scope const(FSChar)* pathz) @trusted
3127         {
3128             return RemoveDirectoryW(pathz);
3129         }
3130     }
version(Posix)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
3146 void rmdir(R)(auto ref R pathname)
3147 if (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   +/
3184 version (StdDdoc) void symlink(RO, RL)(RO original, RL link)
3185 if ((isSomeFiniteCharInputRange!RO || isConvertibleToString!RO) &&
3186     (isSomeFiniteCharInputRange!RL || isConvertibleToString!RL));
3187 else version (Posix) void symlink(RO, RL)(RO original, RL link)
3188 if ((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 
version(Posix)3208 version (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 
3251 version (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   +/
3269 version (StdDdoc) string readLink(R)(R link)
3270 if (isSomeFiniteCharInputRange!R || isConvertibleToString!R);
3271 else version (Posix) string readLink(R)(R link)
3272 if (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 
version(Posix)3320 version (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 
version(Posix)3340 version (Posix) @safe unittest
3341 {
3342     static assert(__traits(compiles, readLink(TestAliasedString("foo"))));
3343 }
3344 
version(Posix)3345 version (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  */
version(Windows)3366 version (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 }
version(Solaris)3399 else 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 }
version(Posix)3409 else 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  */
thisExePath()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 
version(StdDdoc)3596 version (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 
3627 Example:
3628 --------------------
3629 auto de1 = DirEntry("/etc/fonts/fonts.conf");
3630 assert(de1.name == "/etc/fonts/fonts.conf");
3631 
3632 auto de2 = DirEntry("/usr/share/include");
3633 assert(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 
3643 Example:
3644 --------------------
3645 auto de1 = DirEntry("/etc/fonts/fonts.conf");
3646 assert(!de1.isDir);
3647 
3648 auto de2 = DirEntry("/usr/share/include");
3649 assert(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 
3669 Example:
3670 --------------------
3671 auto de1 = DirEntry("/etc/fonts/fonts.conf");
3672 assert(de1.isFile);
3673 
3674 auto de2 = DirEntry("/usr/share/include");
3675 assert(!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 }
3767 else 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 }
3876 else 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 
4167 alias PreserveAttributes = Flag!"preserveAttributes";
4168 
4169 version (StdDdoc)
4170 {
4171     /// Defaults to `Yes.preserveAttributes` on Windows, and the opposite on all other platforms.
4172     PreserveAttributes preserveAttributesDefault;
4173 }
4174 else version (Windows)
4175 {
4176     enum preserveAttributesDefault = Yes.preserveAttributes;
4177 }
4178 else
4179 {
4180     enum preserveAttributesDefault = No.preserveAttributes;
4181 }
4182 
4183 /***************************************************
4184 Copy file `from` _to file `to`. File timestamps are preserved.
4185 File attributes are preserved, if `preserve` equals `Yes.preserveAttributes`.
4186 On Windows only `Yes.preserveAttributes` (the default on Windows) is supported.
4187 If the target file exists, it is overwritten.
4188 
4189 Params:
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 
4194 Throws: $(LREF FileException) on error.
4195  */
4196 void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault)
4197 if (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
4218 void copy(RF, RT)(auto ref RF from, auto ref RT to, PreserveAttributes preserve = preserveAttributesDefault)
4219 if (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 
4253 private 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
4395 version (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  +/
4419 void 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
4427 void 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.
4462 void 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 
4482 version (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 
4491 version (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  */
4540 enum 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 
4594 private 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 
4841 struct DirIterator
4842 {
4843 @safe:
4844 private:
4845     RefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl;
4846     this(string pathname, SpanMode mode, bool followSymlink) @trusted
4847     {
4848         impl = typeof(impl)(pathname, mode, followSymlink);
4849     }
4850 public:
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 
4893 Example:
4894 --------------------
4895 // Iterate a directory in depth
4896 foreach (string name; dirEntries("destroy/me", SpanMode.depth))
4897 {
4898     remove(name);
4899 }
4900 
4901 // Iterate the current directory in breadth
4902 foreach (string name; dirEntries("", SpanMode.breadth))
4903 {
4904     writeln(name);
4905 }
4906 
4907 // Iterate a directory and get detailed info about it
4908 foreach (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
4914 auto dFiles = dirEntries("", SpanMode.depth).filter!(f => f.name.endsWith(".d"));
4915 foreach (d; dFiles)
4916     writeln(d.name);
4917 
4918 // Hook it up with std.parallelism to compile them all in parallel:
4919 foreach (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
4928 auto dFiles = dirEntries("","*.{d,di}",SpanMode.depth);
4929 foreach (d; dFiles)
4930     writeln(d.name);
4931 --------------------
4932  +/
4933 auto 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
5035 auto 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  */
5178 Select!(Types.length == 1, Types[0][], Tuple!(Types)[])
5179 slurp(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 /**
5238 Returns the path to a directory for temporary files.
5239 On POSIX platforms, it searches through the following list of directories
5240 and 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 
5250 On all platforms, `tempDir` returns the current working directory on failure.
5251 
5252 The return value of the function is cached, so the procedures described
5253 below will only be performed the first time the function is called.  All
5254 subsequent runs will return the same string, regardless of whether
5255 environment variables and directory structures have changed in the
5256 meantime.
5257 
5258 The POSIX `tempDir` algorithm is inspired by Python's
5259 $(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, `tempfile.tempdir`).
5260 
5261 Returns:
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 */
5279 string 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 /**
5365 Returns the available disk space based on a given path.
5366 On Windows, `path` must be a directory; on POSIX systems, it can be a file or directory.
5367 
5368 Params:
5369     path = on Windows, it must be a directory; on POSIX it can be a file or directory
5370 Returns:
5371     Available space in bytes
5372 
5373 Throws:
5374     $(LREF FileException) in case of failure
5375 */
5376 ulong 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