]> git.ipfire.org Git - thirdparty/gcc.git/blob - libphobos/src/std/file.d
libphobos: Merge phobos and druntime with upstream.
[thirdparty/gcc.git] / libphobos / src / std / file.d
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 $(BOOKTABLE,
11 $(TR $(TH Category) $(TH Functions))
12 $(TR $(TD General) $(TD
13 $(LREF exists)
14 $(LREF isDir)
15 $(LREF isFile)
16 $(LREF isSymlink)
17 $(LREF rename)
18 $(LREF thisExePath)
19 ))
20 $(TR $(TD Directories) $(TD
21 $(LREF chdir)
22 $(LREF dirEntries)
23 $(LREF getcwd)
24 $(LREF mkdir)
25 $(LREF mkdirRecurse)
26 $(LREF rmdir)
27 $(LREF rmdirRecurse)
28 $(LREF tempDir)
29 ))
30 $(TR $(TD Files) $(TD
31 $(LREF append)
32 $(LREF copy)
33 $(LREF read)
34 $(LREF readText)
35 $(LREF remove)
36 $(LREF slurp)
37 $(LREF write)
38 ))
39 $(TR $(TD Symlinks) $(TD
40 $(LREF symlink)
41 $(LREF readLink)
42 ))
43 $(TR $(TD Attributes) $(TD
44 $(LREF attrIsDir)
45 $(LREF attrIsFile)
46 $(LREF attrIsSymlink)
47 $(LREF getAttributes)
48 $(LREF getLinkAttributes)
49 $(LREF getSize)
50 $(LREF setAttributes)
51 ))
52 $(TR $(TD Timestamp) $(TD
53 $(LREF getTimes)
54 $(LREF getTimesWin)
55 $(LREF setTimes)
56 $(LREF timeLastModified)
57 ))
58 $(TR $(TD Other) $(TD
59 $(LREF DirEntry)
60 $(LREF FileException)
61 $(LREF PreserveAttributes)
62 $(LREF SpanMode)
63 ))
64 )
65
66
67 Copyright: Copyright Digital Mars 2007 - 2011.
68 See_Also: The $(HTTP ddili.org/ders/d.en/files.html, official tutorial) for an
69 introduction to working with files in D, module
70 $(MREF std, stdio) for opening files and manipulating them via handles,
71 and module $(MREF std, path) for manipulating path strings.
72
73 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
74 Authors: $(HTTP digitalmars.com, Walter Bright),
75 $(HTTP erdani.org, Andrei Alexandrescu),
76 Jonathan M Davis
77 Source: $(PHOBOSSRC std/_file.d)
78 */
79 module std.file;
80
81 import core.stdc.errno, core.stdc.stdlib, core.stdc.string;
82 import core.time : abs, dur, hnsecs, seconds;
83
84 import std.datetime.date : DateTime;
85 import std.datetime.systime : Clock, SysTime, unixTimeToStdTime;
86 import std.internal.cstring;
87 import std.meta;
88 import std.range.primitives;
89 import std.traits;
90 import std.typecons;
91
92 version (Windows)
93 {
94 import core.sys.windows.windows, std.windows.syserror;
95 }
96 else version (Posix)
97 {
98 import core.sys.posix.dirent, core.sys.posix.fcntl, core.sys.posix.sys.stat,
99 core.sys.posix.sys.time, core.sys.posix.unistd, core.sys.posix.utime;
100 }
101 else
102 static assert(false, "Module " ~ .stringof ~ " not implemented for this OS.");
103
104 // Character type used for operating system filesystem APIs
105 version (Windows)
106 {
107 private alias FSChar = wchar;
108 }
109 else version (Posix)
110 {
111 private alias FSChar = char;
112 }
113 else
114 static assert(0);
115
116 // Purposefully not documented. Use at your own risk
117 @property string deleteme() @safe
118 {
119 import std.conv : to;
120 import std.path : buildPath;
121 import std.process : thisProcessID;
122
123 static _deleteme = "deleteme.dmd.unittest.pid";
124 static _first = true;
125
126 if (_first)
127 {
128 _deleteme = buildPath(tempDir(), _deleteme) ~ to!string(thisProcessID);
129 _first = false;
130 }
131
132 return _deleteme;
133 }
134
135 version (unittest) private struct TestAliasedString
136 {
137 string get() @safe @nogc pure nothrow { return _s; }
138 alias get this;
139 @disable this(this);
140 string _s;
141 }
142
143 version (Android)
144 {
145 package enum system_directory = "/system/etc";
146 package enum system_file = "/system/etc/hosts";
147 }
148 else version (Posix)
149 {
150 package enum system_directory = "/usr/include";
151 package enum system_file = "/usr/include/assert.h";
152 }
153
154
155 /++
156 Exception thrown for file I/O errors.
157 +/
158 class FileException : Exception
159 {
160 import std.conv : text, to;
161
162 /++
163 OS error code.
164 +/
165 immutable uint errno;
166
167 /++
168 Constructor which takes an error message.
169
170 Params:
171 name = Name of file for which the error occurred.
172 msg = Message describing the error.
173 file = The _file where the error occurred.
174 line = The _line where the error occurred.
175 +/
176 this(in char[] name, in char[] msg, string file = __FILE__, size_t line = __LINE__) @safe pure
177 {
178 if (msg.empty)
179 super(name.idup, file, line);
180 else
181 super(text(name, ": ", msg), file, line);
182
183 errno = 0;
184 }
185
186 /++
187 Constructor which takes the error number ($(LUCKY GetLastError)
188 in Windows, $(D_PARAM errno) in Posix).
189
190 Params:
191 name = Name of file for which the error occurred.
192 errno = The error number.
193 file = The _file where the error occurred.
194 Defaults to $(D __FILE__).
195 line = The _line where the error occurred.
196 Defaults to $(D __LINE__).
197 +/
198 version (Windows) this(in char[] name,
199 uint errno = .GetLastError(),
200 string file = __FILE__,
201 size_t line = __LINE__) @safe
202 {
203 this(name, sysErrorString(errno), file, line);
204 this.errno = errno;
205 }
206 else version (Posix) this(in char[] name,
207 uint errno = .errno,
208 string file = __FILE__,
209 size_t line = __LINE__) @trusted
210 {
211 import std.exception : errnoString;
212 this(name, errnoString(errno), file, line);
213 this.errno = errno;
214 }
215 }
216
217 private T cenforce(T)(T condition, lazy const(char)[] name, string file = __FILE__, size_t line = __LINE__)
218 {
219 if (condition)
220 return condition;
221 version (Windows)
222 {
223 throw new FileException(name, .GetLastError(), file, line);
224 }
225 else version (Posix)
226 {
227 throw new FileException(name, .errno, file, line);
228 }
229 }
230
231 version (Windows)
232 @trusted
233 private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez,
234 string file = __FILE__, size_t line = __LINE__)
235 {
236 if (condition)
237 return condition;
238 if (!name)
239 {
240 import core.stdc.wchar_ : wcslen;
241 import std.conv : to;
242
243 auto len = namez ? wcslen(namez) : 0;
244 name = to!string(namez[0 .. len]);
245 }
246 throw new FileException(name, .GetLastError(), file, line);
247 }
248
249 version (Posix)
250 @trusted
251 private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez,
252 string file = __FILE__, size_t line = __LINE__)
253 {
254 if (condition)
255 return condition;
256 if (!name)
257 {
258 import core.stdc.string : strlen;
259
260 auto len = namez ? strlen(namez) : 0;
261 name = namez[0 .. len].idup;
262 }
263 throw new FileException(name, .errno, file, line);
264 }
265
266 @safe unittest
267 {
268 // issue 17102
269 try
270 {
271 cenforce(false, null, null,
272 __FILE__, __LINE__);
273 }
274 catch (FileException) {}
275 }
276
277 /* **********************************
278 * Basic File operations.
279 */
280
281 /********************************************
282 Read entire contents of file $(D name) and returns it as an untyped
283 array. If the file size is larger than $(D upTo), only $(D upTo)
284 bytes are _read.
285
286 Params:
287 name = string or range of characters representing the file _name
288 upTo = if present, the maximum number of bytes to _read
289
290 Returns: Untyped array of bytes _read.
291
292 Throws: $(LREF FileException) on error.
293 */
294
295 void[] read(R)(R name, size_t upTo = size_t.max)
296 if (isInputRange!R && isSomeChar!(ElementEncodingType!R) && !isInfinite!R &&
297 !isConvertibleToString!R)
298 {
299 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
300 return readImpl(name, name.tempCString!FSChar(), upTo);
301 else
302 return readImpl(null, name.tempCString!FSChar(), upTo);
303 }
304
305 ///
306 @safe unittest
307 {
308 import std.utf : byChar;
309 scope(exit)
310 {
311 assert(exists(deleteme));
312 remove(deleteme);
313 }
314
315 write(deleteme, "1234"); // deleteme is the name of a temporary file
316 assert(read(deleteme, 2) == "12");
317 assert(read(deleteme.byChar) == "1234");
318 assert((cast(const(ubyte)[])read(deleteme)).length == 4);
319 }
320
321 /// ditto
322 void[] read(R)(auto ref R name, size_t upTo = size_t.max)
323 if (isConvertibleToString!R)
324 {
325 return read!(StringTypeOf!R)(name, upTo);
326 }
327
328 @safe unittest
329 {
330 static assert(__traits(compiles, read(TestAliasedString(null))));
331 }
332
333 version (Posix) private void[] readImpl(const(char)[] name, const(FSChar)* namez, size_t upTo = size_t.max) @trusted
334 {
335 import core.memory : GC;
336 import std.algorithm.comparison : min;
337 import std.array : uninitializedArray;
338 import std.conv : to;
339
340 // A few internal configuration parameters {
341 enum size_t
342 minInitialAlloc = 1024 * 4,
343 maxInitialAlloc = size_t.max / 2,
344 sizeIncrement = 1024 * 16,
345 maxSlackMemoryAllowed = 1024;
346 // }
347
348 immutable fd = core.sys.posix.fcntl.open(namez,
349 core.sys.posix.fcntl.O_RDONLY);
350 cenforce(fd != -1, name);
351 scope(exit) core.sys.posix.unistd.close(fd);
352
353 stat_t statbuf = void;
354 cenforce(fstat(fd, &statbuf) == 0, name, namez);
355
356 immutable initialAlloc = min(upTo, to!size_t(statbuf.st_size
357 ? min(statbuf.st_size + 1, maxInitialAlloc)
358 : minInitialAlloc));
359 void[] result = uninitializedArray!(ubyte[])(initialAlloc);
360 scope(failure) GC.free(result.ptr);
361 size_t size = 0;
362
363 for (;;)
364 {
365 immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size,
366 min(result.length, upTo) - size);
367 cenforce(actual != -1, name, namez);
368 if (actual == 0) break;
369 size += actual;
370 if (size >= upTo) break;
371 if (size < result.length) continue;
372 immutable newAlloc = size + sizeIncrement;
373 result = GC.realloc(result.ptr, newAlloc, GC.BlkAttr.NO_SCAN)[0 .. newAlloc];
374 }
375
376 return result.length - size >= maxSlackMemoryAllowed
377 ? GC.realloc(result.ptr, size, GC.BlkAttr.NO_SCAN)[0 .. size]
378 : result[0 .. size];
379 }
380
381
382 version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* namez, size_t upTo = size_t.max) @safe
383 {
384 import core.memory : GC;
385 import std.algorithm.comparison : min;
386 import std.array : uninitializedArray;
387 static trustedCreateFileW(const(wchar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode,
388 SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition,
389 DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted
390 {
391 return CreateFileW(namez, dwDesiredAccess, dwShareMode,
392 lpSecurityAttributes, dwCreationDisposition,
393 dwFlagsAndAttributes, hTemplateFile);
394
395 }
396 static trustedCloseHandle(HANDLE hObject) @trusted
397 {
398 return CloseHandle(hObject);
399 }
400 static trustedGetFileSize(HANDLE hFile, out ulong fileSize) @trusted
401 {
402 DWORD sizeHigh;
403 DWORD sizeLow = GetFileSize(hFile, &sizeHigh);
404 const bool result = sizeLow != INVALID_FILE_SIZE;
405 if (result)
406 fileSize = makeUlong(sizeLow, sizeHigh);
407 return result;
408 }
409 static trustedReadFile(HANDLE hFile, void *lpBuffer, ulong nNumberOfBytesToRead) @trusted
410 {
411 // Read by chunks of size < 4GB (Windows API limit)
412 ulong totalNumRead = 0;
413 while (totalNumRead != nNumberOfBytesToRead)
414 {
415 const uint chunkSize = min(nNumberOfBytesToRead - totalNumRead, 0xffff_0000);
416 DWORD numRead = void;
417 const result = ReadFile(hFile, lpBuffer + totalNumRead, chunkSize, &numRead, null);
418 if (result == 0 || numRead != chunkSize)
419 return false;
420 totalNumRead += chunkSize;
421 }
422 return true;
423 }
424
425 alias defaults =
426 AliasSeq!(GENERIC_READ,
427 FILE_SHARE_READ | FILE_SHARE_WRITE, (SECURITY_ATTRIBUTES*).init,
428 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
429 HANDLE.init);
430 auto h = trustedCreateFileW(namez, defaults);
431
432 cenforce(h != INVALID_HANDLE_VALUE, name, namez);
433 scope(exit) cenforce(trustedCloseHandle(h), name, namez);
434 ulong fileSize = void;
435 cenforce(trustedGetFileSize(h, fileSize), name, namez);
436 size_t size = min(upTo, fileSize);
437 auto buf = uninitializedArray!(ubyte[])(size);
438
439 scope(failure)
440 {
441 () @trusted { GC.free(buf.ptr); } ();
442 }
443
444 if (size)
445 cenforce(trustedReadFile(h, &buf[0], size), name, namez);
446 return buf[0 .. size];
447 }
448
449 version (linux) @safe unittest
450 {
451 // A file with "zero" length that doesn't have 0 length at all
452 auto s = std.file.readText("/proc/sys/kernel/osrelease");
453 assert(s.length > 0);
454 //writefln("'%s'", s);
455 }
456
457 @safe unittest
458 {
459 scope(exit) if (exists(deleteme)) remove(deleteme);
460 import std.stdio;
461 auto f = File(deleteme, "w");
462 f.write("abcd"); f.flush();
463 assert(read(deleteme) == "abcd");
464 }
465
466 /********************************************
467 Read and validates (using $(REF validate, std,utf)) a text file. $(D S)
468 can be a type of array of characters of any width and constancy. No
469 width conversion is performed; if the width of the characters in file
470 $(D name) is different from the width of elements of $(D S),
471 validation will fail.
472
473 Params:
474 name = string or range of characters representing the file _name
475
476 Returns: Array of characters read.
477
478 Throws: $(D FileException) on file error, $(D UTFException) on UTF
479 decoding error.
480 */
481
482 S readText(S = string, R)(R name)
483 if (isSomeString!S &&
484 (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
485 !isConvertibleToString!R)
486 {
487 import std.utf : validate;
488 static auto trustedCast(void[] buf) @trusted { return cast(S) buf; }
489 auto result = trustedCast(read(name));
490 validate(result);
491 return result;
492 }
493
494 ///
495 @safe unittest
496 {
497 import std.exception : enforce;
498 write(deleteme, "abc"); // deleteme is the name of a temporary file
499 scope(exit) remove(deleteme);
500 string content = readText(deleteme);
501 enforce(content == "abc");
502 }
503
504 /// ditto
505 S readText(S = string, R)(auto ref R name)
506 if (isConvertibleToString!R)
507 {
508 return readText!(S, StringTypeOf!R)(name);
509 }
510
511 @safe unittest
512 {
513 static assert(__traits(compiles, readText(TestAliasedString(null))));
514 }
515
516 /*********************************************
517 Write $(D buffer) to file $(D name).
518
519 Creates the file if it does not already exist.
520
521 Params:
522 name = string or range of characters representing the file _name
523 buffer = data to be written to file
524
525 Throws: $(D FileException) on error.
526
527 See_also: $(REF toFile, std,stdio)
528 */
529 void write(R)(R name, const void[] buffer)
530 if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
531 !isConvertibleToString!R)
532 {
533 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
534 writeImpl(name, name.tempCString!FSChar(), buffer, false);
535 else
536 writeImpl(null, name.tempCString!FSChar(), buffer, false);
537 }
538
539 ///
540 @system unittest
541 {
542 scope(exit)
543 {
544 assert(exists(deleteme));
545 remove(deleteme);
546 }
547
548 int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
549 write(deleteme, a); // deleteme is the name of a temporary file
550 assert(cast(int[]) read(deleteme) == a);
551 }
552
553 /// ditto
554 void write(R)(auto ref R name, const void[] buffer)
555 if (isConvertibleToString!R)
556 {
557 write!(StringTypeOf!R)(name, buffer);
558 }
559
560 @safe unittest
561 {
562 static assert(__traits(compiles, write(TestAliasedString(null), null)));
563 }
564
565 /*********************************************
566 Appends $(D buffer) to file $(D name).
567
568 Creates the file if it does not already exist.
569
570 Params:
571 name = string or range of characters representing the file _name
572 buffer = data to be appended to file
573
574 Throws: $(D FileException) on error.
575 */
576 void append(R)(R name, const void[] buffer)
577 if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
578 !isConvertibleToString!R)
579 {
580 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
581 writeImpl(name, name.tempCString!FSChar(), buffer, true);
582 else
583 writeImpl(null, name.tempCString!FSChar(), buffer, true);
584 }
585
586 ///
587 @system unittest
588 {
589 scope(exit)
590 {
591 assert(exists(deleteme));
592 remove(deleteme);
593 }
594
595 int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
596 write(deleteme, a); // deleteme is the name of a temporary file
597 int[] b = [ 13, 21 ];
598 append(deleteme, b);
599 assert(cast(int[]) read(deleteme) == a ~ b);
600 }
601
602 /// ditto
603 void append(R)(auto ref R name, const void[] buffer)
604 if (isConvertibleToString!R)
605 {
606 append!(StringTypeOf!R)(name, buffer);
607 }
608
609 @safe unittest
610 {
611 static assert(__traits(compiles, append(TestAliasedString("foo"), [0, 1, 2, 3])));
612 }
613
614 // Posix implementation helper for write and append
615
616 version (Posix) private void writeImpl(const(char)[] name, const(FSChar)* namez,
617 in void[] buffer, bool append) @trusted
618 {
619 import std.conv : octal;
620
621 // append or write
622 auto mode = append ? O_CREAT | O_WRONLY | O_APPEND
623 : O_CREAT | O_WRONLY | O_TRUNC;
624
625 immutable fd = core.sys.posix.fcntl.open(namez, mode, octal!666);
626 cenforce(fd != -1, name, namez);
627 {
628 scope(failure) core.sys.posix.unistd.close(fd);
629
630 immutable size = buffer.length;
631 size_t sum, cnt = void;
632 while (sum != size)
633 {
634 cnt = (size - sum < 2^^30) ? (size - sum) : 2^^30;
635 const numwritten = core.sys.posix.unistd.write(fd, buffer.ptr + sum, cnt);
636 if (numwritten != cnt)
637 break;
638 sum += numwritten;
639 }
640 cenforce(sum == size, name, namez);
641 }
642 cenforce(core.sys.posix.unistd.close(fd) == 0, name, namez);
643 }
644
645 // Windows implementation helper for write and append
646
647 version (Windows) private void writeImpl(const(char)[] name, const(FSChar)* namez,
648 in void[] buffer, bool append) @trusted
649 {
650 HANDLE h;
651 if (append)
652 {
653 alias defaults =
654 AliasSeq!(GENERIC_WRITE, 0, null, OPEN_ALWAYS,
655 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
656 HANDLE.init);
657
658 h = CreateFileW(namez, defaults);
659 cenforce(h != INVALID_HANDLE_VALUE, name, namez);
660 cenforce(SetFilePointer(h, 0, null, FILE_END) != INVALID_SET_FILE_POINTER,
661 name, namez);
662 }
663 else // write
664 {
665 alias defaults =
666 AliasSeq!(GENERIC_WRITE, 0, null, CREATE_ALWAYS,
667 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
668 HANDLE.init);
669
670 h = CreateFileW(namez, defaults);
671 cenforce(h != INVALID_HANDLE_VALUE, name, namez);
672 }
673 immutable size = buffer.length;
674 size_t sum, cnt = void;
675 DWORD numwritten = void;
676 while (sum != size)
677 {
678 cnt = (size - sum < 2^^30) ? (size - sum) : 2^^30;
679 WriteFile(h, buffer.ptr + sum, cast(uint) cnt, &numwritten, null);
680 if (numwritten != cnt)
681 break;
682 sum += numwritten;
683 }
684 cenforce(sum == size && CloseHandle(h), name, namez);
685 }
686
687 /***************************************************
688 * Rename file $(D from) _to $(D to).
689 * If the target file exists, it is overwritten.
690 * Params:
691 * from = string or range of characters representing the existing file name
692 * to = string or range of characters representing the target file name
693 * Throws: $(D FileException) on error.
694 */
695 void rename(RF, RT)(RF from, RT to)
696 if ((isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) || isSomeString!RF)
697 && !isConvertibleToString!RF &&
698 (isInputRange!RT && !isInfinite!RT && isSomeChar!(ElementEncodingType!RT) || isSomeString!RT)
699 && !isConvertibleToString!RT)
700 {
701 // Place outside of @trusted block
702 auto fromz = from.tempCString!FSChar();
703 auto toz = to.tempCString!FSChar();
704
705 static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char))
706 alias f = from;
707 else
708 enum string f = null;
709
710 static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char))
711 alias t = to;
712 else
713 enum string t = null;
714
715 renameImpl(f, t, fromz, toz);
716 }
717
718 /// ditto
719 void rename(RF, RT)(auto ref RF from, auto ref RT to)
720 if (isConvertibleToString!RF || isConvertibleToString!RT)
721 {
722 import std.meta : staticMap;
723 alias Types = staticMap!(convertToString, RF, RT);
724 rename!Types(from, to);
725 }
726
727 @safe unittest
728 {
729 static assert(__traits(compiles, rename(TestAliasedString(null), TestAliasedString(null))));
730 static assert(__traits(compiles, rename("", TestAliasedString(null))));
731 static assert(__traits(compiles, rename(TestAliasedString(null), "")));
732 import std.utf : byChar;
733 static assert(__traits(compiles, rename(TestAliasedString(null), "".byChar)));
734 }
735
736 private void renameImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz) @trusted
737 {
738 version (Windows)
739 {
740 import std.exception : enforce;
741
742 const result = MoveFileExW(fromz, toz, MOVEFILE_REPLACE_EXISTING);
743 if (!result)
744 {
745 import core.stdc.wchar_ : wcslen;
746 import std.conv : to, text;
747
748 if (!f)
749 f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]);
750
751 if (!t)
752 t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
753
754 enforce(false,
755 new FileException(
756 text("Attempting to rename file ", f, " to ", t)));
757 }
758 }
759 else version (Posix)
760 {
761 static import core.stdc.stdio;
762
763 cenforce(core.stdc.stdio.rename(fromz, toz) == 0, t, toz);
764 }
765 }
766
767 @safe unittest
768 {
769 import std.utf : byWchar;
770
771 auto t1 = deleteme, t2 = deleteme~"2";
772 scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
773 write(t1, "1");
774 rename(t1, t2);
775 assert(readText(t2) == "1");
776 write(t1, "2");
777 rename(t1, t2.byWchar);
778 assert(readText(t2) == "2");
779 }
780
781
782 /***************************************************
783 Delete file $(D name).
784
785 Params:
786 name = string or range of characters representing the file _name
787
788 Throws: $(D FileException) on error.
789 */
790 void remove(R)(R name)
791 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
792 !isConvertibleToString!R)
793 {
794 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
795 removeImpl(name, name.tempCString!FSChar());
796 else
797 removeImpl(null, name.tempCString!FSChar());
798 }
799
800 /// ditto
801 void remove(R)(auto ref R name)
802 if (isConvertibleToString!R)
803 {
804 remove!(StringTypeOf!R)(name);
805 }
806
807 @safe unittest
808 {
809 static assert(__traits(compiles, remove(TestAliasedString("foo"))));
810 }
811
812 private void removeImpl(const(char)[] name, const(FSChar)* namez) @trusted
813 {
814 version (Windows)
815 {
816 cenforce(DeleteFileW(namez), name, namez);
817 }
818 else version (Posix)
819 {
820 static import core.stdc.stdio;
821
822 if (!name)
823 {
824 import core.stdc.string : strlen;
825 auto len = strlen(namez);
826 name = namez[0 .. len];
827 }
828 cenforce(core.stdc.stdio.remove(namez) == 0,
829 "Failed to remove file " ~ name);
830 }
831 }
832
833 version (Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(R)(R name)
834 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R))
835 {
836 auto namez = name.tempCString!FSChar();
837
838 WIN32_FILE_ATTRIBUTE_DATA fad = void;
839
840 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
841 {
842 static void getFA(const(char)[] name, const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
843 {
844 import std.exception : enforce;
845 enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad),
846 new FileException(name.idup));
847 }
848 getFA(name, namez, fad);
849 }
850 else
851 {
852 static void getFA(const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
853 {
854 import core.stdc.wchar_ : wcslen;
855 import std.conv : to;
856 import std.exception : enforce;
857
858 enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad),
859 new FileException(namez[0 .. wcslen(namez)].to!string));
860 }
861 getFA(namez, fad);
862 }
863 return fad;
864 }
865
866 version (Windows) private ulong makeUlong(DWORD dwLow, DWORD dwHigh) @safe pure nothrow @nogc
867 {
868 ULARGE_INTEGER li;
869 li.LowPart = dwLow;
870 li.HighPart = dwHigh;
871 return li.QuadPart;
872 }
873
874 /***************************************************
875 Get size of file $(D name) in bytes.
876
877 Params:
878 name = string or range of characters representing the file _name
879
880 Throws: $(D FileException) on error (e.g., file not found).
881 */
882 ulong getSize(R)(R name)
883 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
884 !isConvertibleToString!R)
885 {
886 version (Windows)
887 {
888 with (getFileAttributesWin(name))
889 return makeUlong(nFileSizeLow, nFileSizeHigh);
890 }
891 else version (Posix)
892 {
893 auto namez = name.tempCString();
894
895 static trustedStat(const(FSChar)* namez, out stat_t buf) @trusted
896 {
897 return stat(namez, &buf);
898 }
899 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
900 alias names = name;
901 else
902 string names = null;
903 stat_t statbuf = void;
904 cenforce(trustedStat(namez, statbuf) == 0, names, namez);
905 return statbuf.st_size;
906 }
907 }
908
909 /// ditto
910 ulong getSize(R)(auto ref R name)
911 if (isConvertibleToString!R)
912 {
913 return getSize!(StringTypeOf!R)(name);
914 }
915
916 @safe unittest
917 {
918 static assert(__traits(compiles, getSize(TestAliasedString("foo"))));
919 }
920
921 @safe unittest
922 {
923 // create a file of size 1
924 write(deleteme, "a");
925 scope(exit) { assert(exists(deleteme)); remove(deleteme); }
926 assert(getSize(deleteme) == 1);
927 // create a file of size 3
928 write(deleteme, "abc");
929 import std.utf : byChar;
930 assert(getSize(deleteme.byChar) == 3);
931 }
932
933
934 // Reads a time field from a stat_t with full precision.
935 version (Posix)
936 private SysTime statTimeToStdTime(char which)(ref stat_t statbuf)
937 {
938 auto unixTime = mixin(`statbuf.st_` ~ which ~ `time`);
939 long stdTime = unixTimeToStdTime(unixTime);
940
941 static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `tim`))))
942 stdTime += mixin(`statbuf.st_` ~ which ~ `tim.tv_nsec`) / 100;
943 else
944 static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `timensec`))))
945 stdTime += mixin(`statbuf.st_` ~ which ~ `timensec`) / 100;
946 else
947 static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `time_nsec`))))
948 stdTime += mixin(`statbuf.st_` ~ which ~ `time_nsec`) / 100;
949 else
950 static if (is(typeof(mixin(`statbuf.__st_` ~ which ~ `timensec`))))
951 stdTime += mixin(`statbuf.__st_` ~ which ~ `timensec`) / 100;
952
953 return SysTime(stdTime);
954 }
955
956 /++
957 Get the access and modified times of file or folder $(D name).
958
959 Params:
960 name = File/Folder _name to get times for.
961 accessTime = Time the file/folder was last accessed.
962 modificationTime = Time the file/folder was last modified.
963
964 Throws:
965 $(D FileException) on error.
966 +/
967 void getTimes(R)(R name,
968 out SysTime accessTime,
969 out SysTime modificationTime)
970 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
971 !isConvertibleToString!R)
972 {
973 version (Windows)
974 {
975 import std.datetime.systime : FILETIMEToSysTime;
976
977 with (getFileAttributesWin(name))
978 {
979 accessTime = FILETIMEToSysTime(&ftLastAccessTime);
980 modificationTime = FILETIMEToSysTime(&ftLastWriteTime);
981 }
982 }
983 else version (Posix)
984 {
985 auto namez = name.tempCString();
986
987 static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
988 {
989 return stat(namez, &buf);
990 }
991 stat_t statbuf = void;
992
993 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
994 alias names = name;
995 else
996 string names = null;
997 cenforce(trustedStat(namez, statbuf) == 0, names, namez);
998
999 accessTime = statTimeToStdTime!'a'(statbuf);
1000 modificationTime = statTimeToStdTime!'m'(statbuf);
1001 }
1002 }
1003
1004 /// ditto
1005 void getTimes(R)(auto ref R name,
1006 out SysTime accessTime,
1007 out SysTime modificationTime)
1008 if (isConvertibleToString!R)
1009 {
1010 return getTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
1011 }
1012
1013 @safe unittest
1014 {
1015 SysTime atime, mtime;
1016 static assert(__traits(compiles, getTimes(TestAliasedString("foo"), atime, mtime)));
1017 }
1018
1019 @system unittest
1020 {
1021 import std.stdio : writefln;
1022
1023 auto currTime = Clock.currTime();
1024
1025 write(deleteme, "a");
1026 scope(exit) { assert(exists(deleteme)); remove(deleteme); }
1027
1028 SysTime accessTime1 = void;
1029 SysTime modificationTime1 = void;
1030
1031 getTimes(deleteme, accessTime1, modificationTime1);
1032
1033 enum leeway = dur!"seconds"(5);
1034
1035 {
1036 auto diffa = accessTime1 - currTime;
1037 auto diffm = modificationTime1 - currTime;
1038 scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime1, modificationTime1, currTime, diffa, diffm);
1039
1040 assert(abs(diffa) <= leeway);
1041 assert(abs(diffm) <= leeway);
1042 }
1043
1044 version (fullFileTests)
1045 {
1046 import core.thread;
1047 enum sleepTime = dur!"seconds"(2);
1048 Thread.sleep(sleepTime);
1049
1050 currTime = Clock.currTime();
1051 write(deleteme, "b");
1052
1053 SysTime accessTime2 = void;
1054 SysTime modificationTime2 = void;
1055
1056 getTimes(deleteme, accessTime2, modificationTime2);
1057
1058 {
1059 auto diffa = accessTime2 - currTime;
1060 auto diffm = modificationTime2 - currTime;
1061 scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime2, modificationTime2, currTime, diffa, diffm);
1062
1063 //There is no guarantee that the access time will be updated.
1064 assert(abs(diffa) <= leeway + sleepTime);
1065 assert(abs(diffm) <= leeway);
1066 }
1067
1068 assert(accessTime1 <= accessTime2);
1069 assert(modificationTime1 <= modificationTime2);
1070 }
1071 }
1072
1073
1074 version (StdDdoc)
1075 {
1076 /++
1077 $(BLUE This function is Windows-Only.)
1078
1079 Get creation/access/modified times of file $(D name).
1080
1081 This is the same as $(D getTimes) except that it also gives you the file
1082 creation time - which isn't possible on Posix systems.
1083
1084 Params:
1085 name = File _name to get times for.
1086 fileCreationTime = Time the file was created.
1087 fileAccessTime = Time the file was last accessed.
1088 fileModificationTime = Time the file was last modified.
1089
1090 Throws:
1091 $(D FileException) on error.
1092 +/
1093 void getTimesWin(R)(R name,
1094 out SysTime fileCreationTime,
1095 out SysTime fileAccessTime,
1096 out SysTime fileModificationTime)
1097 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1098 !isConvertibleToString!R);
1099 }
1100 else version (Windows)
1101 {
1102 void getTimesWin(R)(R name,
1103 out SysTime fileCreationTime,
1104 out SysTime fileAccessTime,
1105 out SysTime fileModificationTime)
1106 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1107 !isConvertibleToString!R)
1108 {
1109 import std.datetime.systime : FILETIMEToSysTime;
1110
1111 with (getFileAttributesWin(name))
1112 {
1113 fileCreationTime = FILETIMEToSysTime(&ftCreationTime);
1114 fileAccessTime = FILETIMEToSysTime(&ftLastAccessTime);
1115 fileModificationTime = FILETIMEToSysTime(&ftLastWriteTime);
1116 }
1117 }
1118
1119 void getTimesWin(R)(auto ref R name,
1120 out SysTime fileCreationTime,
1121 out SysTime fileAccessTime,
1122 out SysTime fileModificationTime)
1123 if (isConvertibleToString!R)
1124 {
1125 getTimesWin!(StringTypeOf!R)(name, fileCreationTime, fileAccessTime, fileModificationTime);
1126 }
1127 }
1128
1129 version (Windows) @system unittest
1130 {
1131 import std.stdio : writefln;
1132 auto currTime = Clock.currTime();
1133
1134 write(deleteme, "a");
1135 scope(exit) { assert(exists(deleteme)); remove(deleteme); }
1136
1137 SysTime creationTime1 = void;
1138 SysTime accessTime1 = void;
1139 SysTime modificationTime1 = void;
1140
1141 getTimesWin(deleteme, creationTime1, accessTime1, modificationTime1);
1142
1143 enum leeway = dur!"seconds"(5);
1144
1145 {
1146 auto diffc = creationTime1 - currTime;
1147 auto diffa = accessTime1 - currTime;
1148 auto diffm = modificationTime1 - currTime;
1149 scope(failure)
1150 {
1151 writefln("[%s] [%s] [%s] [%s] [%s] [%s] [%s]",
1152 creationTime1, accessTime1, modificationTime1, currTime, diffc, diffa, diffm);
1153 }
1154
1155 // Deleting and recreating a file doesn't seem to always reset the "file creation time"
1156 //assert(abs(diffc) <= leeway);
1157 assert(abs(diffa) <= leeway);
1158 assert(abs(diffm) <= leeway);
1159 }
1160
1161 version (fullFileTests)
1162 {
1163 import core.thread;
1164 Thread.sleep(dur!"seconds"(2));
1165
1166 currTime = Clock.currTime();
1167 write(deleteme, "b");
1168
1169 SysTime creationTime2 = void;
1170 SysTime accessTime2 = void;
1171 SysTime modificationTime2 = void;
1172
1173 getTimesWin(deleteme, creationTime2, accessTime2, modificationTime2);
1174
1175 {
1176 auto diffa = accessTime2 - currTime;
1177 auto diffm = modificationTime2 - currTime;
1178 scope(failure)
1179 {
1180 writefln("[%s] [%s] [%s] [%s] [%s]",
1181 accessTime2, modificationTime2, currTime, diffa, diffm);
1182 }
1183
1184 assert(abs(diffa) <= leeway);
1185 assert(abs(diffm) <= leeway);
1186 }
1187
1188 assert(creationTime1 == creationTime2);
1189 assert(accessTime1 <= accessTime2);
1190 assert(modificationTime1 <= modificationTime2);
1191 }
1192
1193 {
1194 SysTime ctime, atime, mtime;
1195 static assert(__traits(compiles, getTimesWin(TestAliasedString("foo"), ctime, atime, mtime)));
1196 }
1197 }
1198
1199
1200 /++
1201 Set access/modified times of file or folder $(D name).
1202
1203 Params:
1204 name = File/Folder _name to get times for.
1205 accessTime = Time the file/folder was last accessed.
1206 modificationTime = Time the file/folder was last modified.
1207
1208 Throws:
1209 $(D FileException) on error.
1210 +/
1211 void setTimes(R)(R name,
1212 SysTime accessTime,
1213 SysTime modificationTime)
1214 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1215 !isConvertibleToString!R)
1216 {
1217 version (Windows)
1218 {
1219 import std.datetime.systime : SysTimeToFILETIME;
1220
1221 auto namez = name.tempCString!FSChar();
1222 static auto trustedCreateFileW(const(FSChar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode,
1223 SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition,
1224 DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted
1225 {
1226 return CreateFileW(namez, dwDesiredAccess, dwShareMode,
1227 lpSecurityAttributes, dwCreationDisposition,
1228 dwFlagsAndAttributes, hTemplateFile);
1229
1230 }
1231 static auto trustedCloseHandle(HANDLE hObject) @trusted
1232 {
1233 return CloseHandle(hObject);
1234 }
1235 static auto trustedSetFileTime(HANDLE hFile, in FILETIME *lpCreationTime,
1236 in ref FILETIME lpLastAccessTime, in ref FILETIME lpLastWriteTime) @trusted
1237 {
1238 return SetFileTime(hFile, lpCreationTime, &lpLastAccessTime, &lpLastWriteTime);
1239 }
1240
1241 const ta = SysTimeToFILETIME(accessTime);
1242 const tm = SysTimeToFILETIME(modificationTime);
1243 alias defaults =
1244 AliasSeq!(GENERIC_WRITE,
1245 0,
1246 null,
1247 OPEN_EXISTING,
1248 FILE_ATTRIBUTE_NORMAL |
1249 FILE_ATTRIBUTE_DIRECTORY |
1250 FILE_FLAG_BACKUP_SEMANTICS,
1251 HANDLE.init);
1252 auto h = trustedCreateFileW(namez, defaults);
1253
1254 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1255 alias names = name;
1256 else
1257 string names = null;
1258 cenforce(h != INVALID_HANDLE_VALUE, names, namez);
1259
1260 scope(exit)
1261 cenforce(trustedCloseHandle(h), names, namez);
1262
1263 cenforce(trustedSetFileTime(h, null, ta, tm), names, namez);
1264 }
1265 else version (Posix)
1266 {
1267 auto namez = name.tempCString!FSChar();
1268 static if (is(typeof(&utimensat)))
1269 {
1270 static auto trustedUtimensat(int fd, const(FSChar)* namez, const ref timespec[2] times, int flags) @trusted
1271 {
1272 return utimensat(fd, namez, times, flags);
1273 }
1274 timespec[2] t = void;
1275
1276 t[0] = accessTime.toTimeSpec();
1277 t[1] = modificationTime.toTimeSpec();
1278
1279 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1280 alias names = name;
1281 else
1282 string names = null;
1283 cenforce(trustedUtimensat(AT_FDCWD, namez, t, 0) == 0, names, namez);
1284 }
1285 else
1286 {
1287 static auto trustedUtimes(const(FSChar)* namez, const ref timeval[2] times) @trusted
1288 {
1289 return utimes(namez, times);
1290 }
1291 timeval[2] t = void;
1292
1293 t[0] = accessTime.toTimeVal();
1294 t[1] = modificationTime.toTimeVal();
1295
1296 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1297 alias names = name;
1298 else
1299 string names = null;
1300 cenforce(trustedUtimes(namez, t) == 0, names, namez);
1301 }
1302 }
1303 }
1304
1305 /// ditto
1306 void setTimes(R)(auto ref R name,
1307 SysTime accessTime,
1308 SysTime modificationTime)
1309 if (isConvertibleToString!R)
1310 {
1311 setTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
1312 }
1313
1314 @safe unittest
1315 {
1316 if (false) // Test instatiation
1317 setTimes(TestAliasedString("foo"), SysTime.init, SysTime.init);
1318 }
1319
1320 @system unittest
1321 {
1322 import std.stdio : File;
1323 string newdir = deleteme ~ r".dir";
1324 string dir = newdir ~ r"/a/b/c";
1325 string file = dir ~ "/file";
1326
1327 if (!exists(dir)) mkdirRecurse(dir);
1328 { auto f = File(file, "w"); }
1329
1330 void testTimes(int hnsecValue)
1331 {
1332 foreach (path; [file, dir]) // test file and dir
1333 {
1334 SysTime atime = SysTime(DateTime(2010, 10, 4, 0, 0, 30), hnsecs(hnsecValue));
1335 SysTime mtime = SysTime(DateTime(2011, 10, 4, 0, 0, 30), hnsecs(hnsecValue));
1336 setTimes(path, atime, mtime);
1337
1338 SysTime atime_res;
1339 SysTime mtime_res;
1340 getTimes(path, atime_res, mtime_res);
1341 assert(atime == atime_res);
1342 assert(mtime == mtime_res);
1343 }
1344 }
1345
1346 testTimes(0);
1347 version (linux)
1348 testTimes(123_456_7);
1349
1350 rmdirRecurse(newdir);
1351 }
1352
1353 /++
1354 Returns the time that the given file was last modified.
1355
1356 Throws:
1357 $(D FileException) if the given file does not exist.
1358 +/
1359 SysTime timeLastModified(R)(R name)
1360 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1361 !isConvertibleToString!R)
1362 {
1363 version (Windows)
1364 {
1365 SysTime dummy;
1366 SysTime ftm;
1367
1368 getTimesWin(name, dummy, dummy, ftm);
1369
1370 return ftm;
1371 }
1372 else version (Posix)
1373 {
1374 auto namez = name.tempCString!FSChar();
1375 static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
1376 {
1377 return stat(namez, &buf);
1378 }
1379 stat_t statbuf = void;
1380
1381 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1382 alias names = name;
1383 else
1384 string names = null;
1385 cenforce(trustedStat(namez, statbuf) == 0, names, namez);
1386
1387 return statTimeToStdTime!'m'(statbuf);
1388 }
1389 }
1390
1391 /// ditto
1392 SysTime timeLastModified(R)(auto ref R name)
1393 if (isConvertibleToString!R)
1394 {
1395 return timeLastModified!(StringTypeOf!R)(name);
1396 }
1397
1398 @safe unittest
1399 {
1400 static assert(__traits(compiles, timeLastModified(TestAliasedString("foo"))));
1401 }
1402
1403 /++
1404 Returns the time that the given file was last modified. If the
1405 file does not exist, returns $(D returnIfMissing).
1406
1407 A frequent usage pattern occurs in build automation tools such as
1408 $(HTTP gnu.org/software/make, make) or $(HTTP
1409 en.wikipedia.org/wiki/Apache_Ant, ant). To check whether file $(D
1410 target) must be rebuilt from file $(D source) (i.e., $(D target) is
1411 older than $(D source) or does not exist), use the comparison
1412 below. The code throws a $(D FileException) if $(D source) does not
1413 exist (as it should). On the other hand, the $(D SysTime.min) default
1414 makes a non-existing $(D target) seem infinitely old so the test
1415 correctly prompts building it.
1416
1417 Params:
1418 name = The _name of the file to get the modification time for.
1419 returnIfMissing = The time to return if the given file does not exist.
1420
1421 Example:
1422 --------------------
1423 if (timeLastModified(source) >= timeLastModified(target, SysTime.min))
1424 {
1425 // must (re)build
1426 }
1427 else
1428 {
1429 // target is up-to-date
1430 }
1431 --------------------
1432 +/
1433 SysTime timeLastModified(R)(R name, SysTime returnIfMissing)
1434 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R))
1435 {
1436 version (Windows)
1437 {
1438 if (!exists(name))
1439 return returnIfMissing;
1440
1441 SysTime dummy;
1442 SysTime ftm;
1443
1444 getTimesWin(name, dummy, dummy, ftm);
1445
1446 return ftm;
1447 }
1448 else version (Posix)
1449 {
1450 auto namez = name.tempCString!FSChar();
1451 static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
1452 {
1453 return stat(namez, &buf);
1454 }
1455 stat_t statbuf = void;
1456
1457 return trustedStat(namez, statbuf) != 0 ?
1458 returnIfMissing :
1459 statTimeToStdTime!'m'(statbuf);
1460 }
1461 }
1462
1463 @safe unittest
1464 {
1465 //std.process.system("echo a > deleteme") == 0 || assert(false);
1466 if (exists(deleteme))
1467 remove(deleteme);
1468
1469 write(deleteme, "a\n");
1470
1471 scope(exit)
1472 {
1473 assert(exists(deleteme));
1474 remove(deleteme);
1475 }
1476
1477 // assert(lastModified("deleteme") >
1478 // lastModified("this file does not exist", SysTime.min));
1479 //assert(lastModified("deleteme") > lastModified(__FILE__));
1480 }
1481
1482
1483 // Tests sub-second precision of querying file times.
1484 // Should pass on most modern systems running on modern filesystems.
1485 // Exceptions:
1486 // - FreeBSD, where one would need to first set the
1487 // vfs.timestamp_precision sysctl to a value greater than zero.
1488 // - OS X, where the native filesystem (HFS+) stores filesystem
1489 // timestamps with 1-second precision.
1490 version (FreeBSD) {} else
1491 version (OSX) {} else
1492 @system unittest
1493 {
1494 import core.thread;
1495
1496 if (exists(deleteme))
1497 remove(deleteme);
1498
1499 SysTime lastTime;
1500 foreach (n; 0 .. 3)
1501 {
1502 write(deleteme, "a");
1503 auto time = timeLastModified(deleteme);
1504 remove(deleteme);
1505 assert(time != lastTime);
1506 lastTime = time;
1507 Thread.sleep(10.msecs);
1508 }
1509 }
1510
1511
1512 /**
1513 * Determine whether the given file (or directory) _exists.
1514 * Params:
1515 * name = string or range of characters representing the file _name
1516 * Returns:
1517 * true if the file _name specified as input _exists
1518 */
1519 bool exists(R)(R name)
1520 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1521 !isConvertibleToString!R)
1522 {
1523 return existsImpl(name.tempCString!FSChar());
1524 }
1525
1526 /// ditto
1527 bool exists(R)(auto ref R name)
1528 if (isConvertibleToString!R)
1529 {
1530 return exists!(StringTypeOf!R)(name);
1531 }
1532
1533 private bool existsImpl(const(FSChar)* namez) @trusted nothrow @nogc
1534 {
1535 version (Windows)
1536 {
1537 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
1538 // fileio/base/getfileattributes.asp
1539 return GetFileAttributesW(namez) != 0xFFFFFFFF;
1540 }
1541 else version (Posix)
1542 {
1543 /*
1544 The reason why we use stat (and not access) here is
1545 the quirky behavior of access for SUID programs: if
1546 we used access, a file may not appear to "exist",
1547 despite that the program would be able to open it
1548 just fine. The behavior in question is described as
1549 follows in the access man page:
1550
1551 > The check is done using the calling process's real
1552 > UID and GID, rather than the effective IDs as is
1553 > done when actually attempting an operation (e.g.,
1554 > open(2)) on the file. This allows set-user-ID
1555 > programs to easily determine the invoking user's
1556 > authority.
1557
1558 While various operating systems provide eaccess or
1559 euidaccess functions, these are not part of POSIX -
1560 so it's safer to use stat instead.
1561 */
1562
1563 stat_t statbuf = void;
1564 return lstat(namez, &statbuf) == 0;
1565 }
1566 else
1567 static assert(0);
1568 }
1569
1570 @safe unittest
1571 {
1572 assert(exists("."));
1573 assert(!exists("this file does not exist"));
1574 write(deleteme, "a\n");
1575 scope(exit) { assert(exists(deleteme)); remove(deleteme); }
1576 assert(exists(deleteme));
1577 }
1578
1579 @safe unittest // Bugzilla 16573
1580 {
1581 enum S : string { foo = "foo" }
1582 assert(__traits(compiles, S.foo.exists));
1583 }
1584
1585 /++
1586 Returns the attributes of the given file.
1587
1588 Note that the file attributes on Windows and Posix systems are
1589 completely different. On Windows, they're what is returned by
1590 $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx,
1591 GetFileAttributes), whereas on Posix systems, they're the $(LUCKY
1592 st_mode) value which is part of the $(D stat struct) gotten by
1593 calling the $(HTTP en.wikipedia.org/wiki/Stat_%28Unix%29, $(D stat))
1594 function.
1595
1596 On Posix systems, if the given file is a symbolic link, then
1597 attributes are the attributes of the file pointed to by the symbolic
1598 link.
1599
1600 Params:
1601 name = The file to get the attributes of.
1602
1603 Throws: $(D FileException) on error.
1604 +/
1605 uint getAttributes(R)(R name)
1606 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1607 !isConvertibleToString!R)
1608 {
1609 version (Windows)
1610 {
1611 auto namez = name.tempCString!FSChar();
1612 static auto trustedGetFileAttributesW(const(FSChar)* namez) @trusted
1613 {
1614 return GetFileAttributesW(namez);
1615 }
1616 immutable result = trustedGetFileAttributesW(namez);
1617
1618 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1619 alias names = name;
1620 else
1621 string names = null;
1622 cenforce(result != INVALID_FILE_ATTRIBUTES, names, namez);
1623
1624 return result;
1625 }
1626 else version (Posix)
1627 {
1628 auto namez = name.tempCString!FSChar();
1629 static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
1630 {
1631 return stat(namez, &buf);
1632 }
1633 stat_t statbuf = void;
1634
1635 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1636 alias names = name;
1637 else
1638 string names = null;
1639 cenforce(trustedStat(namez, statbuf) == 0, names, namez);
1640
1641 return statbuf.st_mode;
1642 }
1643 }
1644
1645 /// ditto
1646 uint getAttributes(R)(auto ref R name)
1647 if (isConvertibleToString!R)
1648 {
1649 return getAttributes!(StringTypeOf!R)(name);
1650 }
1651
1652 @safe unittest
1653 {
1654 static assert(__traits(compiles, getAttributes(TestAliasedString(null))));
1655 }
1656
1657 /++
1658 If the given file is a symbolic link, then this returns the attributes of the
1659 symbolic link itself rather than file that it points to. If the given file
1660 is $(I not) a symbolic link, then this function returns the same result
1661 as getAttributes.
1662
1663 On Windows, getLinkAttributes is identical to getAttributes. It exists on
1664 Windows so that you don't have to special-case code for Windows when dealing
1665 with symbolic links.
1666
1667 Params:
1668 name = The file to get the symbolic link attributes of.
1669
1670 Returns:
1671 the attributes
1672
1673 Throws:
1674 $(D FileException) on error.
1675 +/
1676 uint getLinkAttributes(R)(R name)
1677 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1678 !isConvertibleToString!R)
1679 {
1680 version (Windows)
1681 {
1682 return getAttributes(name);
1683 }
1684 else version (Posix)
1685 {
1686 auto namez = name.tempCString!FSChar();
1687 static auto trustedLstat(const(FSChar)* namez, ref stat_t buf) @trusted
1688 {
1689 return lstat(namez, &buf);
1690 }
1691 stat_t lstatbuf = void;
1692 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1693 alias names = name;
1694 else
1695 string names = null;
1696 cenforce(trustedLstat(namez, lstatbuf) == 0, names, namez);
1697 return lstatbuf.st_mode;
1698 }
1699 }
1700
1701 /// ditto
1702 uint getLinkAttributes(R)(auto ref R name)
1703 if (isConvertibleToString!R)
1704 {
1705 return getLinkAttributes!(StringTypeOf!R)(name);
1706 }
1707
1708 @safe unittest
1709 {
1710 static assert(__traits(compiles, getLinkAttributes(TestAliasedString(null))));
1711 }
1712
1713 /++
1714 Set the _attributes of the given file.
1715
1716 Params:
1717 name = the file _name
1718 attributes = the _attributes to set the file to
1719
1720 Throws:
1721 $(D FileException) if the given file does not exist.
1722 +/
1723 void setAttributes(R)(R name, uint attributes)
1724 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1725 !isConvertibleToString!R)
1726 {
1727 version (Windows)
1728 {
1729 auto namez = name.tempCString!FSChar();
1730 static auto trustedSetFileAttributesW(const(FSChar)* namez, uint dwFileAttributes) @trusted
1731 {
1732 return SetFileAttributesW(namez, dwFileAttributes);
1733 }
1734 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1735 alias names = name;
1736 else
1737 string names = null;
1738 cenforce(trustedSetFileAttributesW(namez, attributes), names, namez);
1739 }
1740 else version (Posix)
1741 {
1742 auto namez = name.tempCString!FSChar();
1743 static auto trustedChmod(const(FSChar)* namez, mode_t mode) @trusted
1744 {
1745 return chmod(namez, mode);
1746 }
1747 assert(attributes <= mode_t.max);
1748 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1749 alias names = name;
1750 else
1751 string names = null;
1752 cenforce(!trustedChmod(namez, cast(mode_t) attributes), names, namez);
1753 }
1754 }
1755
1756 /// ditto
1757 void setAttributes(R)(auto ref R name, uint attributes)
1758 if (isConvertibleToString!R)
1759 {
1760 return setAttributes!(StringTypeOf!R)(name, attributes);
1761 }
1762
1763 @safe unittest
1764 {
1765 static assert(__traits(compiles, setAttributes(TestAliasedString(null), 0)));
1766 }
1767
1768 /++
1769 Returns whether the given file is a directory.
1770
1771 Params:
1772 name = The path to the file.
1773
1774 Returns:
1775 true if name specifies a directory
1776
1777 Throws:
1778 $(D FileException) if the given file does not exist.
1779
1780 Example:
1781 --------------------
1782 assert(!"/etc/fonts/fonts.conf".isDir);
1783 assert("/usr/share/include".isDir);
1784 --------------------
1785 +/
1786 @property bool isDir(R)(R name)
1787 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1788 !isConvertibleToString!R)
1789 {
1790 version (Windows)
1791 {
1792 return (getAttributes(name) & FILE_ATTRIBUTE_DIRECTORY) != 0;
1793 }
1794 else version (Posix)
1795 {
1796 return (getAttributes(name) & S_IFMT) == S_IFDIR;
1797 }
1798 }
1799
1800 /// ditto
1801 @property bool isDir(R)(auto ref R name)
1802 if (isConvertibleToString!R)
1803 {
1804 return name.isDir!(StringTypeOf!R);
1805 }
1806
1807 @safe unittest
1808 {
1809 static assert(__traits(compiles, TestAliasedString(null).isDir));
1810 }
1811
1812 @safe unittest
1813 {
1814 version (Windows)
1815 {
1816 if ("C:\\Program Files\\".exists)
1817 assert("C:\\Program Files\\".isDir);
1818
1819 if ("C:\\Windows\\system.ini".exists)
1820 assert(!"C:\\Windows\\system.ini".isDir);
1821 }
1822 else version (Posix)
1823 {
1824 if (system_directory.exists)
1825 assert(system_directory.isDir);
1826
1827 if (system_file.exists)
1828 assert(!system_file.isDir);
1829 }
1830 }
1831
1832 @system unittest
1833 {
1834 version (Windows)
1835 enum dir = "C:\\Program Files\\";
1836 else version (Posix)
1837 enum dir = system_directory;
1838
1839 if (dir.exists)
1840 {
1841 DirEntry de = DirEntry(dir);
1842 assert(de.isDir);
1843 assert(DirEntry(dir).isDir);
1844 }
1845 }
1846
1847 /++
1848 Returns whether the given file _attributes are for a directory.
1849
1850 Params:
1851 attributes = The file _attributes.
1852
1853 Returns:
1854 true if attributes specifies a directory
1855
1856 Example:
1857 --------------------
1858 assert(!attrIsDir(getAttributes("/etc/fonts/fonts.conf")));
1859 assert(!attrIsDir(getLinkAttributes("/etc/fonts/fonts.conf")));
1860 --------------------
1861 +/
1862 bool attrIsDir(uint attributes) @safe pure nothrow @nogc
1863 {
1864 version (Windows)
1865 {
1866 return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
1867 }
1868 else version (Posix)
1869 {
1870 return (attributes & S_IFMT) == S_IFDIR;
1871 }
1872 }
1873
1874 @safe unittest
1875 {
1876 version (Windows)
1877 {
1878 if ("C:\\Program Files\\".exists)
1879 {
1880 assert(attrIsDir(getAttributes("C:\\Program Files\\")));
1881 assert(attrIsDir(getLinkAttributes("C:\\Program Files\\")));
1882 }
1883
1884 if ("C:\\Windows\\system.ini".exists)
1885 {
1886 assert(!attrIsDir(getAttributes("C:\\Windows\\system.ini")));
1887 assert(!attrIsDir(getLinkAttributes("C:\\Windows\\system.ini")));
1888 }
1889 }
1890 else version (Posix)
1891 {
1892 if (system_directory.exists)
1893 {
1894 assert(attrIsDir(getAttributes(system_directory)));
1895 assert(attrIsDir(getLinkAttributes(system_directory)));
1896 }
1897
1898 if (system_file.exists)
1899 {
1900 assert(!attrIsDir(getAttributes(system_file)));
1901 assert(!attrIsDir(getLinkAttributes(system_file)));
1902 }
1903 }
1904 }
1905
1906
1907 /++
1908 Returns whether the given file (or directory) is a file.
1909
1910 On Windows, if a file is not a directory, then it's a file. So,
1911 either $(D isFile) or $(D isDir) will return true for any given file.
1912
1913 On Posix systems, if $(D isFile) is $(D true), that indicates that the file
1914 is a regular file (e.g. not a block not device). So, on Posix systems, it's
1915 possible for both $(D isFile) and $(D isDir) to be $(D false) for a
1916 particular file (in which case, it's a special file). You can use
1917 $(D getAttributes) to get the attributes to figure out what type of special
1918 it is, or you can use $(D DirEntry) to get at its $(D statBuf), which is the
1919 result from $(D stat). In either case, see the man page for $(D stat) for
1920 more information.
1921
1922 Params:
1923 name = The path to the file.
1924
1925 Returns:
1926 true if name specifies a file
1927
1928 Throws:
1929 $(D FileException) if the given file does not exist.
1930
1931 Example:
1932 --------------------
1933 assert("/etc/fonts/fonts.conf".isFile);
1934 assert(!"/usr/share/include".isFile);
1935 --------------------
1936 +/
1937 @property bool isFile(R)(R name)
1938 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1939 !isConvertibleToString!R)
1940 {
1941 version (Windows)
1942 return !name.isDir;
1943 else version (Posix)
1944 return (getAttributes(name) & S_IFMT) == S_IFREG;
1945 }
1946
1947 /// ditto
1948 @property bool isFile(R)(auto ref R name)
1949 if (isConvertibleToString!R)
1950 {
1951 return isFile!(StringTypeOf!R)(name);
1952 }
1953
1954 @system unittest // bugzilla 15658
1955 {
1956 DirEntry e = DirEntry(".");
1957 static assert(is(typeof(isFile(e))));
1958 }
1959
1960 @safe unittest
1961 {
1962 static assert(__traits(compiles, TestAliasedString(null).isFile));
1963 }
1964
1965 @safe unittest
1966 {
1967 version (Windows)
1968 {
1969 if ("C:\\Program Files\\".exists)
1970 assert(!"C:\\Program Files\\".isFile);
1971
1972 if ("C:\\Windows\\system.ini".exists)
1973 assert("C:\\Windows\\system.ini".isFile);
1974 }
1975 else version (Posix)
1976 {
1977 if (system_directory.exists)
1978 assert(!system_directory.isFile);
1979
1980 if (system_file.exists)
1981 assert(system_file.isFile);
1982 }
1983 }
1984
1985
1986 /++
1987 Returns whether the given file _attributes are for a file.
1988
1989 On Windows, if a file is not a directory, it's a file. So, either
1990 $(D attrIsFile) or $(D attrIsDir) will return $(D true) for the
1991 _attributes of any given file.
1992
1993 On Posix systems, if $(D attrIsFile) is $(D true), that indicates that the
1994 file is a regular file (e.g. not a block not device). So, on Posix systems,
1995 it's possible for both $(D attrIsFile) and $(D attrIsDir) to be $(D false)
1996 for a particular file (in which case, it's a special file). If a file is a
1997 special file, you can use the _attributes to check what type of special file
1998 it is (see the man page for $(D stat) for more information).
1999
2000 Params:
2001 attributes = The file _attributes.
2002
2003 Returns:
2004 true if the given file _attributes are for a file
2005
2006 Example:
2007 --------------------
2008 assert(attrIsFile(getAttributes("/etc/fonts/fonts.conf")));
2009 assert(attrIsFile(getLinkAttributes("/etc/fonts/fonts.conf")));
2010 --------------------
2011 +/
2012 bool attrIsFile(uint attributes) @safe pure nothrow @nogc
2013 {
2014 version (Windows)
2015 {
2016 return (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
2017 }
2018 else version (Posix)
2019 {
2020 return (attributes & S_IFMT) == S_IFREG;
2021 }
2022 }
2023
2024 @safe unittest
2025 {
2026 version (Windows)
2027 {
2028 if ("C:\\Program Files\\".exists)
2029 {
2030 assert(!attrIsFile(getAttributes("C:\\Program Files\\")));
2031 assert(!attrIsFile(getLinkAttributes("C:\\Program Files\\")));
2032 }
2033
2034 if ("C:\\Windows\\system.ini".exists)
2035 {
2036 assert(attrIsFile(getAttributes("C:\\Windows\\system.ini")));
2037 assert(attrIsFile(getLinkAttributes("C:\\Windows\\system.ini")));
2038 }
2039 }
2040 else version (Posix)
2041 {
2042 if (system_directory.exists)
2043 {
2044 assert(!attrIsFile(getAttributes(system_directory)));
2045 assert(!attrIsFile(getLinkAttributes(system_directory)));
2046 }
2047
2048 if (system_file.exists)
2049 {
2050 assert(attrIsFile(getAttributes(system_file)));
2051 assert(attrIsFile(getLinkAttributes(system_file)));
2052 }
2053 }
2054 }
2055
2056
2057 /++
2058 Returns whether the given file is a symbolic link.
2059
2060 On Windows, returns $(D true) when the file is either a symbolic link or a
2061 junction point.
2062
2063 Params:
2064 name = The path to the file.
2065
2066 Returns:
2067 true if name is a symbolic link
2068
2069 Throws:
2070 $(D FileException) if the given file does not exist.
2071 +/
2072 @property bool isSymlink(R)(R name)
2073 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
2074 !isConvertibleToString!R)
2075 {
2076 version (Windows)
2077 return (getAttributes(name) & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
2078 else version (Posix)
2079 return (getLinkAttributes(name) & S_IFMT) == S_IFLNK;
2080 }
2081
2082 /// ditto
2083 @property bool isSymlink(R)(auto ref R name)
2084 if (isConvertibleToString!R)
2085 {
2086 return name.isSymlink!(StringTypeOf!R);
2087 }
2088
2089 @safe unittest
2090 {
2091 static assert(__traits(compiles, TestAliasedString(null).isSymlink));
2092 }
2093
2094 @system unittest
2095 {
2096 version (Windows)
2097 {
2098 if ("C:\\Program Files\\".exists)
2099 assert(!"C:\\Program Files\\".isSymlink);
2100
2101 if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
2102 assert("C:\\Documents and Settings\\".isSymlink);
2103
2104 enum fakeSymFile = "C:\\Windows\\system.ini";
2105 if (fakeSymFile.exists)
2106 {
2107 assert(!fakeSymFile.isSymlink);
2108
2109 assert(!fakeSymFile.isSymlink);
2110 assert(!attrIsSymlink(getAttributes(fakeSymFile)));
2111 assert(!attrIsSymlink(getLinkAttributes(fakeSymFile)));
2112
2113 assert(attrIsFile(getAttributes(fakeSymFile)));
2114 assert(attrIsFile(getLinkAttributes(fakeSymFile)));
2115 assert(!attrIsDir(getAttributes(fakeSymFile)));
2116 assert(!attrIsDir(getLinkAttributes(fakeSymFile)));
2117
2118 assert(getAttributes(fakeSymFile) == getLinkAttributes(fakeSymFile));
2119 }
2120 }
2121 else version (Posix)
2122 {
2123 if (system_directory.exists)
2124 {
2125 assert(!system_directory.isSymlink);
2126
2127 immutable symfile = deleteme ~ "_slink\0";
2128 scope(exit) if (symfile.exists) symfile.remove();
2129
2130 core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
2131
2132 assert(symfile.isSymlink);
2133 assert(!attrIsSymlink(getAttributes(symfile)));
2134 assert(attrIsSymlink(getLinkAttributes(symfile)));
2135
2136 assert(attrIsDir(getAttributes(symfile)));
2137 assert(!attrIsDir(getLinkAttributes(symfile)));
2138
2139 assert(!attrIsFile(getAttributes(symfile)));
2140 assert(!attrIsFile(getLinkAttributes(symfile)));
2141 }
2142
2143 if (system_file.exists)
2144 {
2145 assert(!system_file.isSymlink);
2146
2147 immutable symfile = deleteme ~ "_slink\0";
2148 scope(exit) if (symfile.exists) symfile.remove();
2149
2150 core.sys.posix.unistd.symlink(system_file, symfile.ptr);
2151
2152 assert(symfile.isSymlink);
2153 assert(!attrIsSymlink(getAttributes(symfile)));
2154 assert(attrIsSymlink(getLinkAttributes(symfile)));
2155
2156 assert(!attrIsDir(getAttributes(symfile)));
2157 assert(!attrIsDir(getLinkAttributes(symfile)));
2158
2159 assert(attrIsFile(getAttributes(symfile)));
2160 assert(!attrIsFile(getLinkAttributes(symfile)));
2161 }
2162 }
2163
2164 static assert(__traits(compiles, () @safe { return "dummy".isSymlink; }));
2165 }
2166
2167
2168 /++
2169 Returns whether the given file attributes are for a symbolic link.
2170
2171 On Windows, return $(D true) when the file is either a symbolic link or a
2172 junction point.
2173
2174 Params:
2175 attributes = The file attributes.
2176
2177 Returns:
2178 true if attributes are for a symbolic link
2179
2180 Example:
2181 --------------------
2182 core.sys.posix.unistd.symlink("/etc/fonts/fonts.conf", "/tmp/alink");
2183
2184 assert(!getAttributes("/tmp/alink").isSymlink);
2185 assert(getLinkAttributes("/tmp/alink").isSymlink);
2186 --------------------
2187 +/
2188 bool attrIsSymlink(uint attributes) @safe pure nothrow @nogc
2189 {
2190 version (Windows)
2191 return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
2192 else version (Posix)
2193 return (attributes & S_IFMT) == S_IFLNK;
2194 }
2195
2196
2197 /****************************************************
2198 * Change directory to $(D pathname).
2199 * Throws: $(D FileException) on error.
2200 */
2201 void chdir(R)(R pathname)
2202 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
2203 !isConvertibleToString!R)
2204 {
2205 // Place outside of @trusted block
2206 auto pathz = pathname.tempCString!FSChar();
2207
2208 version (Windows)
2209 {
2210 static auto trustedChdir(const(FSChar)* pathz) @trusted
2211 {
2212 return SetCurrentDirectoryW(pathz);
2213 }
2214 }
2215 else version (Posix)
2216 {
2217 static auto trustedChdir(const(FSChar)* pathz) @trusted
2218 {
2219 return core.sys.posix.unistd.chdir(pathz) == 0;
2220 }
2221 }
2222 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
2223 alias pathStr = pathname;
2224 else
2225 string pathStr = null;
2226 cenforce(trustedChdir(pathz), pathStr, pathz);
2227 }
2228
2229 /// ditto
2230 void chdir(R)(auto ref R pathname)
2231 if (isConvertibleToString!R)
2232 {
2233 return chdir!(StringTypeOf!R)(pathname);
2234 }
2235
2236 @safe unittest
2237 {
2238 static assert(__traits(compiles, chdir(TestAliasedString(null))));
2239 }
2240
2241 /****************************************************
2242 Make directory $(D pathname).
2243
2244 Throws: $(D FileException) on Posix or $(D WindowsException) on Windows
2245 if an error occured.
2246 */
2247 void mkdir(R)(R pathname)
2248 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
2249 !isConvertibleToString!R)
2250 {
2251 // Place outside of @trusted block
2252 const pathz = pathname.tempCString!FSChar();
2253
2254 version (Windows)
2255 {
2256 static auto trustedCreateDirectoryW(const(FSChar)* pathz) @trusted
2257 {
2258 return CreateDirectoryW(pathz, null);
2259 }
2260 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
2261 alias pathStr = pathname;
2262 else
2263 string pathStr = null;
2264 wenforce(trustedCreateDirectoryW(pathz), pathStr, pathz);
2265 }
2266 else version (Posix)
2267 {
2268 import std.conv : octal;
2269
2270 static auto trustedMkdir(const(FSChar)* pathz, mode_t mode) @trusted
2271 {
2272 return core.sys.posix.sys.stat.mkdir(pathz, mode);
2273 }
2274 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
2275 alias pathStr = pathname;
2276 else
2277 string pathStr = null;
2278 cenforce(trustedMkdir(pathz, octal!777) == 0, pathStr, pathz);
2279 }
2280 }
2281
2282 /// ditto
2283 void mkdir(R)(auto ref R pathname)
2284 if (isConvertibleToString!R)
2285 {
2286 return mkdir!(StringTypeOf!R)(pathname);
2287 }
2288
2289 @safe unittest
2290 {
2291 import std.path : mkdir;
2292 static assert(__traits(compiles, mkdir(TestAliasedString(null))));
2293 }
2294
2295 // Same as mkdir but ignores "already exists" errors.
2296 // Returns: "true" if the directory was created,
2297 // "false" if it already existed.
2298 private bool ensureDirExists()(in char[] pathname)
2299 {
2300 import std.exception : enforce;
2301 const pathz = pathname.tempCString!FSChar();
2302
2303 version (Windows)
2304 {
2305 if (() @trusted { return CreateDirectoryW(pathz, null); }())
2306 return true;
2307 cenforce(GetLastError() == ERROR_ALREADY_EXISTS, pathname.idup);
2308 }
2309 else version (Posix)
2310 {
2311 import std.conv : octal;
2312
2313 if (() @trusted { return core.sys.posix.sys.stat.mkdir(pathz, octal!777); }() == 0)
2314 return true;
2315 cenforce(errno == EEXIST || errno == EISDIR, pathname);
2316 }
2317 enforce(pathname.isDir, new FileException(pathname.idup));
2318 return false;
2319 }
2320
2321 /****************************************************
2322 * Make directory and all parent directories as needed.
2323 *
2324 * Does nothing if the directory specified by
2325 * $(D pathname) already exists.
2326 *
2327 * Throws: $(D FileException) on error.
2328 */
2329
2330 void mkdirRecurse(in char[] pathname) @safe
2331 {
2332 import std.path : dirName, baseName;
2333
2334 const left = dirName(pathname);
2335 if (left.length != pathname.length && !exists(left))
2336 {
2337 mkdirRecurse(left);
2338 }
2339 if (!baseName(pathname).empty)
2340 {
2341 ensureDirExists(pathname);
2342 }
2343 }
2344
2345 @safe unittest
2346 {
2347 import std.exception : assertThrown;
2348 {
2349 import std.path : buildPath, buildNormalizedPath;
2350
2351 immutable basepath = deleteme ~ "_dir";
2352 scope(exit) () @trusted { rmdirRecurse(basepath); }();
2353
2354 auto path = buildPath(basepath, "a", "..", "b");
2355 mkdirRecurse(path);
2356 path = path.buildNormalizedPath;
2357 assert(path.isDir);
2358
2359 path = buildPath(basepath, "c");
2360 write(path, "");
2361 assertThrown!FileException(mkdirRecurse(path));
2362
2363 path = buildPath(basepath, "d");
2364 mkdirRecurse(path);
2365 mkdirRecurse(path); // should not throw
2366 }
2367
2368 version (Windows)
2369 {
2370 assertThrown!FileException(mkdirRecurse(`1:\foobar`));
2371 }
2372
2373 // bug3570
2374 {
2375 immutable basepath = deleteme ~ "_dir";
2376 version (Windows)
2377 {
2378 immutable path = basepath ~ "\\fake\\here\\";
2379 }
2380 else version (Posix)
2381 {
2382 immutable path = basepath ~ `/fake/here/`;
2383 }
2384
2385 mkdirRecurse(path);
2386 assert(basepath.exists && basepath.isDir);
2387 scope(exit) () @trusted { rmdirRecurse(basepath); }();
2388 assert(path.exists && path.isDir);
2389 }
2390 }
2391
2392 /****************************************************
2393 Remove directory $(D pathname).
2394
2395 Params:
2396 pathname = Range or string specifying the directory name
2397
2398 Throws: $(D FileException) on error.
2399 */
2400 void rmdir(R)(R pathname)
2401 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
2402 !isConvertibleToString!R)
2403 {
2404 // Place outside of @trusted block
2405 auto pathz = pathname.tempCString!FSChar();
2406
2407 version (Windows)
2408 {
2409 static auto trustedRmdir(const(FSChar)* pathz) @trusted
2410 {
2411 return RemoveDirectoryW(pathz);
2412 }
2413 }
2414 else version (Posix)
2415 {
2416 static auto trustedRmdir(const(FSChar)* pathz) @trusted
2417 {
2418 return core.sys.posix.unistd.rmdir(pathz) == 0;
2419 }
2420 }
2421 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
2422 alias pathStr = pathname;
2423 else
2424 string pathStr = null;
2425 cenforce(trustedRmdir(pathz), pathStr, pathz);
2426 }
2427
2428 /// ditto
2429 void rmdir(R)(auto ref R pathname)
2430 if (isConvertibleToString!R)
2431 {
2432 rmdir!(StringTypeOf!R)(pathname);
2433 }
2434
2435 @safe unittest
2436 {
2437 static assert(__traits(compiles, rmdir(TestAliasedString(null))));
2438 }
2439
2440 /++
2441 $(BLUE This function is Posix-Only.)
2442
2443 Creates a symbolic _link (_symlink).
2444
2445 Params:
2446 original = The file that is being linked. This is the target path that's
2447 stored in the _symlink. A relative path is relative to the created
2448 _symlink.
2449 link = The _symlink to create. A relative path is relative to the
2450 current working directory.
2451
2452 Throws:
2453 $(D FileException) on error (which includes if the _symlink already
2454 exists).
2455 +/
2456 version (StdDdoc) void symlink(RO, RL)(RO original, RL link)
2457 if ((isInputRange!RO && !isInfinite!RO && isSomeChar!(ElementEncodingType!RO) ||
2458 isConvertibleToString!RO) &&
2459 (isInputRange!RL && !isInfinite!RL && isSomeChar!(ElementEncodingType!RL) ||
2460 isConvertibleToString!RL));
2461 else version (Posix) void symlink(RO, RL)(RO original, RL link)
2462 if ((isInputRange!RO && !isInfinite!RO && isSomeChar!(ElementEncodingType!RO) ||
2463 isConvertibleToString!RO) &&
2464 (isInputRange!RL && !isInfinite!RL && isSomeChar!(ElementEncodingType!RL) ||
2465 isConvertibleToString!RL))
2466 {
2467 static if (isConvertibleToString!RO || isConvertibleToString!RL)
2468 {
2469 import std.meta : staticMap;
2470 alias Types = staticMap!(convertToString, RO, RL);
2471 symlink!Types(original, link);
2472 }
2473 else
2474 {
2475 import std.conv : text;
2476 auto oz = original.tempCString();
2477 auto lz = link.tempCString();
2478 alias posixSymlink = core.sys.posix.unistd.symlink;
2479 immutable int result = () @trusted { return posixSymlink(oz, lz); } ();
2480 cenforce(result == 0, text(link));
2481 }
2482 }
2483
2484 version (Posix) @safe unittest
2485 {
2486 if (system_directory.exists)
2487 {
2488 immutable symfile = deleteme ~ "_slink\0";
2489 scope(exit) if (symfile.exists) symfile.remove();
2490
2491 symlink(system_directory, symfile);
2492
2493 assert(symfile.exists);
2494 assert(symfile.isSymlink);
2495 assert(!attrIsSymlink(getAttributes(symfile)));
2496 assert(attrIsSymlink(getLinkAttributes(symfile)));
2497
2498 assert(attrIsDir(getAttributes(symfile)));
2499 assert(!attrIsDir(getLinkAttributes(symfile)));
2500
2501 assert(!attrIsFile(getAttributes(symfile)));
2502 assert(!attrIsFile(getLinkAttributes(symfile)));
2503 }
2504
2505 if (system_file.exists)
2506 {
2507 assert(!system_file.isSymlink);
2508
2509 immutable symfile = deleteme ~ "_slink\0";
2510 scope(exit) if (symfile.exists) symfile.remove();
2511
2512 symlink(system_file, symfile);
2513
2514 assert(symfile.exists);
2515 assert(symfile.isSymlink);
2516 assert(!attrIsSymlink(getAttributes(symfile)));
2517 assert(attrIsSymlink(getLinkAttributes(symfile)));
2518
2519 assert(!attrIsDir(getAttributes(symfile)));
2520 assert(!attrIsDir(getLinkAttributes(symfile)));
2521
2522 assert(attrIsFile(getAttributes(symfile)));
2523 assert(!attrIsFile(getLinkAttributes(symfile)));
2524 }
2525 }
2526
2527 version (Posix) @safe unittest
2528 {
2529 static assert(__traits(compiles,
2530 symlink(TestAliasedString(null), TestAliasedString(null))));
2531 }
2532
2533
2534 /++
2535 $(BLUE This function is Posix-Only.)
2536
2537 Returns the path to the file pointed to by a symlink. Note that the
2538 path could be either relative or absolute depending on the symlink.
2539 If the path is relative, it's relative to the symlink, not the current
2540 working directory.
2541
2542 Throws:
2543 $(D FileException) on error.
2544 +/
2545 version (StdDdoc) string readLink(R)(R link)
2546 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) ||
2547 isConvertibleToString!R);
2548 else version (Posix) string readLink(R)(R link)
2549 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) ||
2550 isConvertibleToString!R)
2551 {
2552 static if (isConvertibleToString!R)
2553 {
2554 return readLink!(convertToString!R)(link);
2555 }
2556 else
2557 {
2558 import std.conv : to;
2559 import std.exception : assumeUnique;
2560 alias posixReadlink = core.sys.posix.unistd.readlink;
2561 enum bufferLen = 2048;
2562 enum maxCodeUnits = 6;
2563 char[bufferLen] buffer;
2564 const linkz = link.tempCString();
2565 auto size = () @trusted {
2566 return posixReadlink(linkz, buffer.ptr, buffer.length);
2567 } ();
2568 cenforce(size != -1, to!string(link));
2569
2570 if (size <= bufferLen - maxCodeUnits)
2571 return to!string(buffer[0 .. size]);
2572
2573 auto dynamicBuffer = new char[](bufferLen * 3 / 2);
2574
2575 foreach (i; 0 .. 10)
2576 {
2577 size = () @trusted {
2578 return posixReadlink(linkz, dynamicBuffer.ptr,
2579 dynamicBuffer.length);
2580 } ();
2581 cenforce(size != -1, to!string(link));
2582
2583 if (size <= dynamicBuffer.length - maxCodeUnits)
2584 {
2585 dynamicBuffer.length = size;
2586 return () @trusted {
2587 return assumeUnique(dynamicBuffer);
2588 } ();
2589 }
2590
2591 dynamicBuffer.length = dynamicBuffer.length * 3 / 2;
2592 }
2593
2594 throw new FileException(to!string(link), "Path is too long to read.");
2595 }
2596 }
2597
2598 version (Posix) @safe unittest
2599 {
2600 import std.exception : assertThrown;
2601 import std.string;
2602
2603 foreach (file; [system_directory, system_file])
2604 {
2605 if (file.exists)
2606 {
2607 immutable symfile = deleteme ~ "_slink\0";
2608 scope(exit) if (symfile.exists) symfile.remove();
2609
2610 symlink(file, symfile);
2611 assert(readLink(symfile) == file, format("Failed file: %s", file));
2612 }
2613 }
2614
2615 assertThrown!FileException(readLink("/doesnotexist"));
2616 }
2617
2618 version (Posix) @safe unittest
2619 {
2620 static assert(__traits(compiles, readLink(TestAliasedString("foo"))));
2621 }
2622
2623 version (Posix) @system unittest // input range of dchars
2624 {
2625 mkdirRecurse(deleteme);
2626 scope(exit) if (deleteme.exists) rmdirRecurse(deleteme);
2627 write(deleteme ~ "/f", "");
2628 import std.range.interfaces : InputRange, inputRangeObject;
2629 import std.utf : byChar;
2630 immutable string link = deleteme ~ "/l";
2631 symlink("f", link);
2632 InputRange!dchar linkr = inputRangeObject(link);
2633 alias R = typeof(linkr);
2634 static assert(isInputRange!R);
2635 static assert(!isForwardRange!R);
2636 assert(readLink(linkr) == "f");
2637 }
2638
2639
2640 /****************************************************
2641 * Get the current working directory.
2642 * Throws: $(D FileException) on error.
2643 */
2644 version (Windows) string getcwd()
2645 {
2646 import std.conv : to;
2647 /* GetCurrentDirectory's return value:
2648 1. function succeeds: the number of characters that are written to
2649 the buffer, not including the terminating null character.
2650 2. function fails: zero
2651 3. the buffer (lpBuffer) is not large enough: the required size of
2652 the buffer, in characters, including the null-terminating character.
2653 */
2654 wchar[4096] buffW = void; //enough for most common case
2655 immutable n = cenforce(GetCurrentDirectoryW(to!DWORD(buffW.length), buffW.ptr),
2656 "getcwd");
2657 // we can do it because toUTFX always produces a fresh string
2658 if (n < buffW.length)
2659 {
2660 return buffW[0 .. n].to!string;
2661 }
2662 else //staticBuff isn't enough
2663 {
2664 auto ptr = cast(wchar*) malloc(wchar.sizeof * n);
2665 scope(exit) free(ptr);
2666 immutable n2 = GetCurrentDirectoryW(n, ptr);
2667 cenforce(n2 && n2 < n, "getcwd");
2668 return ptr[0 .. n2].to!string;
2669 }
2670 }
2671 else version (Solaris) string getcwd()
2672 {
2673 /* BUF_SIZE >= PATH_MAX */
2674 enum BUF_SIZE = 4096;
2675 /* The user should be able to specify any size buffer > 0 */
2676 auto p = cenforce(core.sys.posix.unistd.getcwd(null, BUF_SIZE),
2677 "cannot get cwd");
2678 scope(exit) core.stdc.stdlib.free(p);
2679 return p[0 .. core.stdc.string.strlen(p)].idup;
2680 }
2681 else version (Posix) string getcwd()
2682 {
2683 auto p = cenforce(core.sys.posix.unistd.getcwd(null, 0),
2684 "cannot get cwd");
2685 scope(exit) core.stdc.stdlib.free(p);
2686 return p[0 .. core.stdc.string.strlen(p)].idup;
2687 }
2688
2689 @system unittest
2690 {
2691 auto s = getcwd();
2692 assert(s.length);
2693 }
2694
2695 version (OSX)
2696 private extern (C) int _NSGetExecutablePath(char* buf, uint* bufsize);
2697 else version (FreeBSD)
2698 private extern (C) int sysctl (const int* name, uint namelen, void* oldp,
2699 size_t* oldlenp, const void* newp, size_t newlen);
2700 else version (NetBSD)
2701 private extern (C) int sysctl (const int* name, uint namelen, void* oldp,
2702 size_t* oldlenp, const void* newp, size_t newlen);
2703
2704 /**
2705 * Returns the full path of the current executable.
2706 *
2707 * Throws:
2708 * $(REF1 Exception, object)
2709 */
2710 @trusted string thisExePath ()
2711 {
2712 version (OSX)
2713 {
2714 import core.sys.posix.stdlib : realpath;
2715 import std.conv : to;
2716 import std.exception : errnoEnforce;
2717
2718 uint size;
2719
2720 _NSGetExecutablePath(null, &size); // get the length of the path
2721 auto buffer = new char[size];
2722 _NSGetExecutablePath(buffer.ptr, &size);
2723
2724 auto absolutePath = realpath(buffer.ptr, null); // let the function allocate
2725
2726 scope (exit)
2727 {
2728 if (absolutePath)
2729 free(absolutePath);
2730 }
2731
2732 errnoEnforce(absolutePath);
2733 return to!(string)(absolutePath);
2734 }
2735 else version (linux)
2736 {
2737 return readLink("/proc/self/exe");
2738 }
2739 else version (Windows)
2740 {
2741 import std.conv : to;
2742 import std.exception : enforce;
2743
2744 wchar[MAX_PATH] buf;
2745 wchar[] buffer = buf[];
2746
2747 while (true)
2748 {
2749 auto len = GetModuleFileNameW(null, buffer.ptr, cast(DWORD) buffer.length);
2750 enforce(len, sysErrorString(GetLastError()));
2751 if (len != buffer.length)
2752 return to!(string)(buffer[0 .. len]);
2753 buffer.length *= 2;
2754 }
2755 }
2756 else version (FreeBSD)
2757 {
2758 import std.exception : errnoEnforce, assumeUnique;
2759 enum
2760 {
2761 CTL_KERN = 1,
2762 KERN_PROC = 14,
2763 KERN_PROC_PATHNAME = 12
2764 }
2765
2766 int[4] mib = [CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1];
2767 size_t len;
2768
2769 auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); // get the length of the path
2770 errnoEnforce(result == 0);
2771
2772 auto buffer = new char[len - 1];
2773 result = sysctl(mib.ptr, mib.length, buffer.ptr, &len, null, 0);
2774 errnoEnforce(result == 0);
2775
2776 return buffer.assumeUnique;
2777 }
2778 else version (NetBSD)
2779 {
2780 return readLink("/proc/self/exe");
2781 }
2782 else version (Solaris)
2783 {
2784 import core.sys.posix.unistd : getpid;
2785 import std.string : format;
2786
2787 // Only Solaris 10 and later
2788 return readLink(format("/proc/%d/path/a.out", getpid()));
2789 }
2790 else
2791 static assert(0, "thisExePath is not supported on this platform");
2792 }
2793
2794 @safe unittest
2795 {
2796 import std.path : isAbsolute;
2797 auto path = thisExePath();
2798
2799 assert(path.exists);
2800 assert(path.isAbsolute);
2801 assert(path.isFile);
2802 }
2803
2804 version (StdDdoc)
2805 {
2806 /++
2807 Info on a file, similar to what you'd get from stat on a Posix system.
2808 +/
2809 struct DirEntry
2810 {
2811 /++
2812 Constructs a $(D DirEntry) for the given file (or directory).
2813
2814 Params:
2815 path = The file (or directory) to get a DirEntry for.
2816
2817 Throws:
2818 $(D FileException) if the file does not exist.
2819 +/
2820 this(string path);
2821
2822 version (Windows)
2823 {
2824 private this(string path, in WIN32_FIND_DATAW *fd);
2825 }
2826 else version (Posix)
2827 {
2828 private this(string path, core.sys.posix.dirent.dirent* fd);
2829 }
2830
2831 /++
2832 Returns the path to the file represented by this $(D DirEntry).
2833
2834 Example:
2835 --------------------
2836 auto de1 = DirEntry("/etc/fonts/fonts.conf");
2837 assert(de1.name == "/etc/fonts/fonts.conf");
2838
2839 auto de2 = DirEntry("/usr/share/include");
2840 assert(de2.name == "/usr/share/include");
2841 --------------------
2842 +/
2843 @property string name() const;
2844
2845
2846 /++
2847 Returns whether the file represented by this $(D DirEntry) is a
2848 directory.
2849
2850 Example:
2851 --------------------
2852 auto de1 = DirEntry("/etc/fonts/fonts.conf");
2853 assert(!de1.isDir);
2854
2855 auto de2 = DirEntry("/usr/share/include");
2856 assert(de2.isDir);
2857 --------------------
2858 +/
2859 @property bool isDir();
2860
2861
2862 /++
2863 Returns whether the file represented by this $(D DirEntry) is a file.
2864
2865 On Windows, if a file is not a directory, then it's a file. So,
2866 either $(D isFile) or $(D isDir) will return $(D true).
2867
2868 On Posix systems, if $(D isFile) is $(D true), that indicates that
2869 the file is a regular file (e.g. not a block not device). So, on
2870 Posix systems, it's possible for both $(D isFile) and $(D isDir) to
2871 be $(D false) for a particular file (in which case, it's a special
2872 file). You can use $(D attributes) or $(D statBuf) to get more
2873 information about a special file (see the stat man page for more
2874 details).
2875
2876 Example:
2877 --------------------
2878 auto de1 = DirEntry("/etc/fonts/fonts.conf");
2879 assert(de1.isFile);
2880
2881 auto de2 = DirEntry("/usr/share/include");
2882 assert(!de2.isFile);
2883 --------------------
2884 +/
2885 @property bool isFile();
2886
2887 /++
2888 Returns whether the file represented by this $(D DirEntry) is a
2889 symbolic link.
2890
2891 On Windows, return $(D true) when the file is either a symbolic
2892 link or a junction point.
2893 +/
2894 @property bool isSymlink();
2895
2896 /++
2897 Returns the size of the the file represented by this $(D DirEntry)
2898 in bytes.
2899 +/
2900 @property ulong size();
2901
2902 /++
2903 $(BLUE This function is Windows-Only.)
2904
2905 Returns the creation time of the file represented by this
2906 $(D DirEntry).
2907 +/
2908 @property SysTime timeCreated() const;
2909
2910 /++
2911 Returns the time that the file represented by this $(D DirEntry) was
2912 last accessed.
2913
2914 Note that many file systems do not update the access time for files
2915 (generally for performance reasons), so there's a good chance that
2916 $(D timeLastAccessed) will return the same value as
2917 $(D timeLastModified).
2918 +/
2919 @property SysTime timeLastAccessed();
2920
2921 /++
2922 Returns the time that the file represented by this $(D DirEntry) was
2923 last modified.
2924 +/
2925 @property SysTime timeLastModified();
2926
2927 /++
2928 Returns the _attributes of the file represented by this $(D DirEntry).
2929
2930 Note that the file _attributes on Windows and Posix systems are
2931 completely different. On, Windows, they're what is returned by
2932 $(D GetFileAttributes)
2933 $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx, GetFileAttributes)
2934 Whereas, an Posix systems, they're the $(D st_mode) value which is
2935 part of the $(D stat) struct gotten by calling $(D stat).
2936
2937 On Posix systems, if the file represented by this $(D DirEntry) is a
2938 symbolic link, then _attributes are the _attributes of the file
2939 pointed to by the symbolic link.
2940 +/
2941 @property uint attributes();
2942
2943 /++
2944 On Posix systems, if the file represented by this $(D DirEntry) is a
2945 symbolic link, then $(D linkAttributes) are the attributes of the
2946 symbolic link itself. Otherwise, $(D linkAttributes) is identical to
2947 $(D attributes).
2948
2949 On Windows, $(D linkAttributes) is identical to $(D attributes). It
2950 exists on Windows so that you don't have to special-case code for
2951 Windows when dealing with symbolic links.
2952 +/
2953 @property uint linkAttributes();
2954
2955 version (Windows)
2956 alias stat_t = void*;
2957
2958 /++
2959 $(BLUE This function is Posix-Only.)
2960
2961 The $(D stat) struct gotten from calling $(D stat).
2962 +/
2963 @property stat_t statBuf();
2964 }
2965 }
2966 else version (Windows)
2967 {
2968 struct DirEntry
2969 {
2970 public:
2971 alias name this;
2972
2973 this(string path)
2974 {
2975 import std.datetime.systime : FILETIMEToSysTime;
2976
2977 if (!path.exists())
2978 throw new FileException(path, "File does not exist");
2979
2980 _name = path;
2981
2982 with (getFileAttributesWin(path))
2983 {
2984 _size = makeUlong(nFileSizeLow, nFileSizeHigh);
2985 _timeCreated = FILETIMEToSysTime(&ftCreationTime);
2986 _timeLastAccessed = FILETIMEToSysTime(&ftLastAccessTime);
2987 _timeLastModified = FILETIMEToSysTime(&ftLastWriteTime);
2988 _attributes = dwFileAttributes;
2989 }
2990 }
2991
2992 private this(string path, in WIN32_FIND_DATAW *fd)
2993 {
2994 import core.stdc.wchar_ : wcslen;
2995 import std.conv : to;
2996 import std.datetime.systime : FILETIMEToSysTime;
2997 import std.path : buildPath;
2998
2999 size_t clength = wcslen(fd.cFileName.ptr);
3000 _name = buildPath(path, fd.cFileName[0 .. clength].to!string);
3001 _size = (cast(ulong) fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
3002 _timeCreated = FILETIMEToSysTime(&fd.ftCreationTime);
3003 _timeLastAccessed = FILETIMEToSysTime(&fd.ftLastAccessTime);
3004 _timeLastModified = FILETIMEToSysTime(&fd.ftLastWriteTime);
3005 _attributes = fd.dwFileAttributes;
3006 }
3007
3008 @property string name() const pure nothrow
3009 {
3010 return _name;
3011 }
3012
3013 @property bool isDir() const pure nothrow
3014 {
3015 return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
3016 }
3017
3018 @property bool isFile() const pure nothrow
3019 {
3020 //Are there no options in Windows other than directory and file?
3021 //If there are, then this probably isn't the best way to determine
3022 //whether this DirEntry is a file or not.
3023 return !isDir;
3024 }
3025
3026 @property bool isSymlink() const pure nothrow
3027 {
3028 return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
3029 }
3030
3031 @property ulong size() const pure nothrow
3032 {
3033 return _size;
3034 }
3035
3036 @property SysTime timeCreated() const pure nothrow
3037 {
3038 return cast(SysTime)_timeCreated;
3039 }
3040
3041 @property SysTime timeLastAccessed() const pure nothrow
3042 {
3043 return cast(SysTime)_timeLastAccessed;
3044 }
3045
3046 @property SysTime timeLastModified() const pure nothrow
3047 {
3048 return cast(SysTime)_timeLastModified;
3049 }
3050
3051 @property uint attributes() const pure nothrow
3052 {
3053 return _attributes;
3054 }
3055
3056 @property uint linkAttributes() const pure nothrow
3057 {
3058 return _attributes;
3059 }
3060
3061 private:
3062 string _name; /// The file or directory represented by this DirEntry.
3063
3064 SysTime _timeCreated; /// The time when the file was created.
3065 SysTime _timeLastAccessed; /// The time when the file was last accessed.
3066 SysTime _timeLastModified; /// The time when the file was last modified.
3067
3068 ulong _size; /// The size of the file in bytes.
3069 uint _attributes; /// The file attributes from WIN32_FIND_DATAW.
3070 }
3071 }
3072 else version (Posix)
3073 {
3074 struct DirEntry
3075 {
3076 public:
3077 alias name this;
3078
3079 this(string path)
3080 {
3081 if (!path.exists)
3082 throw new FileException(path, "File does not exist");
3083
3084 _name = path;
3085
3086 _didLStat = false;
3087 _didStat = false;
3088 _dTypeSet = false;
3089 }
3090
3091 private this(string path, core.sys.posix.dirent.dirent* fd)
3092 {
3093 import std.path : buildPath;
3094
3095 immutable len = core.stdc.string.strlen(fd.d_name.ptr);
3096 _name = buildPath(path, fd.d_name[0 .. len]);
3097
3098 _didLStat = false;
3099 _didStat = false;
3100
3101 //fd_d_type doesn't work for all file systems,
3102 //in which case the result is DT_UNKOWN. But we
3103 //can determine the correct type from lstat, so
3104 //we'll only set the dtype here if we could
3105 //correctly determine it (not lstat in the case
3106 //of DT_UNKNOWN in case we don't ever actually
3107 //need the dtype, thus potentially avoiding the
3108 //cost of calling lstat).
3109 static if (__traits(compiles, fd.d_type != DT_UNKNOWN))
3110 {
3111 if (fd.d_type != DT_UNKNOWN)
3112 {
3113 _dType = fd.d_type;
3114 _dTypeSet = true;
3115 }
3116 else
3117 _dTypeSet = false;
3118 }
3119 else
3120 {
3121 // e.g. Solaris does not have the d_type member
3122 _dTypeSet = false;
3123 }
3124 }
3125
3126 @property string name() const pure nothrow
3127 {
3128 return _name;
3129 }
3130
3131 @property bool isDir()
3132 {
3133 _ensureStatOrLStatDone();
3134
3135 return (_statBuf.st_mode & S_IFMT) == S_IFDIR;
3136 }
3137
3138 @property bool isFile()
3139 {
3140 _ensureStatOrLStatDone();
3141
3142 return (_statBuf.st_mode & S_IFMT) == S_IFREG;
3143 }
3144
3145 @property bool isSymlink()
3146 {
3147 _ensureLStatDone();
3148
3149 return (_lstatMode & S_IFMT) == S_IFLNK;
3150 }
3151
3152 @property ulong size()
3153 {
3154 _ensureStatDone();
3155 return _statBuf.st_size;
3156 }
3157
3158 @property SysTime timeStatusChanged()
3159 {
3160 _ensureStatDone();
3161
3162 return statTimeToStdTime!'c'(_statBuf);
3163 }
3164
3165 @property SysTime timeLastAccessed()
3166 {
3167 _ensureStatDone();
3168
3169 return statTimeToStdTime!'a'(_statBuf);
3170 }
3171
3172 @property SysTime timeLastModified()
3173 {
3174 _ensureStatDone();
3175
3176 return statTimeToStdTime!'m'(_statBuf);
3177 }
3178
3179 @property uint attributes()
3180 {
3181 _ensureStatDone();
3182
3183 return _statBuf.st_mode;
3184 }
3185
3186 @property uint linkAttributes()
3187 {
3188 _ensureLStatDone();
3189
3190 return _lstatMode;
3191 }
3192
3193 @property stat_t statBuf()
3194 {
3195 _ensureStatDone();
3196
3197 return _statBuf;
3198 }
3199
3200 private:
3201 /++
3202 This is to support lazy evaluation, because doing stat's is
3203 expensive and not always needed.
3204 +/
3205 void _ensureStatDone() @safe
3206 {
3207 import std.exception : enforce;
3208
3209 static auto trustedStat(in char[] path, stat_t* buf) @trusted
3210 {
3211 return stat(path.tempCString(), buf);
3212 }
3213 if (_didStat)
3214 return;
3215
3216 enforce(trustedStat(_name, &_statBuf) == 0,
3217 "Failed to stat file `" ~ _name ~ "'");
3218
3219 _didStat = true;
3220 }
3221
3222 /++
3223 This is to support lazy evaluation, because doing stat's is
3224 expensive and not always needed.
3225
3226 Try both stat and lstat for isFile and isDir
3227 to detect broken symlinks.
3228 +/
3229 void _ensureStatOrLStatDone()
3230 {
3231 if (_didStat)
3232 return;
3233
3234 if ( stat(_name.tempCString(), &_statBuf) != 0 )
3235 {
3236 _ensureLStatDone();
3237
3238 _statBuf = stat_t.init;
3239 _statBuf.st_mode = S_IFLNK;
3240 }
3241 else
3242 {
3243 _didStat = true;
3244 }
3245 }
3246
3247 /++
3248 This is to support lazy evaluation, because doing stat's is
3249 expensive and not always needed.
3250 +/
3251 void _ensureLStatDone()
3252 {
3253 import std.exception : enforce;
3254
3255 if (_didLStat)
3256 return;
3257
3258 stat_t statbuf = void;
3259
3260 enforce(lstat(_name.tempCString(), &statbuf) == 0,
3261 "Failed to stat file `" ~ _name ~ "'");
3262
3263 _lstatMode = statbuf.st_mode;
3264
3265 _dTypeSet = true;
3266 _didLStat = true;
3267 }
3268
3269 string _name; /// The file or directory represented by this DirEntry.
3270
3271 stat_t _statBuf = void; /// The result of stat().
3272 uint _lstatMode; /// The stat mode from lstat().
3273 ubyte _dType; /// The type of the file.
3274
3275 bool _didLStat = false; /// Whether lstat() has been called for this DirEntry.
3276 bool _didStat = false; /// Whether stat() has been called for this DirEntry.
3277 bool _dTypeSet = false; /// Whether the dType of the file has been set.
3278 }
3279 }
3280
3281 @system unittest
3282 {
3283 version (Windows)
3284 {
3285 if ("C:\\Program Files\\".exists)
3286 {
3287 auto de = DirEntry("C:\\Program Files\\");
3288 assert(!de.isFile);
3289 assert(de.isDir);
3290 assert(!de.isSymlink);
3291 }
3292
3293 if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
3294 {
3295 auto de = DirEntry("C:\\Documents and Settings\\");
3296 assert(de.isSymlink);
3297 }
3298
3299 if ("C:\\Windows\\system.ini".exists)
3300 {
3301 auto de = DirEntry("C:\\Windows\\system.ini");
3302 assert(de.isFile);
3303 assert(!de.isDir);
3304 assert(!de.isSymlink);
3305 }
3306 }
3307 else version (Posix)
3308 {
3309 import std.exception : assertThrown;
3310
3311 if (system_directory.exists)
3312 {
3313 {
3314 auto de = DirEntry(system_directory);
3315 assert(!de.isFile);
3316 assert(de.isDir);
3317 assert(!de.isSymlink);
3318 }
3319
3320 immutable symfile = deleteme ~ "_slink\0";
3321 scope(exit) if (symfile.exists) symfile.remove();
3322
3323 core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
3324
3325 {
3326 auto de = DirEntry(symfile);
3327 assert(!de.isFile);
3328 assert(de.isDir);
3329 assert(de.isSymlink);
3330 }
3331
3332 symfile.remove();
3333 core.sys.posix.unistd.symlink((deleteme ~ "_broken_symlink\0").ptr, symfile.ptr);
3334
3335 {
3336 //Issue 8298
3337 DirEntry de = DirEntry(symfile);
3338
3339 assert(!de.isFile);
3340 assert(!de.isDir);
3341 assert(de.isSymlink);
3342 assertThrown(de.size);
3343 assertThrown(de.timeStatusChanged);
3344 assertThrown(de.timeLastAccessed);
3345 assertThrown(de.timeLastModified);
3346 assertThrown(de.attributes);
3347 assertThrown(de.statBuf);
3348 assert(symfile.exists);
3349 symfile.remove();
3350 }
3351 }
3352
3353 if (system_file.exists)
3354 {
3355 auto de = DirEntry(system_file);
3356 assert(de.isFile);
3357 assert(!de.isDir);
3358 assert(!de.isSymlink);
3359 }
3360 }
3361 }
3362
3363 alias PreserveAttributes = Flag!"preserveAttributes";
3364
3365 version (StdDdoc)
3366 {
3367 /// Defaults to $(D Yes.preserveAttributes) on Windows, and the opposite on all other platforms.
3368 PreserveAttributes preserveAttributesDefault;
3369 }
3370 else version (Windows)
3371 {
3372 enum preserveAttributesDefault = Yes.preserveAttributes;
3373 }
3374 else
3375 {
3376 enum preserveAttributesDefault = No.preserveAttributes;
3377 }
3378
3379 /***************************************************
3380 Copy file $(D from) _to file $(D to). File timestamps are preserved.
3381 File attributes are preserved, if $(D preserve) equals $(D Yes.preserveAttributes).
3382 On Windows only $(D Yes.preserveAttributes) (the default on Windows) is supported.
3383 If the target file exists, it is overwritten.
3384
3385 Params:
3386 from = string or range of characters representing the existing file name
3387 to = string or range of characters representing the target file name
3388 preserve = whether to _preserve the file attributes
3389
3390 Throws: $(D FileException) on error.
3391 */
3392 void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault)
3393 if (isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) && !isConvertibleToString!RF &&
3394 isInputRange!RT && !isInfinite!RT && isSomeChar!(ElementEncodingType!RT) && !isConvertibleToString!RT)
3395 {
3396 // Place outside of @trusted block
3397 auto fromz = from.tempCString!FSChar();
3398 auto toz = to.tempCString!FSChar();
3399
3400 static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char))
3401 alias f = from;
3402 else
3403 enum string f = null;
3404
3405 static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char))
3406 alias t = to;
3407 else
3408 enum string t = null;
3409
3410 copyImpl(f, t, fromz, toz, preserve);
3411 }
3412
3413 /// ditto
3414 void copy(RF, RT)(auto ref RF from, auto ref RT to, PreserveAttributes preserve = preserveAttributesDefault)
3415 if (isConvertibleToString!RF || isConvertibleToString!RT)
3416 {
3417 import std.meta : staticMap;
3418 alias Types = staticMap!(convertToString, RF, RT);
3419 copy!Types(from, to, preserve);
3420 }
3421
3422 @safe unittest // issue 15319
3423 {
3424 assert(__traits(compiles, copy("from.txt", "to.txt")));
3425 }
3426
3427 private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz,
3428 PreserveAttributes preserve) @trusted
3429 {
3430 version (Windows)
3431 {
3432 assert(preserve == Yes.preserveAttributes);
3433 immutable result = CopyFileW(fromz, toz, false);
3434 if (!result)
3435 {
3436 import core.stdc.wchar_ : wcslen;
3437 import std.conv : to;
3438
3439 if (!t)
3440 t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
3441
3442 throw new FileException(t);
3443 }
3444 }
3445 else version (Posix)
3446 {
3447 static import core.stdc.stdio;
3448 import std.conv : to, octal;
3449
3450 immutable fdr = core.sys.posix.fcntl.open(fromz, O_RDONLY);
3451 cenforce(fdr != -1, f, fromz);
3452 scope(exit) core.sys.posix.unistd.close(fdr);
3453
3454 stat_t statbufr = void;
3455 cenforce(fstat(fdr, &statbufr) == 0, f, fromz);
3456 //cenforce(core.sys.posix.sys.stat.fstat(fdr, &statbufr) == 0, f, fromz);
3457
3458 immutable fdw = core.sys.posix.fcntl.open(toz,
3459 O_CREAT | O_WRONLY, octal!666);
3460 cenforce(fdw != -1, t, toz);
3461 {
3462 scope(failure) core.sys.posix.unistd.close(fdw);
3463
3464 stat_t statbufw = void;
3465 cenforce(fstat(fdw, &statbufw) == 0, t, toz);
3466 if (statbufr.st_dev == statbufw.st_dev && statbufr.st_ino == statbufw.st_ino)
3467 throw new FileException(t, "Source and destination are the same file");
3468 }
3469
3470 scope(failure) core.stdc.stdio.remove(toz);
3471 {
3472 scope(failure) core.sys.posix.unistd.close(fdw);
3473 cenforce(ftruncate(fdw, 0) == 0, t, toz);
3474
3475 auto BUFSIZ = 4096u * 16;
3476 auto buf = core.stdc.stdlib.malloc(BUFSIZ);
3477 if (!buf)
3478 {
3479 BUFSIZ = 4096;
3480 buf = core.stdc.stdlib.malloc(BUFSIZ);
3481 if (!buf)
3482 {
3483 import core.exception : onOutOfMemoryError;
3484 onOutOfMemoryError();
3485 }
3486 }
3487 scope(exit) core.stdc.stdlib.free(buf);
3488
3489 for (auto size = statbufr.st_size; size; )
3490 {
3491 immutable toxfer = (size > BUFSIZ) ? BUFSIZ : cast(size_t) size;
3492 cenforce(
3493 core.sys.posix.unistd.read(fdr, buf, toxfer) == toxfer
3494 && core.sys.posix.unistd.write(fdw, buf, toxfer) == toxfer,
3495 f, fromz);
3496 assert(size >= toxfer);
3497 size -= toxfer;
3498 }
3499 if (preserve)
3500 cenforce(fchmod(fdw, to!mode_t(statbufr.st_mode)) == 0, f, fromz);
3501 }
3502
3503 cenforce(core.sys.posix.unistd.close(fdw) != -1, f, fromz);
3504
3505 utimbuf utim = void;
3506 utim.actime = cast(time_t) statbufr.st_atime;
3507 utim.modtime = cast(time_t) statbufr.st_mtime;
3508
3509 cenforce(utime(toz, &utim) != -1, f, fromz);
3510 }
3511 }
3512
3513 @safe unittest
3514 {
3515 import std.algorithm, std.file; // issue 14817
3516 auto t1 = deleteme, t2 = deleteme~"2";
3517 scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
3518 write(t1, "11");
3519 copy(t1, t2);
3520 assert(readText(t2) == "11");
3521 write(t1, "2");
3522 copy(t1, t2);
3523 assert(readText(t2) == "2");
3524
3525 import std.utf : byChar;
3526 copy(t1.byChar, t2.byChar);
3527 assert(readText(t2.byChar) == "2");
3528 }
3529
3530 @safe version (Posix) @safe unittest //issue 11434
3531 {
3532 import std.conv : octal;
3533 auto t1 = deleteme, t2 = deleteme~"2";
3534 scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
3535 write(t1, "1");
3536 setAttributes(t1, octal!767);
3537 copy(t1, t2, Yes.preserveAttributes);
3538 assert(readText(t2) == "1");
3539 assert(getAttributes(t2) == octal!100767);
3540 }
3541
3542 @safe unittest // issue 15865
3543 {
3544 import std.exception : assertThrown;
3545 auto t = deleteme;
3546 write(t, "a");
3547 scope(exit) t.remove();
3548 assertThrown!FileException(copy(t, t));
3549 assert(readText(t) == "a");
3550 }
3551
3552 /++
3553 Remove directory and all of its content and subdirectories,
3554 recursively.
3555
3556 Throws:
3557 $(D FileException) if there is an error (including if the given
3558 file is not a directory).
3559 +/
3560 void rmdirRecurse(in char[] pathname)
3561 {
3562 //No references to pathname will be kept after rmdirRecurse,
3563 //so the cast is safe
3564 rmdirRecurse(DirEntry(cast(string) pathname));
3565 }
3566
3567 /++
3568 Remove directory and all of its content and subdirectories,
3569 recursively.
3570
3571 Throws:
3572 $(D FileException) if there is an error (including if the given
3573 file is not a directory).
3574 +/
3575 void rmdirRecurse(ref DirEntry de)
3576 {
3577 if (!de.isDir)
3578 throw new FileException(de.name, "Not a directory");
3579
3580 if (de.isSymlink)
3581 {
3582 version (Windows)
3583 rmdir(de.name);
3584 else
3585 remove(de.name);
3586 }
3587 else
3588 {
3589 // all children, recursively depth-first
3590 foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false))
3591 {
3592 attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name);
3593 }
3594
3595 // the dir itself
3596 rmdir(de.name);
3597 }
3598 }
3599 ///ditto
3600 //Note, without this overload, passing an RValue DirEntry still works, but
3601 //actually fully reconstructs a DirEntry inside the
3602 //"rmdirRecurse(in char[] pathname)" implementation. That is needlessly
3603 //expensive.
3604 //A DirEntry is a bit big (72B), so keeping the "by ref" signature is desirable.
3605 void rmdirRecurse(DirEntry de)
3606 {
3607 rmdirRecurse(de);
3608 }
3609
3610 version (Windows) @system unittest
3611 {
3612 import std.exception : enforce;
3613 auto d = deleteme ~ r".dir\a\b\c\d\e\f\g";
3614 mkdirRecurse(d);
3615 rmdirRecurse(deleteme ~ ".dir");
3616 enforce(!exists(deleteme ~ ".dir"));
3617 }
3618
3619 version (Posix) @system unittest
3620 {
3621 import std.exception : enforce, collectException;
3622 import std.process : executeShell;
3623 collectException(rmdirRecurse(deleteme));
3624 auto d = deleteme~"/a/b/c/d/e/f/g";
3625 enforce(collectException(mkdir(d)));
3626 mkdirRecurse(d);
3627 core.sys.posix.unistd.symlink((deleteme~"/a/b/c\0").ptr,
3628 (deleteme~"/link\0").ptr);
3629 rmdirRecurse(deleteme~"/link");
3630 enforce(exists(d));
3631 rmdirRecurse(deleteme);
3632 enforce(!exists(deleteme));
3633
3634 d = deleteme~"/a/b/c/d/e/f/g";
3635 mkdirRecurse(d);
3636 version (Android) string link_cmd = "ln -s ";
3637 else string link_cmd = "ln -sf ";
3638 executeShell(link_cmd~deleteme~"/a/b/c "~deleteme~"/link");
3639 rmdirRecurse(deleteme);
3640 enforce(!exists(deleteme));
3641 }
3642
3643 @system unittest
3644 {
3645 void[] buf;
3646
3647 buf = new void[10];
3648 (cast(byte[]) buf)[] = 3;
3649 string unit_file = deleteme ~ "-unittest_write.tmp";
3650 if (exists(unit_file)) remove(unit_file);
3651 write(unit_file, buf);
3652 void[] buf2 = read(unit_file);
3653 assert(buf == buf2);
3654
3655 string unit2_file = deleteme ~ "-unittest_write2.tmp";
3656 copy(unit_file, unit2_file);
3657 buf2 = read(unit2_file);
3658 assert(buf == buf2);
3659
3660 remove(unit_file);
3661 assert(!exists(unit_file));
3662 remove(unit2_file);
3663 assert(!exists(unit2_file));
3664 }
3665
3666 /**
3667 * Dictates directory spanning policy for $(D_PARAM dirEntries) (see below).
3668 */
3669 enum SpanMode
3670 {
3671 /** Only spans one directory. */
3672 shallow,
3673 /** Spans the directory in
3674 $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Post-order,
3675 _depth-first $(B post)-order), i.e. the content of any
3676 subdirectory is spanned before that subdirectory itself. Useful
3677 e.g. when recursively deleting files. */
3678 depth,
3679 /** Spans the directory in
3680 $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Pre-order, depth-first
3681 $(B pre)-order), i.e. the content of any subdirectory is spanned
3682 right after that subdirectory itself.
3683
3684 Note that $(D SpanMode.breadth) will not result in all directory
3685 members occurring before any subdirectory members, i.e. it is not
3686 _true
3687 $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Breadth-first_search,
3688 _breadth-first traversal).
3689 */
3690 breadth,
3691 }
3692
3693 private struct DirIteratorImpl
3694 {
3695 import std.array : Appender, appender;
3696 SpanMode _mode;
3697 // Whether we should follow symlinked directories while iterating.
3698 // It also indicates whether we should avoid functions which call
3699 // stat (since we should only need lstat in this case and it would
3700 // be more efficient to not call stat in addition to lstat).
3701 bool _followSymlink;
3702 DirEntry _cur;
3703 Appender!(DirHandle[]) _stack;
3704 Appender!(DirEntry[]) _stashed; //used in depth first mode
3705 //stack helpers
3706 void pushExtra(DirEntry de){ _stashed.put(de); }
3707 //ditto
3708 bool hasExtra(){ return !_stashed.data.empty; }
3709 //ditto
3710 DirEntry popExtra()
3711 {
3712 DirEntry de;
3713 de = _stashed.data[$-1];
3714 _stashed.shrinkTo(_stashed.data.length - 1);
3715 return de;
3716
3717 }
3718 version (Windows)
3719 {
3720 struct DirHandle
3721 {
3722 string dirpath;
3723 HANDLE h;
3724 }
3725
3726 bool stepIn(string directory)
3727 {
3728 import std.path : chainPath;
3729
3730 auto search_pattern = chainPath(directory, "*.*");
3731 WIN32_FIND_DATAW findinfo;
3732 HANDLE h = FindFirstFileW(search_pattern.tempCString!FSChar(), &findinfo);
3733 cenforce(h != INVALID_HANDLE_VALUE, directory);
3734 _stack.put(DirHandle(directory, h));
3735 return toNext(false, &findinfo);
3736 }
3737
3738 bool next()
3739 {
3740 if (_stack.data.empty)
3741 return false;
3742 WIN32_FIND_DATAW findinfo;
3743 return toNext(true, &findinfo);
3744 }
3745
3746 bool toNext(bool fetch, WIN32_FIND_DATAW* findinfo)
3747 {
3748 import core.stdc.wchar_ : wcscmp;
3749
3750 if (fetch)
3751 {
3752 if (FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE)
3753 {
3754 popDirStack();
3755 return false;
3756 }
3757 }
3758 while ( wcscmp(findinfo.cFileName.ptr, ".") == 0
3759 || wcscmp(findinfo.cFileName.ptr, "..") == 0)
3760 if (FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE)
3761 {
3762 popDirStack();
3763 return false;
3764 }
3765 _cur = DirEntry(_stack.data[$-1].dirpath, findinfo);
3766 return true;
3767 }
3768
3769 void popDirStack()
3770 {
3771 assert(!_stack.data.empty);
3772 FindClose(_stack.data[$-1].h);
3773 _stack.shrinkTo(_stack.data.length-1);
3774 }
3775
3776 void releaseDirStack()
3777 {
3778 foreach ( d; _stack.data)
3779 FindClose(d.h);
3780 }
3781
3782 bool mayStepIn()
3783 {
3784 return _followSymlink ? _cur.isDir : _cur.isDir && !_cur.isSymlink;
3785 }
3786 }
3787 else version (Posix)
3788 {
3789 struct DirHandle
3790 {
3791 string dirpath;
3792 DIR* h;
3793 }
3794
3795 bool stepIn(string directory)
3796 {
3797 auto h = directory.length ? opendir(directory.tempCString()) : opendir(".");
3798 cenforce(h, directory);
3799 _stack.put(DirHandle(directory, h));
3800 return next();
3801 }
3802
3803 bool next()
3804 {
3805 if (_stack.data.empty)
3806 return false;
3807 for (dirent* fdata; (fdata = readdir(_stack.data[$-1].h)) != null; )
3808 {
3809 // Skip "." and ".."
3810 if (core.stdc.string.strcmp(fdata.d_name.ptr, ".") &&
3811 core.stdc.string.strcmp(fdata.d_name.ptr, "..") )
3812 {
3813 _cur = DirEntry(_stack.data[$-1].dirpath, fdata);
3814 return true;
3815 }
3816 }
3817 popDirStack();
3818 return false;
3819 }
3820
3821 void popDirStack()
3822 {
3823 assert(!_stack.data.empty);
3824 closedir(_stack.data[$-1].h);
3825 _stack.shrinkTo(_stack.data.length-1);
3826 }
3827
3828 void releaseDirStack()
3829 {
3830 foreach ( d; _stack.data)
3831 closedir(d.h);
3832 }
3833
3834 bool mayStepIn()
3835 {
3836 return _followSymlink ? _cur.isDir : attrIsDir(_cur.linkAttributes);
3837 }
3838 }
3839
3840 this(R)(R pathname, SpanMode mode, bool followSymlink)
3841 if (isInputRange!R && isSomeChar!(ElementEncodingType!R))
3842 {
3843 _mode = mode;
3844 _followSymlink = followSymlink;
3845 _stack = appender(cast(DirHandle[])[]);
3846 if (_mode == SpanMode.depth)
3847 _stashed = appender(cast(DirEntry[])[]);
3848
3849 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
3850 alias pathnameStr = pathname;
3851 else
3852 {
3853 import std.array : array;
3854 string pathnameStr = pathname.array;
3855 }
3856 if (stepIn(pathnameStr))
3857 {
3858 if (_mode == SpanMode.depth)
3859 while (mayStepIn())
3860 {
3861 auto thisDir = _cur;
3862 if (stepIn(_cur.name))
3863 {
3864 pushExtra(thisDir);
3865 }
3866 else
3867 break;
3868 }
3869 }
3870 }
3871 @property bool empty(){ return _stashed.data.empty && _stack.data.empty; }
3872 @property DirEntry front(){ return _cur; }
3873 void popFront()
3874 {
3875 switch (_mode)
3876 {
3877 case SpanMode.depth:
3878 if (next())
3879 {
3880 while (mayStepIn())
3881 {
3882 auto thisDir = _cur;
3883 if (stepIn(_cur.name))
3884 {
3885 pushExtra(thisDir);
3886 }
3887 else
3888 break;
3889 }
3890 }
3891 else if (hasExtra())
3892 _cur = popExtra();
3893 break;
3894 case SpanMode.breadth:
3895 if (mayStepIn())
3896 {
3897 if (!stepIn(_cur.name))
3898 while (!empty && !next()){}
3899 }
3900 else
3901 while (!empty && !next()){}
3902 break;
3903 default:
3904 next();
3905 }
3906 }
3907
3908 ~this()
3909 {
3910 releaseDirStack();
3911 }
3912 }
3913
3914 struct DirIterator
3915 {
3916 private:
3917 RefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl;
3918 this(string pathname, SpanMode mode, bool followSymlink)
3919 {
3920 impl = typeof(impl)(pathname, mode, followSymlink);
3921 }
3922 public:
3923 @property bool empty(){ return impl.empty; }
3924 @property DirEntry front(){ return impl.front; }
3925 void popFront(){ impl.popFront(); }
3926
3927 }
3928 /++
3929 Returns an input range of $(D DirEntry) that lazily iterates a given directory,
3930 also provides two ways of foreach iteration. The iteration variable can be of
3931 type $(D string) if only the name is needed, or $(D DirEntry)
3932 if additional details are needed. The span _mode dictates how the
3933 directory is traversed. The name of each iterated directory entry
3934 contains the absolute _path.
3935
3936 Params:
3937 path = The directory to iterate over.
3938 If empty, the current directory will be iterated.
3939
3940 pattern = Optional string with wildcards, such as $(RED
3941 "*.d"). When present, it is used to filter the
3942 results by their file name. The supported wildcard
3943 strings are described under $(REF globMatch,
3944 std,_path).
3945
3946 mode = Whether the directory's sub-directories should be
3947 iterated in depth-first port-order ($(LREF depth)),
3948 depth-first pre-order ($(LREF breadth)), or not at all
3949 ($(LREF shallow)).
3950
3951 followSymlink = Whether symbolic links which point to directories
3952 should be treated as directories and their contents
3953 iterated over.
3954
3955 Throws:
3956 $(D FileException) if the directory does not exist.
3957
3958 Example:
3959 --------------------
3960 // Iterate a directory in depth
3961 foreach (string name; dirEntries("destroy/me", SpanMode.depth))
3962 {
3963 remove(name);
3964 }
3965
3966 // Iterate the current directory in breadth
3967 foreach (string name; dirEntries("", SpanMode.breadth))
3968 {
3969 writeln(name);
3970 }
3971
3972 // Iterate a directory and get detailed info about it
3973 foreach (DirEntry e; dirEntries("dmd-testing", SpanMode.breadth))
3974 {
3975 writeln(e.name, "\t", e.size);
3976 }
3977
3978 // Iterate over all *.d files in current directory and all its subdirectories
3979 auto dFiles = dirEntries("", SpanMode.depth).filter!(f => f.name.endsWith(".d"));
3980 foreach (d; dFiles)
3981 writeln(d.name);
3982
3983 // Hook it up with std.parallelism to compile them all in parallel:
3984 foreach (d; parallel(dFiles, 1)) //passes by 1 file to each thread
3985 {
3986 string cmd = "dmd -c " ~ d.name;
3987 writeln(cmd);
3988 std.process.system(cmd);
3989 }
3990
3991 // Iterate over all D source files in current directory and all its
3992 // subdirectories
3993 auto dFiles = dirEntries("","*.{d,di}",SpanMode.depth);
3994 foreach (d; dFiles)
3995 writeln(d.name);
3996 --------------------
3997 +/
3998 auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
3999 {
4000 return DirIterator(path, mode, followSymlink);
4001 }
4002
4003 /// Duplicate functionality of D1's $(D std.file.listdir()):
4004 @safe unittest
4005 {
4006 string[] listdir(string pathname)
4007 {
4008 import std.algorithm;
4009 import std.array;
4010 import std.file;
4011 import std.path;
4012
4013 return std.file.dirEntries(pathname, SpanMode.shallow)
4014 .filter!(a => a.isFile)
4015 .map!(a => std.path.baseName(a.name))
4016 .array;
4017 }
4018
4019 void main(string[] args)
4020 {
4021 import std.stdio;
4022
4023 string[] files = listdir(args[1]);
4024 writefln("%s", files);
4025 }
4026 }
4027
4028 @system unittest
4029 {
4030 import std.algorithm.comparison : equal;
4031 import std.algorithm.iteration : map;
4032 import std.algorithm.searching : startsWith;
4033 import std.array : array;
4034 import std.conv : to;
4035 import std.path : dirEntries, buildPath, absolutePath;
4036 import std.process : thisProcessID;
4037 import std.range.primitives : walkLength;
4038
4039 version (Android)
4040 string testdir = deleteme; // This has to be an absolute path when
4041 // called from a shared library on Android,
4042 // ie an apk
4043 else
4044 string testdir = "deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID); // needs to be relative
4045 mkdirRecurse(buildPath(testdir, "somedir"));
4046 scope(exit) rmdirRecurse(testdir);
4047 write(buildPath(testdir, "somefile"), null);
4048 write(buildPath(testdir, "somedir", "somedeepfile"), null);
4049
4050 // testing range interface
4051 size_t equalEntries(string relpath, SpanMode mode)
4052 {
4053 import std.exception : enforce;
4054 auto len = enforce(walkLength(dirEntries(absolutePath(relpath), mode)));
4055 assert(walkLength(dirEntries(relpath, mode)) == len);
4056 assert(equal(
4057 map!(a => absolutePath(a.name))(dirEntries(relpath, mode)),
4058 map!(a => a.name)(dirEntries(absolutePath(relpath), mode))));
4059 return len;
4060 }
4061
4062 assert(equalEntries(testdir, SpanMode.shallow) == 2);
4063 assert(equalEntries(testdir, SpanMode.depth) == 3);
4064 assert(equalEntries(testdir, SpanMode.breadth) == 3);
4065
4066 // testing opApply
4067 foreach (string name; dirEntries(testdir, SpanMode.breadth))
4068 {
4069 //writeln(name);
4070 assert(name.startsWith(testdir));
4071 }
4072 foreach (DirEntry e; dirEntries(absolutePath(testdir), SpanMode.breadth))
4073 {
4074 //writeln(name);
4075 assert(e.isFile || e.isDir, e.name);
4076 }
4077
4078 //issue 7264
4079 foreach (string name; dirEntries(testdir, "*.d", SpanMode.breadth))
4080 {
4081
4082 }
4083 foreach (entry; dirEntries(testdir, SpanMode.breadth))
4084 {
4085 static assert(is(typeof(entry) == DirEntry));
4086 }
4087 //issue 7138
4088 auto a = array(dirEntries(testdir, SpanMode.shallow));
4089
4090 // issue 11392
4091 auto dFiles = dirEntries(testdir, SpanMode.shallow);
4092 foreach (d; dFiles){}
4093
4094 // issue 15146
4095 dirEntries("", SpanMode.shallow).walkLength();
4096 }
4097
4098 /// Ditto
4099 auto dirEntries(string path, string pattern, SpanMode mode,
4100 bool followSymlink = true)
4101 {
4102 import std.algorithm.iteration : filter;
4103 import std.path : globMatch, baseName;
4104
4105 bool f(DirEntry de) { return globMatch(baseName(de.name), pattern); }
4106 return filter!f(DirIterator(path, mode, followSymlink));
4107 }
4108
4109 @system unittest
4110 {
4111 import std.stdio : writefln;
4112 immutable dpath = deleteme ~ "_dir";
4113 immutable fpath = deleteme ~ "_file";
4114 immutable sdpath = deleteme ~ "_sdir";
4115 immutable sfpath = deleteme ~ "_sfile";
4116 scope(exit)
4117 {
4118 if (dpath.exists) rmdirRecurse(dpath);
4119 if (fpath.exists) remove(fpath);
4120 if (sdpath.exists) remove(sdpath);
4121 if (sfpath.exists) remove(sfpath);
4122 }
4123
4124 mkdir(dpath);
4125 write(fpath, "hello world");
4126 version (Posix)
4127 {
4128 core.sys.posix.unistd.symlink((dpath ~ '\0').ptr, (sdpath ~ '\0').ptr);
4129 core.sys.posix.unistd.symlink((fpath ~ '\0').ptr, (sfpath ~ '\0').ptr);
4130 }
4131
4132 static struct Flags { bool dir, file, link; }
4133 auto tests = [dpath : Flags(true), fpath : Flags(false, true)];
4134 version (Posix)
4135 {
4136 tests[sdpath] = Flags(true, false, true);
4137 tests[sfpath] = Flags(false, true, true);
4138 }
4139
4140 auto past = Clock.currTime() - 2.seconds;
4141 auto future = past + 4.seconds;
4142
4143 foreach (path, flags; tests)
4144 {
4145 auto de = DirEntry(path);
4146 assert(de.name == path);
4147 assert(de.isDir == flags.dir);
4148 assert(de.isFile == flags.file);
4149 assert(de.isSymlink == flags.link);
4150
4151 assert(de.isDir == path.isDir);
4152 assert(de.isFile == path.isFile);
4153 assert(de.isSymlink == path.isSymlink);
4154 assert(de.size == path.getSize());
4155 assert(de.attributes == getAttributes(path));
4156 assert(de.linkAttributes == getLinkAttributes(path));
4157
4158 scope(failure) writefln("[%s] [%s] [%s] [%s]", past, de.timeLastAccessed, de.timeLastModified, future);
4159 assert(de.timeLastAccessed > past);
4160 assert(de.timeLastAccessed < future);
4161 assert(de.timeLastModified > past);
4162 assert(de.timeLastModified < future);
4163
4164 assert(attrIsDir(de.attributes) == flags.dir);
4165 assert(attrIsDir(de.linkAttributes) == (flags.dir && !flags.link));
4166 assert(attrIsFile(de.attributes) == flags.file);
4167 assert(attrIsFile(de.linkAttributes) == (flags.file && !flags.link));
4168 assert(!attrIsSymlink(de.attributes));
4169 assert(attrIsSymlink(de.linkAttributes) == flags.link);
4170
4171 version (Windows)
4172 {
4173 assert(de.timeCreated > past);
4174 assert(de.timeCreated < future);
4175 }
4176 else version (Posix)
4177 {
4178 assert(de.timeStatusChanged > past);
4179 assert(de.timeStatusChanged < future);
4180 assert(de.attributes == de.statBuf.st_mode);
4181 }
4182 }
4183 }
4184
4185
4186 /**
4187 * Reads a file line by line and parses the line into a single value or a
4188 * $(REF Tuple, std,typecons) of values depending on the length of `Types`.
4189 * The lines are parsed using the specified format string. The format string is
4190 * passed to $(REF formattedRead, std,_format), and therefore must conform to the
4191 * _format string specification outlined in $(MREF std, _format).
4192 *
4193 * Params:
4194 * Types = the types that each of the elements in the line should be returned as
4195 * filename = the name of the file to read
4196 * format = the _format string to use when reading
4197 *
4198 * Returns:
4199 * If only one type is passed, then an array of that type. Otherwise, an
4200 * array of $(REF Tuple, std,typecons)s.
4201 *
4202 * Throws:
4203 * `Exception` if the format string is malformed. Also, throws `Exception`
4204 * if any of the lines in the file are not fully consumed by the call
4205 * to $(REF formattedRead, std,_format). Meaning that no empty lines or lines
4206 * with extra characters are allowed.
4207 */
4208 Select!(Types.length == 1, Types[0][], Tuple!(Types)[])
4209 slurp(Types...)(string filename, in char[] format)
4210 {
4211 import std.array : appender;
4212 import std.conv : text;
4213 import std.exception : enforce;
4214 import std.format : formattedRead;
4215 import std.stdio : File;
4216
4217 auto app = appender!(typeof(return))();
4218 ElementType!(typeof(return)) toAdd;
4219 auto f = File(filename);
4220 scope(exit) f.close();
4221 foreach (line; f.byLine())
4222 {
4223 formattedRead(line, format, &toAdd);
4224 enforce(line.empty,
4225 text("Trailing characters at the end of line: `", line,
4226 "'"));
4227 app.put(toAdd);
4228 }
4229 return app.data;
4230 }
4231
4232 ///
4233 @system unittest
4234 {
4235 import std.typecons : tuple;
4236
4237 scope(exit)
4238 {
4239 assert(exists(deleteme));
4240 remove(deleteme);
4241 }
4242
4243 write(deleteme, "12 12.25\n345 1.125"); // deleteme is the name of a temporary file
4244
4245 // Load file; each line is an int followed by comma, whitespace and a
4246 // double.
4247 auto a = slurp!(int, double)(deleteme, "%s %s");
4248 assert(a.length == 2);
4249 assert(a[0] == tuple(12, 12.25));
4250 assert(a[1] == tuple(345, 1.125));
4251 }
4252
4253
4254 /**
4255 Returns the path to a directory for temporary files.
4256
4257 On Windows, this function returns the result of calling the Windows API function
4258 $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992.aspx, $(D GetTempPath)).
4259
4260 On POSIX platforms, it searches through the following list of directories
4261 and returns the first one which is found to exist:
4262 $(OL
4263 $(LI The directory given by the $(D TMPDIR) environment variable.)
4264 $(LI The directory given by the $(D TEMP) environment variable.)
4265 $(LI The directory given by the $(D TMP) environment variable.)
4266 $(LI $(D /tmp))
4267 $(LI $(D /var/tmp))
4268 $(LI $(D /usr/tmp))
4269 )
4270
4271 On all platforms, $(D tempDir) returns $(D ".") on failure, representing
4272 the current working directory.
4273
4274 The return value of the function is cached, so the procedures described
4275 above will only be performed the first time the function is called. All
4276 subsequent runs will return the same string, regardless of whether
4277 environment variables and directory structures have changed in the
4278 meantime.
4279
4280 The POSIX $(D tempDir) algorithm is inspired by Python's
4281 $(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, $(D tempfile.tempdir)).
4282 */
4283 string tempDir() @trusted
4284 {
4285 static string cache;
4286 if (cache is null)
4287 {
4288 version (Windows)
4289 {
4290 import std.conv : to;
4291 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992(v=vs.85).aspx
4292 wchar[MAX_PATH + 2] buf;
4293 DWORD len = GetTempPathW(buf.length, buf.ptr);
4294 if (len) cache = buf[0 .. len].to!string;
4295 }
4296 else version (Posix)
4297 {
4298 import std.process : environment;
4299 // This function looks through the list of alternative directories
4300 // and returns the first one which exists and is a directory.
4301 static string findExistingDir(T...)(lazy T alternatives)
4302 {
4303 foreach (dir; alternatives)
4304 if (!dir.empty && exists(dir)) return dir;
4305 return null;
4306 }
4307
4308 cache = findExistingDir(environment.get("TMPDIR"),
4309 environment.get("TEMP"),
4310 environment.get("TMP"),
4311 "/tmp",
4312 "/var/tmp",
4313 "/usr/tmp");
4314 }
4315 else static assert(false, "Unsupported platform");
4316
4317 if (cache is null) cache = getcwd();
4318 }
4319 return cache;
4320 }