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
version(Windows)92 version (Windows)
93 {
94 import core.sys.windows.windows, std.windows.syserror;
95 }
version(Posix)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
version(Windows)105 version (Windows)
106 {
107 private alias FSChar = wchar;
108 }
version(Posix)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
deleteme()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 {
getTestAliasedString137 string get() @safe @nogc pure nothrow { return _s; }
138 alias get this;
139 @disable this(this);
140 string _s;
141 }
142
version(Android)143 version (Android)
144 {
145 package enum system_directory = "/system/etc";
146 package enum system_file = "/system/etc/hosts";
147 }
version(Posix)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
cenforce(T)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
version(Windows)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
version(Posix)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 }
catch(FileException)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;
scope(exit)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
version(Posix)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
version(Windows)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
version(linux)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;
trustedCast(void[]buf)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 {
scope(exit)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 {
scope(exit)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
version(Posix)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
version(Windows)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
renameImpl(const (char)[]f,const (char)[]t,const (FSChar)* fromz,const (FSChar)* toz)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
removeImpl(const (char)[]name,const (FSChar)* namez)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 {
getFA(const (char)[]name,const (FSChar)* namez,out WIN32_FILE_ATTRIBUTE_DATA fad)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 {
getFA(const (FSChar)* namez,out WIN32_FILE_ATTRIBUTE_DATA fad)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
version(Windows)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 {
version(Windows)886 version (Windows)
887 {
888 with (getFileAttributesWin(name))
889 return makeUlong(nFileSizeLow, nFileSizeHigh);
890 }
version(Posix)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");
scope(exit)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.
version(Posix)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 {
version(Windows)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 }
version(Posix)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");
scope(exit)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
version(fullFileTests)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
version(StdDdoc)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 {
version(Windows)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 }
version(Posix)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
testTimes(int hnsecValue)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 {
version(Windows)1363 version (Windows)
1364 {
1365 SysTime dummy;
1366 SysTime ftm;
1367
1368 getTimesWin(name, dummy, dummy, ftm);
1369
1370 return ftm;
1371 }
version(Posix)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 {
version(Windows)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 }
version(Posix)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
scope(exit)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.
version(FreeBSD)1490 version (FreeBSD) {} else
version(DragonFlyBSD)1491 version (DragonFlyBSD) {} else
version(OSX)1492 version (OSX) {} else
1493 @system unittest
1494 {
1495 import core.thread;
1496
1497 if (exists(deleteme))
1498 remove(deleteme);
1499
1500 SysTime lastTime;
1501 foreach (n; 0 .. 3)
1502 {
1503 write(deleteme, "a");
1504 auto time = timeLastModified(deleteme);
1505 remove(deleteme);
1506 assert(time != lastTime);
1507 lastTime = time;
1508 Thread.sleep(10.msecs);
1509 }
1510 }
1511
1512
1513 /**
1514 * Determine whether the given file (or directory) _exists.
1515 * Params:
1516 * name = string or range of characters representing the file _name
1517 * Returns:
1518 * true if the file _name specified as input _exists
1519 */
1520 bool exists(R)(R name)
1521 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1522 !isConvertibleToString!R)
1523 {
1524 return existsImpl(name.tempCString!FSChar());
1525 }
1526
1527 /// ditto
1528 bool exists(R)(auto ref R name)
1529 if (isConvertibleToString!R)
1530 {
1531 return exists!(StringTypeOf!R)(name);
1532 }
1533
existsImpl(const (FSChar)* namez)1534 private bool existsImpl(const(FSChar)* namez) @trusted nothrow @nogc
1535 {
1536 version (Windows)
1537 {
1538 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
1539 // fileio/base/getfileattributes.asp
1540 return GetFileAttributesW(namez) != 0xFFFFFFFF;
1541 }
1542 else version (Posix)
1543 {
1544 /*
1545 The reason why we use stat (and not access) here is
1546 the quirky behavior of access for SUID programs: if
1547 we used access, a file may not appear to "exist",
1548 despite that the program would be able to open it
1549 just fine. The behavior in question is described as
1550 follows in the access man page:
1551
1552 > The check is done using the calling process's real
1553 > UID and GID, rather than the effective IDs as is
1554 > done when actually attempting an operation (e.g.,
1555 > open(2)) on the file. This allows set-user-ID
1556 > programs to easily determine the invoking user's
1557 > authority.
1558
1559 While various operating systems provide eaccess or
1560 euidaccess functions, these are not part of POSIX -
1561 so it's safer to use stat instead.
1562 */
1563
1564 stat_t statbuf = void;
1565 return lstat(namez, &statbuf) == 0;
1566 }
1567 else
1568 static assert(0);
1569 }
1570
1571 @safe unittest
1572 {
1573 assert(exists("."));
1574 assert(!exists("this file does not exist"));
1575 write(deleteme, "a\n");
scope(exit)1576 scope(exit) { assert(exists(deleteme)); remove(deleteme); }
1577 assert(exists(deleteme));
1578 }
1579
1580 @safe unittest // Bugzilla 16573
1581 {
1582 enum S : string { foo = "foo" }
1583 assert(__traits(compiles, S.foo.exists));
1584 }
1585
1586 /++
1587 Returns the attributes of the given file.
1588
1589 Note that the file attributes on Windows and Posix systems are
1590 completely different. On Windows, they're what is returned by
1591 $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx,
1592 GetFileAttributes), whereas on Posix systems, they're the $(LUCKY
1593 st_mode) value which is part of the $(D stat struct) gotten by
1594 calling the $(HTTP en.wikipedia.org/wiki/Stat_%28Unix%29, $(D stat))
1595 function.
1596
1597 On Posix systems, if the given file is a symbolic link, then
1598 attributes are the attributes of the file pointed to by the symbolic
1599 link.
1600
1601 Params:
1602 name = The file to get the attributes of.
1603
1604 Throws: $(D FileException) on error.
1605 +/
1606 uint getAttributes(R)(R name)
1607 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1608 !isConvertibleToString!R)
1609 {
version(Windows)1610 version (Windows)
1611 {
1612 auto namez = name.tempCString!FSChar();
1613 static auto trustedGetFileAttributesW(const(FSChar)* namez) @trusted
1614 {
1615 return GetFileAttributesW(namez);
1616 }
1617 immutable result = trustedGetFileAttributesW(namez);
1618
1619 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1620 alias names = name;
1621 else
1622 string names = null;
1623 cenforce(result != INVALID_FILE_ATTRIBUTES, names, namez);
1624
1625 return result;
1626 }
version(Posix)1627 else version (Posix)
1628 {
1629 auto namez = name.tempCString!FSChar();
1630 static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted
1631 {
1632 return stat(namez, &buf);
1633 }
1634 stat_t statbuf = void;
1635
1636 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1637 alias names = name;
1638 else
1639 string names = null;
1640 cenforce(trustedStat(namez, statbuf) == 0, names, namez);
1641
1642 return statbuf.st_mode;
1643 }
1644 }
1645
1646 /// ditto
1647 uint getAttributes(R)(auto ref R name)
1648 if (isConvertibleToString!R)
1649 {
1650 return getAttributes!(StringTypeOf!R)(name);
1651 }
1652
1653 @safe unittest
1654 {
1655 static assert(__traits(compiles, getAttributes(TestAliasedString(null))));
1656 }
1657
1658 /++
1659 If the given file is a symbolic link, then this returns the attributes of the
1660 symbolic link itself rather than file that it points to. If the given file
1661 is $(I not) a symbolic link, then this function returns the same result
1662 as getAttributes.
1663
1664 On Windows, getLinkAttributes is identical to getAttributes. It exists on
1665 Windows so that you don't have to special-case code for Windows when dealing
1666 with symbolic links.
1667
1668 Params:
1669 name = The file to get the symbolic link attributes of.
1670
1671 Returns:
1672 the attributes
1673
1674 Throws:
1675 $(D FileException) on error.
1676 +/
1677 uint getLinkAttributes(R)(R name)
1678 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1679 !isConvertibleToString!R)
1680 {
version(Windows)1681 version (Windows)
1682 {
1683 return getAttributes(name);
1684 }
1685 else version (Posix)
1686 {
1687 auto namez = name.tempCString!FSChar();
1688 static auto trustedLstat(const(FSChar)* namez, ref stat_t buf) @trusted
1689 {
1690 return lstat(namez, &buf);
1691 }
1692 stat_t lstatbuf = void;
1693 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1694 alias names = name;
1695 else
1696 string names = null;
1697 cenforce(trustedLstat(namez, lstatbuf) == 0, names, namez);
1698 return lstatbuf.st_mode;
1699 }
1700 }
1701
1702 /// ditto
1703 uint getLinkAttributes(R)(auto ref R name)
1704 if (isConvertibleToString!R)
1705 {
1706 return getLinkAttributes!(StringTypeOf!R)(name);
1707 }
1708
1709 @safe unittest
1710 {
1711 static assert(__traits(compiles, getLinkAttributes(TestAliasedString(null))));
1712 }
1713
1714 /++
1715 Set the _attributes of the given file.
1716
1717 Params:
1718 name = the file _name
1719 attributes = the _attributes to set the file to
1720
1721 Throws:
1722 $(D FileException) if the given file does not exist.
1723 +/
1724 void setAttributes(R)(R name, uint attributes)
1725 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1726 !isConvertibleToString!R)
1727 {
1728 version (Windows)
1729 {
1730 auto namez = name.tempCString!FSChar();
1731 static auto trustedSetFileAttributesW(const(FSChar)* namez, uint dwFileAttributes) @trusted
1732 {
1733 return SetFileAttributesW(namez, dwFileAttributes);
1734 }
1735 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1736 alias names = name;
1737 else
1738 string names = null;
1739 cenforce(trustedSetFileAttributesW(namez, attributes), names, namez);
1740 }
1741 else version (Posix)
1742 {
1743 auto namez = name.tempCString!FSChar();
1744 static auto trustedChmod(const(FSChar)* namez, mode_t mode) @trusted
1745 {
1746 return chmod(namez, mode);
1747 }
1748 assert(attributes <= mode_t.max);
1749 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
1750 alias names = name;
1751 else
1752 string names = null;
1753 cenforce(!trustedChmod(namez, cast(mode_t) attributes), names, namez);
1754 }
1755 }
1756
1757 /// ditto
1758 void setAttributes(R)(auto ref R name, uint attributes)
1759 if (isConvertibleToString!R)
1760 {
1761 return setAttributes!(StringTypeOf!R)(name, attributes);
1762 }
1763
1764 @safe unittest
1765 {
1766 static assert(__traits(compiles, setAttributes(TestAliasedString(null), 0)));
1767 }
1768
1769 /++
1770 Returns whether the given file is a directory.
1771
1772 Params:
1773 name = The path to the file.
1774
1775 Returns:
1776 true if name specifies a directory
1777
1778 Throws:
1779 $(D FileException) if the given file does not exist.
1780
1781 Example:
1782 --------------------
1783 assert(!"/etc/fonts/fonts.conf".isDir);
1784 assert("/usr/share/include".isDir);
1785 --------------------
1786 +/
1787 @property bool isDir(R)(R name)
1788 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1789 !isConvertibleToString!R)
1790 {
1791 version (Windows)
1792 {
1793 return (getAttributes(name) & FILE_ATTRIBUTE_DIRECTORY) != 0;
1794 }
1795 else version (Posix)
1796 {
1797 return (getAttributes(name) & S_IFMT) == S_IFDIR;
1798 }
1799 }
1800
1801 /// ditto
1802 @property bool isDir(R)(auto ref R name)
1803 if (isConvertibleToString!R)
1804 {
1805 return name.isDir!(StringTypeOf!R);
1806 }
1807
1808 @safe unittest
1809 {
1810 static assert(__traits(compiles, TestAliasedString(null).isDir));
1811 }
1812
1813 @safe unittest
1814 {
1815 version (Windows)
1816 {
1817 if ("C:\\Program Files\\".exists)
1818 assert("C:\\Program Files\\".isDir);
1819
1820 if ("C:\\Windows\\system.ini".exists)
1821 assert(!"C:\\Windows\\system.ini".isDir);
1822 }
1823 else version (Posix)
1824 {
1825 if (system_directory.exists)
1826 assert(system_directory.isDir);
1827
1828 if (system_file.exists)
1829 assert(!system_file.isDir);
1830 }
1831 }
1832
1833 @system unittest
1834 {
1835 version (Windows)
1836 enum dir = "C:\\Program Files\\";
1837 else version (Posix)
1838 enum dir = system_directory;
1839
1840 if (dir.exists)
1841 {
1842 DirEntry de = DirEntry(dir);
1843 assert(de.isDir);
1844 assert(DirEntry(dir).isDir);
1845 }
1846 }
1847
1848 /++
1849 Returns whether the given file _attributes are for a directory.
1850
1851 Params:
1852 attributes = The file _attributes.
1853
1854 Returns:
1855 true if attributes specifies a directory
1856
1857 Example:
1858 --------------------
1859 assert(!attrIsDir(getAttributes("/etc/fonts/fonts.conf")));
1860 assert(!attrIsDir(getLinkAttributes("/etc/fonts/fonts.conf")));
1861 --------------------
1862 +/
1863 bool attrIsDir(uint attributes) @safe pure nothrow @nogc
1864 {
1865 version (Windows)
1866 {
1867 return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
1868 }
1869 else version (Posix)
1870 {
1871 return (attributes & S_IFMT) == S_IFDIR;
1872 }
1873 }
1874
1875 @safe unittest
1876 {
1877 version (Windows)
1878 {
1879 if ("C:\\Program Files\\".exists)
1880 {
1881 assert(attrIsDir(getAttributes("C:\\Program Files\\")));
1882 assert(attrIsDir(getLinkAttributes("C:\\Program Files\\")));
1883 }
1884
1885 if ("C:\\Windows\\system.ini".exists)
1886 {
1887 assert(!attrIsDir(getAttributes("C:\\Windows\\system.ini")));
1888 assert(!attrIsDir(getLinkAttributes("C:\\Windows\\system.ini")));
1889 }
1890 }
1891 else version (Posix)
1892 {
1893 if (system_directory.exists)
1894 {
1895 assert(attrIsDir(getAttributes(system_directory)));
1896 assert(attrIsDir(getLinkAttributes(system_directory)));
1897 }
1898
1899 if (system_file.exists)
1900 {
1901 assert(!attrIsDir(getAttributes(system_file)));
1902 assert(!attrIsDir(getLinkAttributes(system_file)));
1903 }
1904 }
1905 }
1906
1907
1908 /++
1909 Returns whether the given file (or directory) is a file.
1910
1911 On Windows, if a file is not a directory, then it's a file. So,
1912 either $(D isFile) or $(D isDir) will return true for any given file.
1913
1914 On Posix systems, if $(D isFile) is $(D true), that indicates that the file
1915 is a regular file (e.g. not a block not device). So, on Posix systems, it's
1916 possible for both $(D isFile) and $(D isDir) to be $(D false) for a
1917 particular file (in which case, it's a special file). You can use
1918 $(D getAttributes) to get the attributes to figure out what type of special
1919 it is, or you can use $(D DirEntry) to get at its $(D statBuf), which is the
1920 result from $(D stat). In either case, see the man page for $(D stat) for
1921 more information.
1922
1923 Params:
1924 name = The path to the file.
1925
1926 Returns:
1927 true if name specifies a file
1928
1929 Throws:
1930 $(D FileException) if the given file does not exist.
1931
1932 Example:
1933 --------------------
1934 assert("/etc/fonts/fonts.conf".isFile);
1935 assert(!"/usr/share/include".isFile);
1936 --------------------
1937 +/
1938 @property bool isFile(R)(R name)
1939 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1940 !isConvertibleToString!R)
1941 {
1942 version (Windows)
1943 return !name.isDir;
1944 else version (Posix)
1945 return (getAttributes(name) & S_IFMT) == S_IFREG;
1946 }
1947
1948 /// ditto
1949 @property bool isFile(R)(auto ref R name)
1950 if (isConvertibleToString!R)
1951 {
1952 return isFile!(StringTypeOf!R)(name);
1953 }
1954
1955 @system unittest // bugzilla 15658
1956 {
1957 DirEntry e = DirEntry(".");
1958 static assert(is(typeof(isFile(e))));
1959 }
1960
1961 @safe unittest
1962 {
1963 static assert(__traits(compiles, TestAliasedString(null).isFile));
1964 }
1965
1966 @safe unittest
1967 {
version(Windows)1968 version (Windows)
1969 {
1970 if ("C:\\Program Files\\".exists)
1971 assert(!"C:\\Program Files\\".isFile);
1972
1973 if ("C:\\Windows\\system.ini".exists)
1974 assert("C:\\Windows\\system.ini".isFile);
1975 }
version(Posix)1976 else version (Posix)
1977 {
1978 if (system_directory.exists)
1979 assert(!system_directory.isFile);
1980
1981 if (system_file.exists)
1982 assert(system_file.isFile);
1983 }
1984 }
1985
1986
1987 /++
1988 Returns whether the given file _attributes are for a file.
1989
1990 On Windows, if a file is not a directory, it's a file. So, either
1991 $(D attrIsFile) or $(D attrIsDir) will return $(D true) for the
1992 _attributes of any given file.
1993
1994 On Posix systems, if $(D attrIsFile) is $(D true), that indicates that the
1995 file is a regular file (e.g. not a block not device). So, on Posix systems,
1996 it's possible for both $(D attrIsFile) and $(D attrIsDir) to be $(D false)
1997 for a particular file (in which case, it's a special file). If a file is a
1998 special file, you can use the _attributes to check what type of special file
1999 it is (see the man page for $(D stat) for more information).
2000
2001 Params:
2002 attributes = The file _attributes.
2003
2004 Returns:
2005 true if the given file _attributes are for a file
2006
2007 Example:
2008 --------------------
2009 assert(attrIsFile(getAttributes("/etc/fonts/fonts.conf")));
2010 assert(attrIsFile(getLinkAttributes("/etc/fonts/fonts.conf")));
2011 --------------------
2012 +/
attrIsFile(uint attributes)2013 bool attrIsFile(uint attributes) @safe pure nothrow @nogc
2014 {
2015 version (Windows)
2016 {
2017 return (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
2018 }
2019 else version (Posix)
2020 {
2021 return (attributes & S_IFMT) == S_IFREG;
2022 }
2023 }
2024
2025 @safe unittest
2026 {
2027 version (Windows)
2028 {
2029 if ("C:\\Program Files\\".exists)
2030 {
2031 assert(!attrIsFile(getAttributes("C:\\Program Files\\")));
2032 assert(!attrIsFile(getLinkAttributes("C:\\Program Files\\")));
2033 }
2034
2035 if ("C:\\Windows\\system.ini".exists)
2036 {
2037 assert(attrIsFile(getAttributes("C:\\Windows\\system.ini")));
2038 assert(attrIsFile(getLinkAttributes("C:\\Windows\\system.ini")));
2039 }
2040 }
2041 else version (Posix)
2042 {
2043 if (system_directory.exists)
2044 {
2045 assert(!attrIsFile(getAttributes(system_directory)));
2046 assert(!attrIsFile(getLinkAttributes(system_directory)));
2047 }
2048
2049 if (system_file.exists)
2050 {
2051 assert(attrIsFile(getAttributes(system_file)));
2052 assert(attrIsFile(getLinkAttributes(system_file)));
2053 }
2054 }
2055 }
2056
2057
2058 /++
2059 Returns whether the given file is a symbolic link.
2060
2061 On Windows, returns $(D true) when the file is either a symbolic link or a
2062 junction point.
2063
2064 Params:
2065 name = The path to the file.
2066
2067 Returns:
2068 true if name is a symbolic link
2069
2070 Throws:
2071 $(D FileException) if the given file does not exist.
2072 +/
2073 @property bool isSymlink(R)(R name)
2074 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
2075 !isConvertibleToString!R)
2076 {
2077 version (Windows)
2078 return (getAttributes(name) & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
2079 else version (Posix)
2080 return (getLinkAttributes(name) & S_IFMT) == S_IFLNK;
2081 }
2082
2083 /// ditto
2084 @property bool isSymlink(R)(auto ref R name)
2085 if (isConvertibleToString!R)
2086 {
2087 return name.isSymlink!(StringTypeOf!R);
2088 }
2089
2090 @safe unittest
2091 {
2092 static assert(__traits(compiles, TestAliasedString(null).isSymlink));
2093 }
2094
2095 @system unittest
2096 {
2097 version (Windows)
2098 {
2099 if ("C:\\Program Files\\".exists)
2100 assert(!"C:\\Program Files\\".isSymlink);
2101
2102 if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
2103 assert("C:\\Documents and Settings\\".isSymlink);
2104
2105 enum fakeSymFile = "C:\\Windows\\system.ini";
2106 if (fakeSymFile.exists)
2107 {
2108 assert(!fakeSymFile.isSymlink);
2109
2110 assert(!fakeSymFile.isSymlink);
2111 assert(!attrIsSymlink(getAttributes(fakeSymFile)));
2112 assert(!attrIsSymlink(getLinkAttributes(fakeSymFile)));
2113
2114 assert(attrIsFile(getAttributes(fakeSymFile)));
2115 assert(attrIsFile(getLinkAttributes(fakeSymFile)));
2116 assert(!attrIsDir(getAttributes(fakeSymFile)));
2117 assert(!attrIsDir(getLinkAttributes(fakeSymFile)));
2118
2119 assert(getAttributes(fakeSymFile) == getLinkAttributes(fakeSymFile));
2120 }
2121 }
2122 else version (Posix)
2123 {
2124 if (system_directory.exists)
2125 {
2126 assert(!system_directory.isSymlink);
2127
2128 immutable symfile = deleteme ~ "_slink\0";
2129 scope(exit) if (symfile.exists) symfile.remove();
2130
2131 core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
2132
2133 assert(symfile.isSymlink);
2134 assert(!attrIsSymlink(getAttributes(symfile)));
2135 assert(attrIsSymlink(getLinkAttributes(symfile)));
2136
2137 assert(attrIsDir(getAttributes(symfile)));
2138 assert(!attrIsDir(getLinkAttributes(symfile)));
2139
2140 assert(!attrIsFile(getAttributes(symfile)));
2141 assert(!attrIsFile(getLinkAttributes(symfile)));
2142 }
2143
2144 if (system_file.exists)
2145 {
2146 assert(!system_file.isSymlink);
2147
2148 immutable symfile = deleteme ~ "_slink\0";
2149 scope(exit) if (symfile.exists) symfile.remove();
2150
2151 core.sys.posix.unistd.symlink(system_file, symfile.ptr);
2152
2153 assert(symfile.isSymlink);
2154 assert(!attrIsSymlink(getAttributes(symfile)));
2155 assert(attrIsSymlink(getLinkAttributes(symfile)));
2156
2157 assert(!attrIsDir(getAttributes(symfile)));
2158 assert(!attrIsDir(getLinkAttributes(symfile)));
2159
2160 assert(attrIsFile(getAttributes(symfile)));
2161 assert(!attrIsFile(getLinkAttributes(symfile)));
2162 }
2163 }
2164
2165 static assert(__traits(compiles, () @safe { return "dummy".isSymlink; }));
2166 }
2167
2168
2169 /++
2170 Returns whether the given file attributes are for a symbolic link.
2171
2172 On Windows, return $(D true) when the file is either a symbolic link or a
2173 junction point.
2174
2175 Params:
2176 attributes = The file attributes.
2177
2178 Returns:
2179 true if attributes are for a symbolic link
2180
2181 Example:
2182 --------------------
2183 core.sys.posix.unistd.symlink("/etc/fonts/fonts.conf", "/tmp/alink");
2184
2185 assert(!getAttributes("/tmp/alink").isSymlink);
2186 assert(getLinkAttributes("/tmp/alink").isSymlink);
2187 --------------------
2188 +/
2189 bool attrIsSymlink(uint attributes) @safe pure nothrow @nogc
2190 {
2191 version (Windows)
2192 return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
2193 else version (Posix)
2194 return (attributes & S_IFMT) == S_IFLNK;
2195 }
2196
2197
2198 /****************************************************
2199 * Change directory to $(D pathname).
2200 * Throws: $(D FileException) on error.
2201 */
2202 void chdir(R)(R pathname)
2203 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
2204 !isConvertibleToString!R)
2205 {
2206 // Place outside of @trusted block
2207 auto pathz = pathname.tempCString!FSChar();
2208
2209 version (Windows)
2210 {
2211 static auto trustedChdir(const(FSChar)* pathz) @trusted
2212 {
2213 return SetCurrentDirectoryW(pathz);
2214 }
2215 }
2216 else version (Posix)
2217 {
2218 static auto trustedChdir(const(FSChar)* pathz) @trusted
2219 {
2220 return core.sys.posix.unistd.chdir(pathz) == 0;
2221 }
2222 }
2223 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
2224 alias pathStr = pathname;
2225 else
2226 string pathStr = null;
2227 cenforce(trustedChdir(pathz), pathStr, pathz);
2228 }
2229
2230 /// ditto
2231 void chdir(R)(auto ref R pathname)
2232 if (isConvertibleToString!R)
2233 {
2234 return chdir!(StringTypeOf!R)(pathname);
2235 }
2236
2237 @safe unittest
2238 {
2239 static assert(__traits(compiles, chdir(TestAliasedString(null))));
2240 }
2241
2242 /****************************************************
2243 Make directory $(D pathname).
2244
2245 Throws: $(D FileException) on Posix or $(D WindowsException) on Windows
2246 if an error occured.
2247 */
2248 void mkdir(R)(R pathname)
2249 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
2250 !isConvertibleToString!R)
2251 {
2252 // Place outside of @trusted block
2253 const pathz = pathname.tempCString!FSChar();
2254
2255 version (Windows)
2256 {
2257 static auto trustedCreateDirectoryW(const(FSChar)* pathz) @trusted
2258 {
2259 return CreateDirectoryW(pathz, null);
2260 }
2261 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
2262 alias pathStr = pathname;
2263 else
2264 string pathStr = null;
2265 wenforce(trustedCreateDirectoryW(pathz), pathStr, pathz);
2266 }
2267 else version (Posix)
2268 {
2269 import std.conv : octal;
2270
2271 static auto trustedMkdir(const(FSChar)* pathz, mode_t mode) @trusted
2272 {
2273 return core.sys.posix.sys.stat.mkdir(pathz, mode);
2274 }
2275 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
2276 alias pathStr = pathname;
2277 else
2278 string pathStr = null;
2279 cenforce(trustedMkdir(pathz, octal!777) == 0, pathStr, pathz);
2280 }
2281 }
2282
2283 /// ditto
2284 void mkdir(R)(auto ref R pathname)
2285 if (isConvertibleToString!R)
2286 {
2287 return mkdir!(StringTypeOf!R)(pathname);
2288 }
2289
2290 @safe unittest
2291 {
2292 import std.path : mkdir;
2293 static assert(__traits(compiles, mkdir(TestAliasedString(null))));
2294 }
2295
2296 // Same as mkdir but ignores "already exists" errors.
2297 // Returns: "true" if the directory was created,
2298 // "false" if it already existed.
2299 private bool ensureDirExists()(in char[] pathname)
2300 {
2301 import std.exception : enforce;
2302 const pathz = pathname.tempCString!FSChar();
2303
2304 version (Windows)
2305 {
2306 if (() @trusted { return CreateDirectoryW(pathz, null); }())
2307 return true;
2308 cenforce(GetLastError() == ERROR_ALREADY_EXISTS, pathname.idup);
2309 }
2310 else version (Posix)
2311 {
2312 import std.conv : octal;
2313
2314 if (() @trusted { return core.sys.posix.sys.stat.mkdir(pathz, octal!777); }() == 0)
2315 return true;
2316 cenforce(errno == EEXIST || errno == EISDIR, pathname);
2317 }
2318 enforce(pathname.isDir, new FileException(pathname.idup));
2319 return false;
2320 }
2321
2322 /****************************************************
2323 * Make directory and all parent directories as needed.
2324 *
2325 * Does nothing if the directory specified by
2326 * $(D pathname) already exists.
2327 *
2328 * Throws: $(D FileException) on error.
2329 */
2330
2331 void mkdirRecurse(in char[] pathname) @safe
2332 {
2333 import std.path : dirName, baseName;
2334
2335 const left = dirName(pathname);
2336 if (left.length != pathname.length && !exists(left))
2337 {
2338 mkdirRecurse(left);
2339 }
2340 if (!baseName(pathname).empty)
2341 {
2342 ensureDirExists(pathname);
2343 }
2344 }
2345
2346 @safe unittest
2347 {
2348 import std.exception : assertThrown;
2349 {
2350 import std.path : buildPath, buildNormalizedPath;
2351
2352 immutable basepath = deleteme ~ "_dir";
2353 scope(exit) () @trusted { rmdirRecurse(basepath); }();
2354
2355 auto path = buildPath(basepath, "a", "..", "b");
2356 mkdirRecurse(path);
2357 path = path.buildNormalizedPath;
2358 assert(path.isDir);
2359
2360 path = buildPath(basepath, "c");
2361 write(path, "");
2362 assertThrown!FileException(mkdirRecurse(path));
2363
2364 path = buildPath(basepath, "d");
2365 mkdirRecurse(path);
2366 mkdirRecurse(path); // should not throw
2367 }
2368
2369 version (Windows)
2370 {
2371 assertThrown!FileException(mkdirRecurse(`1:\foobar`));
2372 }
2373
2374 // bug3570
2375 {
2376 immutable basepath = deleteme ~ "_dir";
2377 version (Windows)
2378 {
2379 immutable path = basepath ~ "\\fake\\here\\";
2380 }
2381 else version (Posix)
2382 {
2383 immutable path = basepath ~ `/fake/here/`;
2384 }
2385
2386 mkdirRecurse(path);
2387 assert(basepath.exists && basepath.isDir);
2388 scope(exit) () @trusted { rmdirRecurse(basepath); }();
2389 assert(path.exists && path.isDir);
2390 }
2391 }
2392
2393 /****************************************************
2394 Remove directory $(D pathname).
2395
2396 Params:
2397 pathname = Range or string specifying the directory name
2398
2399 Throws: $(D FileException) on error.
2400 */
2401 void rmdir(R)(R pathname)
2402 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
2403 !isConvertibleToString!R)
2404 {
2405 // Place outside of @trusted block
2406 auto pathz = pathname.tempCString!FSChar();
2407
2408 version (Windows)
2409 {
2410 static auto trustedRmdir(const(FSChar)* pathz) @trusted
2411 {
2412 return RemoveDirectoryW(pathz);
2413 }
2414 }
2415 else version (Posix)
2416 {
2417 static auto trustedRmdir(const(FSChar)* pathz) @trusted
2418 {
2419 return core.sys.posix.unistd.rmdir(pathz) == 0;
2420 }
2421 }
2422 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
2423 alias pathStr = pathname;
2424 else
2425 string pathStr = null;
2426 cenforce(trustedRmdir(pathz), pathStr, pathz);
2427 }
2428
2429 /// ditto
2430 void rmdir(R)(auto ref R pathname)
2431 if (isConvertibleToString!R)
2432 {
2433 rmdir!(StringTypeOf!R)(pathname);
2434 }
2435
2436 @safe unittest
2437 {
2438 static assert(__traits(compiles, rmdir(TestAliasedString(null))));
2439 }
2440
2441 /++
2442 $(BLUE This function is Posix-Only.)
2443
2444 Creates a symbolic _link (_symlink).
2445
2446 Params:
2447 original = The file that is being linked. This is the target path that's
2448 stored in the _symlink. A relative path is relative to the created
2449 _symlink.
2450 link = The _symlink to create. A relative path is relative to the
2451 current working directory.
2452
2453 Throws:
2454 $(D FileException) on error (which includes if the _symlink already
2455 exists).
2456 +/
2457 version (StdDdoc) void symlink(RO, RL)(RO original, RL link)
2458 if ((isInputRange!RO && !isInfinite!RO && isSomeChar!(ElementEncodingType!RO) ||
2459 isConvertibleToString!RO) &&
2460 (isInputRange!RL && !isInfinite!RL && isSomeChar!(ElementEncodingType!RL) ||
2461 isConvertibleToString!RL));
2462 else version (Posix) void symlink(RO, RL)(RO original, RL link)
2463 if ((isInputRange!RO && !isInfinite!RO && isSomeChar!(ElementEncodingType!RO) ||
2464 isConvertibleToString!RO) &&
2465 (isInputRange!RL && !isInfinite!RL && isSomeChar!(ElementEncodingType!RL) ||
2466 isConvertibleToString!RL))
2467 {
2468 static if (isConvertibleToString!RO || isConvertibleToString!RL)
2469 {
2470 import std.meta : staticMap;
2471 alias Types = staticMap!(convertToString, RO, RL);
2472 symlink!Types(original, link);
2473 }
2474 else
2475 {
2476 import std.conv : text;
2477 auto oz = original.tempCString();
2478 auto lz = link.tempCString();
2479 alias posixSymlink = core.sys.posix.unistd.symlink;
2480 immutable int result = () @trusted { return posixSymlink(oz, lz); } ();
2481 cenforce(result == 0, text(link));
2482 }
2483 }
2484
version(Posix)2485 version (Posix) @safe unittest
2486 {
2487 if (system_directory.exists)
2488 {
2489 immutable symfile = deleteme ~ "_slink\0";
2490 scope(exit) if (symfile.exists) symfile.remove();
2491
2492 symlink(system_directory, symfile);
2493
2494 assert(symfile.exists);
2495 assert(symfile.isSymlink);
2496 assert(!attrIsSymlink(getAttributes(symfile)));
2497 assert(attrIsSymlink(getLinkAttributes(symfile)));
2498
2499 assert(attrIsDir(getAttributes(symfile)));
2500 assert(!attrIsDir(getLinkAttributes(symfile)));
2501
2502 assert(!attrIsFile(getAttributes(symfile)));
2503 assert(!attrIsFile(getLinkAttributes(symfile)));
2504 }
2505
2506 if (system_file.exists)
2507 {
2508 assert(!system_file.isSymlink);
2509
2510 immutable symfile = deleteme ~ "_slink\0";
2511 scope(exit) if (symfile.exists) symfile.remove();
2512
2513 symlink(system_file, symfile);
2514
2515 assert(symfile.exists);
2516 assert(symfile.isSymlink);
2517 assert(!attrIsSymlink(getAttributes(symfile)));
2518 assert(attrIsSymlink(getLinkAttributes(symfile)));
2519
2520 assert(!attrIsDir(getAttributes(symfile)));
2521 assert(!attrIsDir(getLinkAttributes(symfile)));
2522
2523 assert(attrIsFile(getAttributes(symfile)));
2524 assert(!attrIsFile(getLinkAttributes(symfile)));
2525 }
2526 }
2527
version(Posix)2528 version (Posix) @safe unittest
2529 {
2530 static assert(__traits(compiles,
2531 symlink(TestAliasedString(null), TestAliasedString(null))));
2532 }
2533
2534
2535 /++
2536 $(BLUE This function is Posix-Only.)
2537
2538 Returns the path to the file pointed to by a symlink. Note that the
2539 path could be either relative or absolute depending on the symlink.
2540 If the path is relative, it's relative to the symlink, not the current
2541 working directory.
2542
2543 Throws:
2544 $(D FileException) on error.
2545 +/
2546 version (StdDdoc) string readLink(R)(R link)
2547 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) ||
2548 isConvertibleToString!R);
2549 else version (Posix) string readLink(R)(R link)
2550 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) ||
2551 isConvertibleToString!R)
2552 {
2553 static if (isConvertibleToString!R)
2554 {
2555 return readLink!(convertToString!R)(link);
2556 }
2557 else
2558 {
2559 import std.conv : to;
2560 import std.exception : assumeUnique;
2561 alias posixReadlink = core.sys.posix.unistd.readlink;
2562 enum bufferLen = 2048;
2563 enum maxCodeUnits = 6;
2564 char[bufferLen] buffer;
2565 const linkz = link.tempCString();
2566 auto size = () @trusted {
2567 return posixReadlink(linkz, buffer.ptr, buffer.length);
2568 } ();
2569 cenforce(size != -1, to!string(link));
2570
2571 if (size <= bufferLen - maxCodeUnits)
2572 return to!string(buffer[0 .. size]);
2573
2574 auto dynamicBuffer = new char[](bufferLen * 3 / 2);
2575
2576 foreach (i; 0 .. 10)
2577 {
2578 size = () @trusted {
2579 return posixReadlink(linkz, dynamicBuffer.ptr,
2580 dynamicBuffer.length);
2581 } ();
2582 cenforce(size != -1, to!string(link));
2583
2584 if (size <= dynamicBuffer.length - maxCodeUnits)
2585 {
2586 dynamicBuffer.length = size;
2587 return () @trusted {
2588 return assumeUnique(dynamicBuffer);
2589 } ();
2590 }
2591
2592 dynamicBuffer.length = dynamicBuffer.length * 3 / 2;
2593 }
2594
2595 throw new FileException(to!string(link), "Path is too long to read.");
2596 }
2597 }
2598
version(Posix)2599 version (Posix) @safe unittest
2600 {
2601 import std.exception : assertThrown;
2602 import std.string;
2603
2604 foreach (file; [system_directory, system_file])
2605 {
2606 if (file.exists)
2607 {
2608 immutable symfile = deleteme ~ "_slink\0";
2609 scope(exit) if (symfile.exists) symfile.remove();
2610
2611 symlink(file, symfile);
2612 assert(readLink(symfile) == file, format("Failed file: %s", file));
2613 }
2614 }
2615
2616 assertThrown!FileException(readLink("/doesnotexist"));
2617 }
2618
2619 version (Posix) @safe unittest
2620 {
2621 static assert(__traits(compiles, readLink(TestAliasedString("foo"))));
2622 }
2623
2624 version (Posix) @system unittest // input range of dchars
2625 {
2626 mkdirRecurse(deleteme);
2627 scope(exit) if (deleteme.exists) rmdirRecurse(deleteme);
2628 write(deleteme ~ "/f", "");
2629 import std.range.interfaces : InputRange, inputRangeObject;
2630 import std.utf : byChar;
2631 immutable string link = deleteme ~ "/l";
2632 symlink("f", link);
2633 InputRange!dchar linkr = inputRangeObject(link);
2634 alias R = typeof(linkr);
2635 static assert(isInputRange!R);
2636 static assert(!isForwardRange!R);
2637 assert(readLink(linkr) == "f");
2638 }
2639
2640
2641 /****************************************************
2642 * Get the current working directory.
2643 * Throws: $(D FileException) on error.
2644 */
2645 version (Windows) string getcwd()
2646 {
2647 import std.conv : to;
2648 /* GetCurrentDirectory's return value:
2649 1. function succeeds: the number of characters that are written to
2650 the buffer, not including the terminating null character.
2651 2. function fails: zero
2652 3. the buffer (lpBuffer) is not large enough: the required size of
2653 the buffer, in characters, including the null-terminating character.
2654 */
2655 wchar[4096] buffW = void; //enough for most common case
2656 immutable n = cenforce(GetCurrentDirectoryW(to!DWORD(buffW.length), buffW.ptr),
2657 "getcwd");
2658 // we can do it because toUTFX always produces a fresh string
2659 if (n < buffW.length)
2660 {
2661 return buffW[0 .. n].to!string;
2662 }
2663 else //staticBuff isn't enough
2664 {
2665 auto ptr = cast(wchar*) malloc(wchar.sizeof * n);
2666 scope(exit) free(ptr);
2667 immutable n2 = GetCurrentDirectoryW(n, ptr);
2668 cenforce(n2 && n2 < n, "getcwd");
2669 return ptr[0 .. n2].to!string;
2670 }
2671 }
version(Solaris)2672 else version (Solaris) string getcwd()
2673 {
2674 /* BUF_SIZE >= PATH_MAX */
2675 enum BUF_SIZE = 4096;
2676 /* The user should be able to specify any size buffer > 0 */
2677 auto p = cenforce(core.sys.posix.unistd.getcwd(null, BUF_SIZE),
2678 "cannot get cwd");
2679 scope(exit) core.stdc.stdlib.free(p);
2680 return p[0 .. core.stdc.string.strlen(p)].idup;
2681 }
version(Posix)2682 else version (Posix) string getcwd()
2683 {
2684 auto p = cenforce(core.sys.posix.unistd.getcwd(null, 0),
2685 "cannot get cwd");
2686 scope(exit) core.stdc.stdlib.free(p);
2687 return p[0 .. core.stdc.string.strlen(p)].idup;
2688 }
2689
2690 @system unittest
2691 {
2692 auto s = getcwd();
2693 assert(s.length);
2694 }
2695
2696 version (OSX)
2697 private extern (C) int _NSGetExecutablePath(char* buf, uint* bufsize);
2698 else version (FreeBSD)
2699 private extern (C) int sysctl (const int* name, uint namelen, void* oldp,
2700 size_t* oldlenp, const void* newp, size_t newlen);
2701 else version (NetBSD)
2702 private extern (C) int sysctl (const int* name, uint namelen, void* oldp,
2703 size_t* oldlenp, const void* newp, size_t newlen);
2704
2705 /**
2706 * Returns the full path of the current executable.
2707 *
2708 * Throws:
2709 * $(REF1 Exception, object)
2710 */
thisExePath()2711 @trusted string thisExePath ()
2712 {
2713 version (OSX)
2714 {
2715 import core.sys.posix.stdlib : realpath;
2716 import std.conv : to;
2717 import std.exception : errnoEnforce;
2718
2719 uint size;
2720
2721 _NSGetExecutablePath(null, &size); // get the length of the path
2722 auto buffer = new char[size];
2723 _NSGetExecutablePath(buffer.ptr, &size);
2724
2725 auto absolutePath = realpath(buffer.ptr, null); // let the function allocate
2726
2727 scope (exit)
2728 {
2729 if (absolutePath)
2730 free(absolutePath);
2731 }
2732
2733 errnoEnforce(absolutePath);
2734 return to!(string)(absolutePath);
2735 }
2736 else version (linux)
2737 {
2738 return readLink("/proc/self/exe");
2739 }
2740 else version (Windows)
2741 {
2742 import std.conv : to;
2743 import std.exception : enforce;
2744
2745 wchar[MAX_PATH] buf;
2746 wchar[] buffer = buf[];
2747
2748 while (true)
2749 {
2750 auto len = GetModuleFileNameW(null, buffer.ptr, cast(DWORD) buffer.length);
2751 enforce(len, sysErrorString(GetLastError()));
2752 if (len != buffer.length)
2753 return to!(string)(buffer[0 .. len]);
2754 buffer.length *= 2;
2755 }
2756 }
2757 else version (FreeBSD)
2758 {
2759 import std.exception : errnoEnforce, assumeUnique;
2760 enum
2761 {
2762 CTL_KERN = 1,
2763 KERN_PROC = 14,
2764 KERN_PROC_PATHNAME = 12
2765 }
2766
2767 int[4] mib = [CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1];
2768 size_t len;
2769
2770 auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); // get the length of the path
2771 errnoEnforce(result == 0);
2772
2773 auto buffer = new char[len - 1];
2774 result = sysctl(mib.ptr, mib.length, buffer.ptr, &len, null, 0);
2775 errnoEnforce(result == 0);
2776
2777 return buffer.assumeUnique;
2778 }
2779 else version (NetBSD)
2780 {
2781 return readLink("/proc/self/exe");
2782 }
2783 else version (DragonFlyBSD)
2784 {
2785 return readLink("/proc/curproc/file");
2786 }
2787 else version (Solaris)
2788 {
2789 import core.sys.posix.unistd : getpid;
2790 import std.string : format;
2791
2792 // Only Solaris 10 and later
2793 return readLink(format("/proc/%d/path/a.out", getpid()));
2794 }
2795 else
2796 static assert(0, "thisExePath is not supported on this platform");
2797 }
2798
2799 @safe unittest
2800 {
2801 import std.path : isAbsolute;
2802 auto path = thisExePath();
2803
2804 assert(path.exists);
2805 assert(path.isAbsolute);
2806 assert(path.isFile);
2807 }
2808
version(StdDdoc)2809 version (StdDdoc)
2810 {
2811 /++
2812 Info on a file, similar to what you'd get from stat on a Posix system.
2813 +/
2814 struct DirEntry
2815 {
2816 /++
2817 Constructs a $(D DirEntry) for the given file (or directory).
2818
2819 Params:
2820 path = The file (or directory) to get a DirEntry for.
2821
2822 Throws:
2823 $(D FileException) if the file does not exist.
2824 +/
2825 this(string path);
2826
2827 version (Windows)
2828 {
2829 private this(string path, in WIN32_FIND_DATAW *fd);
2830 }
2831 else version (Posix)
2832 {
2833 private this(string path, core.sys.posix.dirent.dirent* fd);
2834 }
2835
2836 /++
2837 Returns the path to the file represented by this $(D DirEntry).
2838
2839 Example:
2840 --------------------
2841 auto de1 = DirEntry("/etc/fonts/fonts.conf");
2842 assert(de1.name == "/etc/fonts/fonts.conf");
2843
2844 auto de2 = DirEntry("/usr/share/include");
2845 assert(de2.name == "/usr/share/include");
2846 --------------------
2847 +/
2848 @property string name() const;
2849
2850
2851 /++
2852 Returns whether the file represented by this $(D DirEntry) is a
2853 directory.
2854
2855 Example:
2856 --------------------
2857 auto de1 = DirEntry("/etc/fonts/fonts.conf");
2858 assert(!de1.isDir);
2859
2860 auto de2 = DirEntry("/usr/share/include");
2861 assert(de2.isDir);
2862 --------------------
2863 +/
2864 @property bool isDir();
2865
2866
2867 /++
2868 Returns whether the file represented by this $(D DirEntry) is a file.
2869
2870 On Windows, if a file is not a directory, then it's a file. So,
2871 either $(D isFile) or $(D isDir) will return $(D true).
2872
2873 On Posix systems, if $(D isFile) is $(D true), that indicates that
2874 the file is a regular file (e.g. not a block not device). So, on
2875 Posix systems, it's possible for both $(D isFile) and $(D isDir) to
2876 be $(D false) for a particular file (in which case, it's a special
2877 file). You can use $(D attributes) or $(D statBuf) to get more
2878 information about a special file (see the stat man page for more
2879 details).
2880
2881 Example:
2882 --------------------
2883 auto de1 = DirEntry("/etc/fonts/fonts.conf");
2884 assert(de1.isFile);
2885
2886 auto de2 = DirEntry("/usr/share/include");
2887 assert(!de2.isFile);
2888 --------------------
2889 +/
2890 @property bool isFile();
2891
2892 /++
2893 Returns whether the file represented by this $(D DirEntry) is a
2894 symbolic link.
2895
2896 On Windows, return $(D true) when the file is either a symbolic
2897 link or a junction point.
2898 +/
2899 @property bool isSymlink();
2900
2901 /++
2902 Returns the size of the the file represented by this $(D DirEntry)
2903 in bytes.
2904 +/
2905 @property ulong size();
2906
2907 /++
2908 $(BLUE This function is Windows-Only.)
2909
2910 Returns the creation time of the file represented by this
2911 $(D DirEntry).
2912 +/
2913 @property SysTime timeCreated() const;
2914
2915 /++
2916 Returns the time that the file represented by this $(D DirEntry) was
2917 last accessed.
2918
2919 Note that many file systems do not update the access time for files
2920 (generally for performance reasons), so there's a good chance that
2921 $(D timeLastAccessed) will return the same value as
2922 $(D timeLastModified).
2923 +/
2924 @property SysTime timeLastAccessed();
2925
2926 /++
2927 Returns the time that the file represented by this $(D DirEntry) was
2928 last modified.
2929 +/
2930 @property SysTime timeLastModified();
2931
2932 /++
2933 Returns the _attributes of the file represented by this $(D DirEntry).
2934
2935 Note that the file _attributes on Windows and Posix systems are
2936 completely different. On, Windows, they're what is returned by
2937 $(D GetFileAttributes)
2938 $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx, GetFileAttributes)
2939 Whereas, an Posix systems, they're the $(D st_mode) value which is
2940 part of the $(D stat) struct gotten by calling $(D stat).
2941
2942 On Posix systems, if the file represented by this $(D DirEntry) is a
2943 symbolic link, then _attributes are the _attributes of the file
2944 pointed to by the symbolic link.
2945 +/
2946 @property uint attributes();
2947
2948 /++
2949 On Posix systems, if the file represented by this $(D DirEntry) is a
2950 symbolic link, then $(D linkAttributes) are the attributes of the
2951 symbolic link itself. Otherwise, $(D linkAttributes) is identical to
2952 $(D attributes).
2953
2954 On Windows, $(D linkAttributes) is identical to $(D attributes). It
2955 exists on Windows so that you don't have to special-case code for
2956 Windows when dealing with symbolic links.
2957 +/
2958 @property uint linkAttributes();
2959
2960 version (Windows)
2961 alias stat_t = void*;
2962
2963 /++
2964 $(BLUE This function is Posix-Only.)
2965
2966 The $(D stat) struct gotten from calling $(D stat).
2967 +/
2968 @property stat_t statBuf();
2969 }
2970 }
2971 else version (Windows)
2972 {
2973 struct DirEntry
2974 {
2975 public:
2976 alias name this;
2977
2978 this(string path)
2979 {
2980 import std.datetime.systime : FILETIMEToSysTime;
2981
2982 if (!path.exists())
2983 throw new FileException(path, "File does not exist");
2984
2985 _name = path;
2986
2987 with (getFileAttributesWin(path))
2988 {
2989 _size = makeUlong(nFileSizeLow, nFileSizeHigh);
2990 _timeCreated = FILETIMEToSysTime(&ftCreationTime);
2991 _timeLastAccessed = FILETIMEToSysTime(&ftLastAccessTime);
2992 _timeLastModified = FILETIMEToSysTime(&ftLastWriteTime);
2993 _attributes = dwFileAttributes;
2994 }
2995 }
2996
2997 private this(string path, in WIN32_FIND_DATAW *fd)
2998 {
2999 import core.stdc.wchar_ : wcslen;
3000 import std.conv : to;
3001 import std.datetime.systime : FILETIMEToSysTime;
3002 import std.path : buildPath;
3003
3004 size_t clength = wcslen(fd.cFileName.ptr);
3005 _name = buildPath(path, fd.cFileName[0 .. clength].to!string);
3006 _size = (cast(ulong) fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
3007 _timeCreated = FILETIMEToSysTime(&fd.ftCreationTime);
3008 _timeLastAccessed = FILETIMEToSysTime(&fd.ftLastAccessTime);
3009 _timeLastModified = FILETIMEToSysTime(&fd.ftLastWriteTime);
3010 _attributes = fd.dwFileAttributes;
3011 }
3012
3013 @property string name() const pure nothrow
3014 {
3015 return _name;
3016 }
3017
3018 @property bool isDir() const pure nothrow
3019 {
3020 return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
3021 }
3022
3023 @property bool isFile() const pure nothrow
3024 {
3025 //Are there no options in Windows other than directory and file?
3026 //If there are, then this probably isn't the best way to determine
3027 //whether this DirEntry is a file or not.
3028 return !isDir;
3029 }
3030
3031 @property bool isSymlink() const pure nothrow
3032 {
3033 return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
3034 }
3035
3036 @property ulong size() const pure nothrow
3037 {
3038 return _size;
3039 }
3040
3041 @property SysTime timeCreated() const pure nothrow
3042 {
3043 return cast(SysTime)_timeCreated;
3044 }
3045
3046 @property SysTime timeLastAccessed() const pure nothrow
3047 {
3048 return cast(SysTime)_timeLastAccessed;
3049 }
3050
3051 @property SysTime timeLastModified() const pure nothrow
3052 {
3053 return cast(SysTime)_timeLastModified;
3054 }
3055
3056 @property uint attributes() const pure nothrow
3057 {
3058 return _attributes;
3059 }
3060
3061 @property uint linkAttributes() const pure nothrow
3062 {
3063 return _attributes;
3064 }
3065
3066 private:
3067 string _name; /// The file or directory represented by this DirEntry.
3068
3069 SysTime _timeCreated; /// The time when the file was created.
3070 SysTime _timeLastAccessed; /// The time when the file was last accessed.
3071 SysTime _timeLastModified; /// The time when the file was last modified.
3072
3073 ulong _size; /// The size of the file in bytes.
3074 uint _attributes; /// The file attributes from WIN32_FIND_DATAW.
3075 }
3076 }
3077 else version (Posix)
3078 {
3079 struct DirEntry
3080 {
3081 public:
3082 alias name this;
3083
3084 this(string path)
3085 {
3086 if (!path.exists)
3087 throw new FileException(path, "File does not exist");
3088
3089 _name = path;
3090
3091 _didLStat = false;
3092 _didStat = false;
3093 _dTypeSet = false;
3094 }
3095
3096 private this(string path, core.sys.posix.dirent.dirent* fd)
3097 {
3098 import std.path : buildPath;
3099
3100 static if (is(typeof(fd.d_namlen)))
3101 immutable len = fd.d_namlen;
3102 else
3103 immutable len = (() @trusted => core.stdc.string.strlen(fd.d_name.ptr))();
3104
3105 _name = buildPath(path, (() @trusted => fd.d_name.ptr[0 .. len])());
3106
3107 _didLStat = false;
3108 _didStat = false;
3109
3110 //fd_d_type doesn't work for all file systems,
3111 //in which case the result is DT_UNKOWN. But we
3112 //can determine the correct type from lstat, so
3113 //we'll only set the dtype here if we could
3114 //correctly determine it (not lstat in the case
3115 //of DT_UNKNOWN in case we don't ever actually
3116 //need the dtype, thus potentially avoiding the
3117 //cost of calling lstat).
3118 static if (__traits(compiles, fd.d_type != DT_UNKNOWN))
3119 {
3120 if (fd.d_type != DT_UNKNOWN)
3121 {
3122 _dType = fd.d_type;
3123 _dTypeSet = true;
3124 }
3125 else
3126 _dTypeSet = false;
3127 }
3128 else
3129 {
3130 // e.g. Solaris does not have the d_type member
3131 _dTypeSet = false;
3132 }
3133 }
3134
3135 @property string name() const pure nothrow
3136 {
3137 return _name;
3138 }
3139
3140 @property bool isDir()
3141 {
3142 _ensureStatOrLStatDone();
3143
3144 return (_statBuf.st_mode & S_IFMT) == S_IFDIR;
3145 }
3146
3147 @property bool isFile()
3148 {
3149 _ensureStatOrLStatDone();
3150
3151 return (_statBuf.st_mode & S_IFMT) == S_IFREG;
3152 }
3153
3154 @property bool isSymlink()
3155 {
3156 _ensureLStatDone();
3157
3158 return (_lstatMode & S_IFMT) == S_IFLNK;
3159 }
3160
3161 @property ulong size()
3162 {
3163 _ensureStatDone();
3164 return _statBuf.st_size;
3165 }
3166
3167 @property SysTime timeStatusChanged()
3168 {
3169 _ensureStatDone();
3170
3171 return statTimeToStdTime!'c'(_statBuf);
3172 }
3173
3174 @property SysTime timeLastAccessed()
3175 {
3176 _ensureStatDone();
3177
3178 return statTimeToStdTime!'a'(_statBuf);
3179 }
3180
3181 @property SysTime timeLastModified()
3182 {
3183 _ensureStatDone();
3184
3185 return statTimeToStdTime!'m'(_statBuf);
3186 }
3187
3188 @property uint attributes()
3189 {
3190 _ensureStatDone();
3191
3192 return _statBuf.st_mode;
3193 }
3194
3195 @property uint linkAttributes()
3196 {
3197 _ensureLStatDone();
3198
3199 return _lstatMode;
3200 }
3201
3202 @property stat_t statBuf()
3203 {
3204 _ensureStatDone();
3205
3206 return _statBuf;
3207 }
3208
3209 private:
3210 /++
3211 This is to support lazy evaluation, because doing stat's is
3212 expensive and not always needed.
3213 +/
3214 void _ensureStatDone() @safe
3215 {
3216 import std.exception : enforce;
3217
3218 static auto trustedStat(in char[] path, stat_t* buf) @trusted
3219 {
3220 return stat(path.tempCString(), buf);
3221 }
3222 if (_didStat)
3223 return;
3224
3225 enforce(trustedStat(_name, &_statBuf) == 0,
3226 "Failed to stat file `" ~ _name ~ "'");
3227
3228 _didStat = true;
3229 }
3230
3231 /++
3232 This is to support lazy evaluation, because doing stat's is
3233 expensive and not always needed.
3234
3235 Try both stat and lstat for isFile and isDir
3236 to detect broken symlinks.
3237 +/
3238 void _ensureStatOrLStatDone()
3239 {
3240 if (_didStat)
3241 return;
3242
3243 if ( stat(_name.tempCString(), &_statBuf) != 0 )
3244 {
3245 _ensureLStatDone();
3246
3247 _statBuf = stat_t.init;
3248 _statBuf.st_mode = S_IFLNK;
3249 }
3250 else
3251 {
3252 _didStat = true;
3253 }
3254 }
3255
3256 /++
3257 This is to support lazy evaluation, because doing stat's is
3258 expensive and not always needed.
3259 +/
3260 void _ensureLStatDone()
3261 {
3262 import std.exception : enforce;
3263
3264 if (_didLStat)
3265 return;
3266
3267 stat_t statbuf = void;
3268
3269 enforce(lstat(_name.tempCString(), &statbuf) == 0,
3270 "Failed to stat file `" ~ _name ~ "'");
3271
3272 _lstatMode = statbuf.st_mode;
3273
3274 _dTypeSet = true;
3275 _didLStat = true;
3276 }
3277
3278 string _name; /// The file or directory represented by this DirEntry.
3279
3280 stat_t _statBuf = void; /// The result of stat().
3281 uint _lstatMode; /// The stat mode from lstat().
3282 ubyte _dType; /// The type of the file.
3283
3284 bool _didLStat = false; /// Whether lstat() has been called for this DirEntry.
3285 bool _didStat = false; /// Whether stat() has been called for this DirEntry.
3286 bool _dTypeSet = false; /// Whether the dType of the file has been set.
3287 }
3288 }
3289
3290 @system unittest
3291 {
3292 version (Windows)
3293 {
3294 if ("C:\\Program Files\\".exists)
3295 {
3296 auto de = DirEntry("C:\\Program Files\\");
3297 assert(!de.isFile);
3298 assert(de.isDir);
3299 assert(!de.isSymlink);
3300 }
3301
3302 if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists)
3303 {
3304 auto de = DirEntry("C:\\Documents and Settings\\");
3305 assert(de.isSymlink);
3306 }
3307
3308 if ("C:\\Windows\\system.ini".exists)
3309 {
3310 auto de = DirEntry("C:\\Windows\\system.ini");
3311 assert(de.isFile);
3312 assert(!de.isDir);
3313 assert(!de.isSymlink);
3314 }
3315 }
3316 else version (Posix)
3317 {
3318 import std.exception : assertThrown;
3319
3320 if (system_directory.exists)
3321 {
3322 {
3323 auto de = DirEntry(system_directory);
3324 assert(!de.isFile);
3325 assert(de.isDir);
3326 assert(!de.isSymlink);
3327 }
3328
3329 immutable symfile = deleteme ~ "_slink\0";
3330 scope(exit) if (symfile.exists) symfile.remove();
3331
3332 core.sys.posix.unistd.symlink(system_directory, symfile.ptr);
3333
3334 {
3335 auto de = DirEntry(symfile);
3336 assert(!de.isFile);
3337 assert(de.isDir);
3338 assert(de.isSymlink);
3339 }
3340
3341 symfile.remove();
3342 core.sys.posix.unistd.symlink((deleteme ~ "_broken_symlink\0").ptr, symfile.ptr);
3343
3344 {
3345 //Issue 8298
3346 DirEntry de = DirEntry(symfile);
3347
3348 assert(!de.isFile);
3349 assert(!de.isDir);
3350 assert(de.isSymlink);
3351 assertThrown(de.size);
3352 assertThrown(de.timeStatusChanged);
3353 assertThrown(de.timeLastAccessed);
3354 assertThrown(de.timeLastModified);
3355 assertThrown(de.attributes);
3356 assertThrown(de.statBuf);
3357 assert(symfile.exists);
3358 symfile.remove();
3359 }
3360 }
3361
3362 if (system_file.exists)
3363 {
3364 auto de = DirEntry(system_file);
3365 assert(de.isFile);
3366 assert(!de.isDir);
3367 assert(!de.isSymlink);
3368 }
3369 }
3370 }
3371
3372 alias PreserveAttributes = Flag!"preserveAttributes";
3373
3374 version (StdDdoc)
3375 {
3376 /// Defaults to $(D Yes.preserveAttributes) on Windows, and the opposite on all other platforms.
3377 PreserveAttributes preserveAttributesDefault;
3378 }
3379 else version (Windows)
3380 {
3381 enum preserveAttributesDefault = Yes.preserveAttributes;
3382 }
3383 else
3384 {
3385 enum preserveAttributesDefault = No.preserveAttributes;
3386 }
3387
3388 /***************************************************
3389 Copy file $(D from) _to file $(D to). File timestamps are preserved.
3390 File attributes are preserved, if $(D preserve) equals $(D Yes.preserveAttributes).
3391 On Windows only $(D Yes.preserveAttributes) (the default on Windows) is supported.
3392 If the target file exists, it is overwritten.
3393
3394 Params:
3395 from = string or range of characters representing the existing file name
3396 to = string or range of characters representing the target file name
3397 preserve = whether to _preserve the file attributes
3398
3399 Throws: $(D FileException) on error.
3400 */
3401 void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault)
3402 if (isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) && !isConvertibleToString!RF &&
3403 isInputRange!RT && !isInfinite!RT && isSomeChar!(ElementEncodingType!RT) && !isConvertibleToString!RT)
3404 {
3405 // Place outside of @trusted block
3406 auto fromz = from.tempCString!FSChar();
3407 auto toz = to.tempCString!FSChar();
3408
3409 static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char))
3410 alias f = from;
3411 else
3412 enum string f = null;
3413
3414 static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char))
3415 alias t = to;
3416 else
3417 enum string t = null;
3418
3419 copyImpl(f, t, fromz, toz, preserve);
3420 }
3421
3422 /// ditto
3423 void copy(RF, RT)(auto ref RF from, auto ref RT to, PreserveAttributes preserve = preserveAttributesDefault)
3424 if (isConvertibleToString!RF || isConvertibleToString!RT)
3425 {
3426 import std.meta : staticMap;
3427 alias Types = staticMap!(convertToString, RF, RT);
3428 copy!Types(from, to, preserve);
3429 }
3430
3431 @safe unittest // issue 15319
3432 {
3433 assert(__traits(compiles, copy("from.txt", "to.txt")));
3434 }
3435
3436 private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz,
3437 PreserveAttributes preserve) @trusted
3438 {
3439 version (Windows)
3440 {
3441 assert(preserve == Yes.preserveAttributes);
3442 immutable result = CopyFileW(fromz, toz, false);
3443 if (!result)
3444 {
3445 import core.stdc.wchar_ : wcslen;
3446 import std.conv : to;
3447
3448 if (!t)
3449 t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
3450
3451 throw new FileException(t);
3452 }
3453 }
3454 else version (Posix)
3455 {
3456 static import core.stdc.stdio;
3457 import std.conv : to, octal;
3458
3459 immutable fdr = core.sys.posix.fcntl.open(fromz, O_RDONLY);
3460 cenforce(fdr != -1, f, fromz);
3461 scope(exit) core.sys.posix.unistd.close(fdr);
3462
3463 stat_t statbufr = void;
3464 cenforce(fstat(fdr, &statbufr) == 0, f, fromz);
3465 //cenforce(core.sys.posix.sys.stat.fstat(fdr, &statbufr) == 0, f, fromz);
3466
3467 immutable fdw = core.sys.posix.fcntl.open(toz,
3468 O_CREAT | O_WRONLY, octal!666);
3469 cenforce(fdw != -1, t, toz);
3470 {
3471 scope(failure) core.sys.posix.unistd.close(fdw);
3472
3473 stat_t statbufw = void;
3474 cenforce(fstat(fdw, &statbufw) == 0, t, toz);
3475 if (statbufr.st_dev == statbufw.st_dev && statbufr.st_ino == statbufw.st_ino)
3476 throw new FileException(t, "Source and destination are the same file");
3477 }
3478
3479 scope(failure) core.stdc.stdio.remove(toz);
3480 {
3481 scope(failure) core.sys.posix.unistd.close(fdw);
3482 cenforce(ftruncate(fdw, 0) == 0, t, toz);
3483
3484 auto BUFSIZ = 4096u * 16;
3485 auto buf = core.stdc.stdlib.malloc(BUFSIZ);
3486 if (!buf)
3487 {
3488 BUFSIZ = 4096;
3489 buf = core.stdc.stdlib.malloc(BUFSIZ);
3490 if (!buf)
3491 {
3492 import core.exception : onOutOfMemoryError;
3493 onOutOfMemoryError();
3494 }
3495 }
3496 scope(exit) core.stdc.stdlib.free(buf);
3497
3498 for (auto size = statbufr.st_size; size; )
3499 {
3500 immutable toxfer = (size > BUFSIZ) ? BUFSIZ : cast(size_t) size;
3501 cenforce(
3502 core.sys.posix.unistd.read(fdr, buf, toxfer) == toxfer
3503 && core.sys.posix.unistd.write(fdw, buf, toxfer) == toxfer,
3504 f, fromz);
3505 assert(size >= toxfer);
3506 size -= toxfer;
3507 }
3508 if (preserve)
3509 cenforce(fchmod(fdw, to!mode_t(statbufr.st_mode)) == 0, f, fromz);
3510 }
3511
3512 cenforce(core.sys.posix.unistd.close(fdw) != -1, f, fromz);
3513
3514 utimbuf utim = void;
3515 utim.actime = cast(time_t) statbufr.st_atime;
3516 utim.modtime = cast(time_t) statbufr.st_mtime;
3517
3518 cenforce(utime(toz, &utim) != -1, f, fromz);
3519 }
3520 }
3521
3522 @safe unittest
3523 {
3524 import std.algorithm, std.file; // issue 14817
3525 auto t1 = deleteme, t2 = deleteme~"2";
3526 scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
3527 write(t1, "11");
3528 copy(t1, t2);
3529 assert(readText(t2) == "11");
3530 write(t1, "2");
3531 copy(t1, t2);
3532 assert(readText(t2) == "2");
3533
3534 import std.utf : byChar;
3535 copy(t1.byChar, t2.byChar);
3536 assert(readText(t2.byChar) == "2");
3537 }
3538
3539 @safe version (Posix) @safe unittest //issue 11434
3540 {
3541 import std.conv : octal;
3542 auto t1 = deleteme, t2 = deleteme~"2";
3543 scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
3544 write(t1, "1");
3545 setAttributes(t1, octal!767);
3546 copy(t1, t2, Yes.preserveAttributes);
3547 assert(readText(t2) == "1");
3548 assert(getAttributes(t2) == octal!100767);
3549 }
3550
3551 @safe unittest // issue 15865
3552 {
3553 import std.exception : assertThrown;
3554 auto t = deleteme;
3555 write(t, "a");
3556 scope(exit) t.remove();
3557 assertThrown!FileException(copy(t, t));
3558 assert(readText(t) == "a");
3559 }
3560
3561 /++
3562 Remove directory and all of its content and subdirectories,
3563 recursively.
3564
3565 Throws:
3566 $(D FileException) if there is an error (including if the given
3567 file is not a directory).
3568 +/
3569 void rmdirRecurse(in char[] pathname)
3570 {
3571 //No references to pathname will be kept after rmdirRecurse,
3572 //so the cast is safe
3573 rmdirRecurse(DirEntry(cast(string) pathname));
3574 }
3575
3576 /++
3577 Remove directory and all of its content and subdirectories,
3578 recursively.
3579
3580 Throws:
3581 $(D FileException) if there is an error (including if the given
3582 file is not a directory).
3583 +/
3584 void rmdirRecurse(ref DirEntry de)
3585 {
3586 if (!de.isDir)
3587 throw new FileException(de.name, "Not a directory");
3588
3589 if (de.isSymlink)
3590 {
3591 version (Windows)
3592 rmdir(de.name);
3593 else
3594 remove(de.name);
3595 }
3596 else
3597 {
3598 // all children, recursively depth-first
3599 foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false))
3600 {
3601 attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name);
3602 }
3603
3604 // the dir itself
3605 rmdir(de.name);
3606 }
3607 }
3608 ///ditto
3609 //Note, without this overload, passing an RValue DirEntry still works, but
3610 //actually fully reconstructs a DirEntry inside the
3611 //"rmdirRecurse(in char[] pathname)" implementation. That is needlessly
3612 //expensive.
3613 //A DirEntry is a bit big (72B), so keeping the "by ref" signature is desirable.
3614 void rmdirRecurse(DirEntry de)
3615 {
3616 rmdirRecurse(de);
3617 }
3618
3619 version (Windows) @system unittest
3620 {
3621 import std.exception : enforce;
3622 auto d = deleteme ~ r".dir\a\b\c\d\e\f\g";
3623 mkdirRecurse(d);
3624 rmdirRecurse(deleteme ~ ".dir");
3625 enforce(!exists(deleteme ~ ".dir"));
3626 }
3627
3628 version (Posix) @system unittest
3629 {
3630 import std.exception : enforce, collectException;
3631 import std.process : executeShell;
3632 collectException(rmdirRecurse(deleteme));
3633 auto d = deleteme~"/a/b/c/d/e/f/g";
3634 enforce(collectException(mkdir(d)));
3635 mkdirRecurse(d);
3636 core.sys.posix.unistd.symlink((deleteme~"/a/b/c\0").ptr,
3637 (deleteme~"/link\0").ptr);
3638 rmdirRecurse(deleteme~"/link");
3639 enforce(exists(d));
3640 rmdirRecurse(deleteme);
3641 enforce(!exists(deleteme));
3642
3643 d = deleteme~"/a/b/c/d/e/f/g";
3644 mkdirRecurse(d);
3645 version (Android) string link_cmd = "ln -s ";
3646 else string link_cmd = "ln -sf ";
3647 executeShell(link_cmd~deleteme~"/a/b/c "~deleteme~"/link");
3648 rmdirRecurse(deleteme);
3649 enforce(!exists(deleteme));
3650 }
3651
3652 @system unittest
3653 {
3654 void[] buf;
3655
3656 buf = new void[10];
3657 (cast(byte[]) buf)[] = 3;
3658 string unit_file = deleteme ~ "-unittest_write.tmp";
3659 if (exists(unit_file)) remove(unit_file);
3660 write(unit_file, buf);
3661 void[] buf2 = read(unit_file);
3662 assert(buf == buf2);
3663
3664 string unit2_file = deleteme ~ "-unittest_write2.tmp";
3665 copy(unit_file, unit2_file);
3666 buf2 = read(unit2_file);
3667 assert(buf == buf2);
3668
3669 remove(unit_file);
3670 assert(!exists(unit_file));
3671 remove(unit2_file);
3672 assert(!exists(unit2_file));
3673 }
3674
3675 /**
3676 * Dictates directory spanning policy for $(D_PARAM dirEntries) (see below).
3677 */
3678 enum SpanMode
3679 {
3680 /** Only spans one directory. */
3681 shallow,
3682 /** Spans the directory in
3683 $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Post-order,
3684 _depth-first $(B post)-order), i.e. the content of any
3685 subdirectory is spanned before that subdirectory itself. Useful
3686 e.g. when recursively deleting files. */
3687 depth,
3688 /** Spans the directory in
3689 $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Pre-order, depth-first
3690 $(B pre)-order), i.e. the content of any subdirectory is spanned
3691 right after that subdirectory itself.
3692
3693 Note that $(D SpanMode.breadth) will not result in all directory
3694 members occurring before any subdirectory members, i.e. it is not
3695 _true
3696 $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Breadth-first_search,
3697 _breadth-first traversal).
3698 */
3699 breadth,
3700 }
3701
3702 private struct DirIteratorImpl
3703 {
3704 import std.array : Appender, appender;
3705 SpanMode _mode;
3706 // Whether we should follow symlinked directories while iterating.
3707 // It also indicates whether we should avoid functions which call
3708 // stat (since we should only need lstat in this case and it would
3709 // be more efficient to not call stat in addition to lstat).
3710 bool _followSymlink;
3711 DirEntry _cur;
3712 Appender!(DirHandle[]) _stack;
3713 Appender!(DirEntry[]) _stashed; //used in depth first mode
3714 //stack helpers
3715 void pushExtra(DirEntry de){ _stashed.put(de); }
3716 //ditto
3717 bool hasExtra(){ return !_stashed.data.empty; }
3718 //ditto
3719 DirEntry popExtra()
3720 {
3721 DirEntry de;
3722 de = _stashed.data[$-1];
3723 _stashed.shrinkTo(_stashed.data.length - 1);
3724 return de;
3725
3726 }
3727 version (Windows)
3728 {
3729 struct DirHandle
3730 {
3731 string dirpath;
3732 HANDLE h;
3733 }
3734
3735 bool stepIn(string directory)
3736 {
3737 import std.path : chainPath;
3738
3739 auto search_pattern = chainPath(directory, "*.*");
3740 WIN32_FIND_DATAW findinfo;
3741 HANDLE h = FindFirstFileW(search_pattern.tempCString!FSChar(), &findinfo);
3742 cenforce(h != INVALID_HANDLE_VALUE, directory);
3743 _stack.put(DirHandle(directory, h));
3744 return toNext(false, &findinfo);
3745 }
3746
3747 bool next()
3748 {
3749 if (_stack.data.empty)
3750 return false;
3751 WIN32_FIND_DATAW findinfo;
3752 return toNext(true, &findinfo);
3753 }
3754
3755 bool toNext(bool fetch, WIN32_FIND_DATAW* findinfo)
3756 {
3757 import core.stdc.wchar_ : wcscmp;
3758
3759 if (fetch)
3760 {
3761 if (FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE)
3762 {
3763 popDirStack();
3764 return false;
3765 }
3766 }
3767 while ( wcscmp(findinfo.cFileName.ptr, ".") == 0
3768 || wcscmp(findinfo.cFileName.ptr, "..") == 0)
3769 if (FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE)
3770 {
3771 popDirStack();
3772 return false;
3773 }
3774 _cur = DirEntry(_stack.data[$-1].dirpath, findinfo);
3775 return true;
3776 }
3777
3778 void popDirStack()
3779 {
3780 assert(!_stack.data.empty);
3781 FindClose(_stack.data[$-1].h);
3782 _stack.shrinkTo(_stack.data.length-1);
3783 }
3784
3785 void releaseDirStack()
3786 {
3787 foreach ( d; _stack.data)
3788 FindClose(d.h);
3789 }
3790
3791 bool mayStepIn()
3792 {
3793 return _followSymlink ? _cur.isDir : _cur.isDir && !_cur.isSymlink;
3794 }
3795 }
3796 else version (Posix)
3797 {
3798 struct DirHandle
3799 {
3800 string dirpath;
3801 DIR* h;
3802 }
3803
3804 bool stepIn(string directory)
3805 {
3806 auto h = directory.length ? opendir(directory.tempCString()) : opendir(".");
3807 cenforce(h, directory);
3808 _stack.put(DirHandle(directory, h));
3809 return next();
3810 }
3811
3812 bool next()
3813 {
3814 if (_stack.data.empty)
3815 return false;
3816 for (dirent* fdata; (fdata = readdir(_stack.data[$-1].h)) != null; )
3817 {
3818 // Skip "." and ".."
3819 if (core.stdc.string.strcmp(fdata.d_name.ptr, ".") &&
3820 core.stdc.string.strcmp(fdata.d_name.ptr, "..") )
3821 {
3822 _cur = DirEntry(_stack.data[$-1].dirpath, fdata);
3823 return true;
3824 }
3825 }
3826 popDirStack();
3827 return false;
3828 }
3829
3830 void popDirStack()
3831 {
3832 assert(!_stack.data.empty);
3833 closedir(_stack.data[$-1].h);
3834 _stack.shrinkTo(_stack.data.length-1);
3835 }
3836
3837 void releaseDirStack()
3838 {
3839 foreach ( d; _stack.data)
3840 closedir(d.h);
3841 }
3842
3843 bool mayStepIn()
3844 {
3845 return _followSymlink ? _cur.isDir : attrIsDir(_cur.linkAttributes);
3846 }
3847 }
3848
3849 this(R)(R pathname, SpanMode mode, bool followSymlink)
3850 if (isInputRange!R && isSomeChar!(ElementEncodingType!R))
3851 {
3852 _mode = mode;
3853 _followSymlink = followSymlink;
3854 _stack = appender(cast(DirHandle[])[]);
3855 if (_mode == SpanMode.depth)
3856 _stashed = appender(cast(DirEntry[])[]);
3857
3858 static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
3859 alias pathnameStr = pathname;
3860 else
3861 {
3862 import std.array : array;
3863 string pathnameStr = pathname.array;
3864 }
3865 if (stepIn(pathnameStr))
3866 {
3867 if (_mode == SpanMode.depth)
3868 while (mayStepIn())
3869 {
3870 auto thisDir = _cur;
3871 if (stepIn(_cur.name))
3872 {
3873 pushExtra(thisDir);
3874 }
3875 else
3876 break;
3877 }
3878 }
3879 }
3880 @property bool empty(){ return _stashed.data.empty && _stack.data.empty; }
3881 @property DirEntry front(){ return _cur; }
3882 void popFront()
3883 {
3884 switch (_mode)
3885 {
3886 case SpanMode.depth:
3887 if (next())
3888 {
3889 while (mayStepIn())
3890 {
3891 auto thisDir = _cur;
3892 if (stepIn(_cur.name))
3893 {
3894 pushExtra(thisDir);
3895 }
3896 else
3897 break;
3898 }
3899 }
3900 else if (hasExtra())
3901 _cur = popExtra();
3902 break;
3903 case SpanMode.breadth:
3904 if (mayStepIn())
3905 {
3906 if (!stepIn(_cur.name))
3907 while (!empty && !next()){}
3908 }
3909 else
3910 while (!empty && !next()){}
3911 break;
3912 default:
3913 next();
3914 }
3915 }
3916
3917 ~this()
3918 {
3919 releaseDirStack();
3920 }
3921 }
3922
3923 struct DirIterator
3924 {
3925 private:
3926 RefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl;
3927 this(string pathname, SpanMode mode, bool followSymlink)
3928 {
3929 impl = typeof(impl)(pathname, mode, followSymlink);
3930 }
3931 public:
3932 @property bool empty(){ return impl.empty; }
3933 @property DirEntry front(){ return impl.front; }
3934 void popFront(){ impl.popFront(); }
3935
3936 }
3937 /++
3938 Returns an input range of $(D DirEntry) that lazily iterates a given directory,
3939 also provides two ways of foreach iteration. The iteration variable can be of
3940 type $(D string) if only the name is needed, or $(D DirEntry)
3941 if additional details are needed. The span _mode dictates how the
3942 directory is traversed. The name of each iterated directory entry
3943 contains the absolute _path.
3944
3945 Params:
3946 path = The directory to iterate over.
3947 If empty, the current directory will be iterated.
3948
3949 pattern = Optional string with wildcards, such as $(RED
3950 "*.d"). When present, it is used to filter the
3951 results by their file name. The supported wildcard
3952 strings are described under $(REF globMatch,
3953 std,_path).
3954
3955 mode = Whether the directory's sub-directories should be
3956 iterated in depth-first port-order ($(LREF depth)),
3957 depth-first pre-order ($(LREF breadth)), or not at all
3958 ($(LREF shallow)).
3959
3960 followSymlink = Whether symbolic links which point to directories
3961 should be treated as directories and their contents
3962 iterated over.
3963
3964 Throws:
3965 $(D FileException) if the directory does not exist.
3966
3967 Example:
3968 --------------------
3969 // Iterate a directory in depth
3970 foreach (string name; dirEntries("destroy/me", SpanMode.depth))
3971 {
3972 remove(name);
3973 }
3974
3975 // Iterate the current directory in breadth
3976 foreach (string name; dirEntries("", SpanMode.breadth))
3977 {
3978 writeln(name);
3979 }
3980
3981 // Iterate a directory and get detailed info about it
3982 foreach (DirEntry e; dirEntries("dmd-testing", SpanMode.breadth))
3983 {
3984 writeln(e.name, "\t", e.size);
3985 }
3986
3987 // Iterate over all *.d files in current directory and all its subdirectories
3988 auto dFiles = dirEntries("", SpanMode.depth).filter!(f => f.name.endsWith(".d"));
3989 foreach (d; dFiles)
3990 writeln(d.name);
3991
3992 // Hook it up with std.parallelism to compile them all in parallel:
3993 foreach (d; parallel(dFiles, 1)) //passes by 1 file to each thread
3994 {
3995 string cmd = "dmd -c " ~ d.name;
3996 writeln(cmd);
3997 std.process.system(cmd);
3998 }
3999
4000 // Iterate over all D source files in current directory and all its
4001 // subdirectories
4002 auto dFiles = dirEntries("","*.{d,di}",SpanMode.depth);
4003 foreach (d; dFiles)
4004 writeln(d.name);
4005 --------------------
4006 +/
4007 auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
4008 {
4009 return DirIterator(path, mode, followSymlink);
4010 }
4011
4012 /// Duplicate functionality of D1's $(D std.file.listdir()):
4013 @safe unittest
4014 {
4015 string[] listdir(string pathname)
4016 {
4017 import std.algorithm;
4018 import std.array;
4019 import std.file;
4020 import std.path;
4021
4022 return std.file.dirEntries(pathname, SpanMode.shallow)
4023 .filter!(a => a.isFile)
4024 .map!(a => std.path.baseName(a.name))
4025 .array;
4026 }
4027
4028 void main(string[] args)
4029 {
4030 import std.stdio;
4031
4032 string[] files = listdir(args[1]);
4033 writefln("%s", files);
4034 }
4035 }
4036
4037 @system unittest
4038 {
4039 import std.algorithm.comparison : equal;
4040 import std.algorithm.iteration : map;
4041 import std.algorithm.searching : startsWith;
4042 import std.array : array;
4043 import std.conv : to;
4044 import std.path : dirEntries, buildPath, absolutePath;
4045 import std.process : thisProcessID;
4046 import std.range.primitives : walkLength;
4047
4048 version (Android)
4049 string testdir = deleteme; // This has to be an absolute path when
4050 // called from a shared library on Android,
4051 // ie an apk
4052 else
4053 string testdir = "deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID); // needs to be relative
4054 mkdirRecurse(buildPath(testdir, "somedir"));
4055 scope(exit) rmdirRecurse(testdir);
4056 write(buildPath(testdir, "somefile"), null);
4057 write(buildPath(testdir, "somedir", "somedeepfile"), null);
4058
4059 // testing range interface
4060 size_t equalEntries(string relpath, SpanMode mode)
4061 {
4062 import std.exception : enforce;
4063 auto len = enforce(walkLength(dirEntries(absolutePath(relpath), mode)));
4064 assert(walkLength(dirEntries(relpath, mode)) == len);
4065 assert(equal(
4066 map!(a => absolutePath(a.name))(dirEntries(relpath, mode)),
4067 map!(a => a.name)(dirEntries(absolutePath(relpath), mode))));
4068 return len;
4069 }
4070
4071 assert(equalEntries(testdir, SpanMode.shallow) == 2);
4072 assert(equalEntries(testdir, SpanMode.depth) == 3);
4073 assert(equalEntries(testdir, SpanMode.breadth) == 3);
4074
4075 // testing opApply
4076 foreach (string name; dirEntries(testdir, SpanMode.breadth))
4077 {
4078 //writeln(name);
4079 assert(name.startsWith(testdir));
4080 }
4081 foreach (DirEntry e; dirEntries(absolutePath(testdir), SpanMode.breadth))
4082 {
4083 //writeln(name);
4084 assert(e.isFile || e.isDir, e.name);
4085 }
4086
4087 //issue 7264
4088 foreach (string name; dirEntries(testdir, "*.d", SpanMode.breadth))
4089 {
4090
4091 }
4092 foreach (entry; dirEntries(testdir, SpanMode.breadth))
4093 {
4094 static assert(is(typeof(entry) == DirEntry));
4095 }
4096 //issue 7138
4097 auto a = array(dirEntries(testdir, SpanMode.shallow));
4098
4099 // issue 11392
4100 auto dFiles = dirEntries(testdir, SpanMode.shallow);
4101 foreach (d; dFiles){}
4102
4103 // issue 15146
4104 dirEntries("", SpanMode.shallow).walkLength();
4105 }
4106
4107 /// Ditto
4108 auto dirEntries(string path, string pattern, SpanMode mode,
4109 bool followSymlink = true)
4110 {
4111 import std.algorithm.iteration : filter;
4112 import std.path : globMatch, baseName;
4113
4114 bool f(DirEntry de) { return globMatch(baseName(de.name), pattern); }
4115 return filter!f(DirIterator(path, mode, followSymlink));
4116 }
4117
4118 @system unittest
4119 {
4120 import std.stdio : writefln;
4121 immutable dpath = deleteme ~ "_dir";
4122 immutable fpath = deleteme ~ "_file";
4123 immutable sdpath = deleteme ~ "_sdir";
4124 immutable sfpath = deleteme ~ "_sfile";
4125 scope(exit)
4126 {
4127 if (dpath.exists) rmdirRecurse(dpath);
4128 if (fpath.exists) remove(fpath);
4129 if (sdpath.exists) remove(sdpath);
4130 if (sfpath.exists) remove(sfpath);
4131 }
4132
4133 mkdir(dpath);
4134 write(fpath, "hello world");
4135 version (Posix)
4136 {
4137 core.sys.posix.unistd.symlink((dpath ~ '\0').ptr, (sdpath ~ '\0').ptr);
4138 core.sys.posix.unistd.symlink((fpath ~ '\0').ptr, (sfpath ~ '\0').ptr);
4139 }
4140
4141 static struct Flags { bool dir, file, link; }
4142 auto tests = [dpath : Flags(true), fpath : Flags(false, true)];
4143 version (Posix)
4144 {
4145 tests[sdpath] = Flags(true, false, true);
4146 tests[sfpath] = Flags(false, true, true);
4147 }
4148
4149 auto past = Clock.currTime() - 2.seconds;
4150 auto future = past + 4.seconds;
4151
4152 foreach (path, flags; tests)
4153 {
4154 auto de = DirEntry(path);
4155 assert(de.name == path);
4156 assert(de.isDir == flags.dir);
4157 assert(de.isFile == flags.file);
4158 assert(de.isSymlink == flags.link);
4159
4160 assert(de.isDir == path.isDir);
4161 assert(de.isFile == path.isFile);
4162 assert(de.isSymlink == path.isSymlink);
4163 assert(de.size == path.getSize());
4164 assert(de.attributes == getAttributes(path));
4165 assert(de.linkAttributes == getLinkAttributes(path));
4166
4167 scope(failure) writefln("[%s] [%s] [%s] [%s]", past, de.timeLastAccessed, de.timeLastModified, future);
4168 assert(de.timeLastAccessed > past);
4169 assert(de.timeLastAccessed < future);
4170 assert(de.timeLastModified > past);
4171 assert(de.timeLastModified < future);
4172
4173 assert(attrIsDir(de.attributes) == flags.dir);
4174 assert(attrIsDir(de.linkAttributes) == (flags.dir && !flags.link));
4175 assert(attrIsFile(de.attributes) == flags.file);
4176 assert(attrIsFile(de.linkAttributes) == (flags.file && !flags.link));
4177 assert(!attrIsSymlink(de.attributes));
4178 assert(attrIsSymlink(de.linkAttributes) == flags.link);
4179
4180 version (Windows)
4181 {
4182 assert(de.timeCreated > past);
4183 assert(de.timeCreated < future);
4184 }
4185 else version (Posix)
4186 {
4187 assert(de.timeStatusChanged > past);
4188 assert(de.timeStatusChanged < future);
4189 assert(de.attributes == de.statBuf.st_mode);
4190 }
4191 }
4192 }
4193
4194
4195 /**
4196 * Reads a file line by line and parses the line into a single value or a
4197 * $(REF Tuple, std,typecons) of values depending on the length of `Types`.
4198 * The lines are parsed using the specified format string. The format string is
4199 * passed to $(REF formattedRead, std,_format), and therefore must conform to the
4200 * _format string specification outlined in $(MREF std, _format).
4201 *
4202 * Params:
4203 * Types = the types that each of the elements in the line should be returned as
4204 * filename = the name of the file to read
4205 * format = the _format string to use when reading
4206 *
4207 * Returns:
4208 * If only one type is passed, then an array of that type. Otherwise, an
4209 * array of $(REF Tuple, std,typecons)s.
4210 *
4211 * Throws:
4212 * `Exception` if the format string is malformed. Also, throws `Exception`
4213 * if any of the lines in the file are not fully consumed by the call
4214 * to $(REF formattedRead, std,_format). Meaning that no empty lines or lines
4215 * with extra characters are allowed.
4216 */
4217 Select!(Types.length == 1, Types[0][], Tuple!(Types)[])
4218 slurp(Types...)(string filename, in char[] format)
4219 {
4220 import std.array : appender;
4221 import std.conv : text;
4222 import std.exception : enforce;
4223 import std.format : formattedRead;
4224 import std.stdio : File;
4225
4226 auto app = appender!(typeof(return))();
4227 ElementType!(typeof(return)) toAdd;
4228 auto f = File(filename);
4229 scope(exit) f.close();
4230 foreach (line; f.byLine())
4231 {
4232 formattedRead(line, format, &toAdd);
4233 enforce(line.empty,
4234 text("Trailing characters at the end of line: `", line,
4235 "'"));
4236 app.put(toAdd);
4237 }
4238 return app.data;
4239 }
4240
4241 ///
4242 @system unittest
4243 {
4244 import std.typecons : tuple;
4245
4246 scope(exit)
4247 {
4248 assert(exists(deleteme));
4249 remove(deleteme);
4250 }
4251
4252 write(deleteme, "12 12.25\n345 1.125"); // deleteme is the name of a temporary file
4253
4254 // Load file; each line is an int followed by comma, whitespace and a
4255 // double.
4256 auto a = slurp!(int, double)(deleteme, "%s %s");
4257 assert(a.length == 2);
4258 assert(a[0] == tuple(12, 12.25));
4259 assert(a[1] == tuple(345, 1.125));
4260 }
4261
4262
4263 /**
4264 Returns the path to a directory for temporary files.
4265
4266 On Windows, this function returns the result of calling the Windows API function
4267 $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992.aspx, $(D GetTempPath)).
4268
4269 On POSIX platforms, it searches through the following list of directories
4270 and returns the first one which is found to exist:
4271 $(OL
4272 $(LI The directory given by the $(D TMPDIR) environment variable.)
4273 $(LI The directory given by the $(D TEMP) environment variable.)
4274 $(LI The directory given by the $(D TMP) environment variable.)
4275 $(LI $(D /tmp))
4276 $(LI $(D /var/tmp))
4277 $(LI $(D /usr/tmp))
4278 )
4279
4280 On all platforms, $(D tempDir) returns $(D ".") on failure, representing
4281 the current working directory.
4282
4283 The return value of the function is cached, so the procedures described
4284 above will only be performed the first time the function is called. All
4285 subsequent runs will return the same string, regardless of whether
4286 environment variables and directory structures have changed in the
4287 meantime.
4288
4289 The POSIX $(D tempDir) algorithm is inspired by Python's
4290 $(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, $(D tempfile.tempdir)).
4291 */
4292 string tempDir() @trusted
4293 {
4294 static string cache;
4295 if (cache is null)
4296 {
4297 version (Windows)
4298 {
4299 import std.conv : to;
4300 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992(v=vs.85).aspx
4301 wchar[MAX_PATH + 2] buf;
4302 DWORD len = GetTempPathW(buf.length, buf.ptr);
4303 if (len) cache = buf[0 .. len].to!string;
4304 }
4305 else version (Posix)
4306 {
4307 import std.process : environment;
4308 // This function looks through the list of alternative directories
4309 // and returns the first one which exists and is a directory.
4310 static string findExistingDir(T...)(lazy T alternatives)
4311 {
4312 foreach (dir; alternatives)
4313 if (!dir.empty && exists(dir)) return dir;
4314 return null;
4315 }
4316
4317 cache = findExistingDir(environment.get("TMPDIR"),
4318 environment.get("TEMP"),
4319 environment.get("TMP"),
4320 "/tmp",
4321 "/var/tmp",
4322 "/usr/tmp");
4323 }
4324 else static assert(false, "Unsupported platform");
4325
4326 if (cache is null) cache = getcwd();
4327 }
4328 return cache;
4329 }
4330