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