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