]> git.ipfire.org Git - thirdparty/gcc.git/blame - 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
CommitLineData
b4c522fa
IB
1// Written in the D programming language.
2
3/**
4Utilities for manipulating files and scanning directories. Functions
5fee5ec3 5in this module handle files as a unit, e.g., read or write one file
b4c522fa
IB
6at a time. For opening files and manipulating them via handles refer
7to module $(MREF std, stdio).
8
9$(SCRIPT inhibitQuickIndex = 1;)
5fee5ec3 10$(DIVC quickindex,
b4c522fa
IB
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)
5fee5ec3
IB
58 $(LREF timeLastAccessed)
59 $(LREF timeStatusChanged)
b4c522fa
IB
60))
61$(TR $(TD Other) $(TD
62 $(LREF DirEntry)
63 $(LREF FileException)
64 $(LREF PreserveAttributes)
65 $(LREF SpanMode)
5fee5ec3
IB
66 $(LREF getAvailableDiskSpace)
67))
b4c522fa 68))
b4c522fa
IB
69
70
5fee5ec3 71Copyright: Copyright The D Language Foundation 2007 - 2011.
b4c522fa
IB
72See_Also: The $(HTTP ddili.org/ders/d.en/files.html, official tutorial) for an
73introduction to working with files in D, module
74$(MREF std, stdio) for opening files and manipulating them via handles,
75and module $(MREF std, path) for manipulating path strings.
76
77License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
78Authors: $(HTTP digitalmars.com, Walter Bright),
79 $(HTTP erdani.org, Andrei Alexandrescu),
5fee5ec3
IB
80 $(HTTP jmdavisprog.com, Jonathan M Davis)
81Source: $(PHOBOSSRC std/file.d)
b4c522fa
IB
82 */
83module std.file;
84
85import core.stdc.errno, core.stdc.stdlib, core.stdc.string;
86import core.time : abs, dur, hnsecs, seconds;
87
88import std.datetime.date : DateTime;
89import std.datetime.systime : Clock, SysTime, unixTimeToStdTime;
90import std.internal.cstring;
91import std.meta;
92import std.range.primitives;
93import std.traits;
94import std.typecons;
95
5fee5ec3
IB
96version (OSX)
97 version = Darwin;
98else version (iOS)
99 version = Darwin;
100else version (TVOS)
101 version = Darwin;
102else version (WatchOS)
103 version = Darwin;
104
b4c522fa
IB
105version (Windows)
106{
5fee5ec3 107 import core.sys.windows.winbase, core.sys.windows.winnt, std.windows.syserror;
b4c522fa
IB
108}
109else 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}
114else
115 static assert(false, "Module " ~ .stringof ~ " not implemented for this OS.");
116
117// Character type used for operating system filesystem APIs
118version (Windows)
119{
5fee5ec3 120 private alias FSChar = WCHAR; // WCHAR can be aliased to wchar or wchar_t
b4c522fa
IB
121}
122else version (Posix)
123{
124 private alias FSChar = char;
125}
126else
127 static assert(0);
128
129// Purposefully not documented. Use at your own risk
130@property string deleteme() @safe
131{
5fee5ec3 132 import std.conv : text;
b4c522fa
IB
133 import std.path : buildPath;
134 import std.process : thisProcessID;
135
5fee5ec3
IB
136 enum base = "deleteme.dmd.unittest.pid";
137 static string fileName;
b4c522fa 138
5fee5ec3
IB
139 if (!fileName)
140 fileName = text(buildPath(tempDir(), base), thisProcessID);
141 return fileName;
b4c522fa
IB
142}
143
5fee5ec3 144version (StdUnittest) private struct TestAliasedString
b4c522fa 145{
5fee5ec3 146 string get() @safe @nogc pure nothrow return scope { return _s; }
b4c522fa
IB
147 alias get this;
148 @disable this(this);
149 string _s;
150}
151
152version (Android)
153{
154 package enum system_directory = "/system/etc";
155 package enum system_file = "/system/etc/hosts";
156}
157else 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 +/
167class FileException : Exception
168{
169 import std.conv : text, to;
170
171 /++
172 OS error code.
173 +/
174 immutable uint errno;
175
5fee5ec3 176 private this(scope const(char)[] name, scope const(char)[] msg, string file, size_t line, uint errno) @safe pure
5a36cae2
IB
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
b4c522fa
IB
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.
5fee5ec3 192 file = The file where the error occurred.
b4c522fa
IB
193 line = The _line where the error occurred.
194 +/
5fee5ec3 195 this(scope const(char)[] name, scope const(char)[] msg, string file = __FILE__, size_t line = __LINE__) @safe pure
b4c522fa 196 {
5a36cae2 197 this(name, msg, file, line, 0);
b4c522fa
IB
198 }
199
200 /++
201 Constructor which takes the error number ($(LUCKY GetLastError)
5fee5ec3 202 in Windows, $(D_PARAM errno) in POSIX).
b4c522fa
IB
203
204 Params:
205 name = Name of file for which the error occurred.
206 errno = The error number.
5fee5ec3
IB
207 file = The file where the error occurred.
208 Defaults to `__FILE__`.
b4c522fa 209 line = The _line where the error occurred.
5fee5ec3 210 Defaults to `__LINE__`.
b4c522fa 211 +/
5fee5ec3 212 version (Windows) this(scope const(char)[] name,
b4c522fa
IB
213 uint errno = .GetLastError(),
214 string file = __FILE__,
215 size_t line = __LINE__) @safe
216 {
5a36cae2 217 this(name, sysErrorString(errno), file, line, errno);
b4c522fa 218 }
5fee5ec3 219 else version (Posix) this(scope const(char)[] name,
b4c522fa
IB
220 uint errno = .errno,
221 string file = __FILE__,
222 size_t line = __LINE__) @trusted
223 {
224 import std.exception : errnoString;
5a36cae2 225 this(name, errnoString(errno), file, line, errno);
b4c522fa
IB
226 }
227}
228
5fee5ec3
IB
229///
230@safe unittest
231{
232 import std.exception : assertThrown;
233
234 assertThrown!FileException("non.existing.file.".readText);
235}
236
237private T cenforce(T)(T condition, lazy scope const(char)[] name, string file = __FILE__, size_t line = __LINE__)
b4c522fa
IB
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
251version (Windows)
252@trusted
5fee5ec3 253private T cenforce(T)(T condition, scope const(char)[] name, scope const(FSChar)* namez,
b4c522fa
IB
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
269version (Posix)
270@trusted
5fee5ec3 271private T cenforce(T)(T condition, scope const(char)[] name, scope const(FSChar)* namez,
b4c522fa
IB
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
5fee5ec3 286// https://issues.dlang.org/show_bug.cgi?id=17102
b4c522fa
IB
287@safe unittest
288{
b4c522fa
IB
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/********************************************
5fee5ec3
IB
302Read entire contents of file `name` and returns it as an untyped
303array. If the file size is larger than `upTo`, only `upTo`
b4c522fa
IB
304bytes are _read.
305
306Params:
307 name = string or range of characters representing the file _name
308 upTo = if present, the maximum number of bytes to _read
309
310Returns: Untyped array of bytes _read.
311
312Throws: $(LREF FileException) on error.
313 */
314
315void[] read(R)(R name, size_t upTo = size_t.max)
316if (isInputRange!R && isSomeChar!(ElementEncodingType!R) && !isInfinite!R &&
317 !isConvertibleToString!R)
318{
5fee5ec3 319 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
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
5fee5ec3 335 std.file.write(deleteme, "1234"); // deleteme is the name of a temporary file
b4c522fa
IB
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
342void[] read(R)(auto ref R name, size_t upTo = size_t.max)
343if (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
5fee5ec3
IB
353version (Posix) private void[] readImpl(scope const(char)[] name, scope const(FSChar)* namez,
354 size_t upTo = size_t.max) @trusted
b4c522fa
IB
355{
356 import core.memory : GC;
357 import std.algorithm.comparison : min;
b4c522fa 358 import std.conv : to;
5fee5ec3 359 import std.experimental.checkedint : checked;
b4c522fa
IB
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));
5fee5ec3 380 void[] result = GC.malloc(initialAlloc, GC.BlkAttr.NO_SCAN)[0 .. initialAlloc];
b4c522fa 381 scope(failure) GC.free(result.ptr);
5fee5ec3
IB
382
383 auto size = checked(size_t(0));
b4c522fa
IB
384
385 for (;;)
386 {
5fee5ec3
IB
387 immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size.get,
388 (min(result.length, upTo) - size).get);
b4c522fa
IB
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;
5fee5ec3 395 result = GC.realloc(result.ptr, newAlloc.get, GC.BlkAttr.NO_SCAN)[0 .. newAlloc.get];
b4c522fa
IB
396 }
397
398 return result.length - size >= maxSlackMemoryAllowed
5fee5ec3
IB
399 ? GC.realloc(result.ptr, size.get, GC.BlkAttr.NO_SCAN)[0 .. size.get]
400 : result[0 .. size.get];
b4c522fa
IB
401}
402
403
5fee5ec3
IB
404version (Windows) private void[] readImpl(scope const(char)[] name, scope const(FSChar)* namez,
405 size_t upTo = size_t.max) @trusted
b4c522fa
IB
406{
407 import core.memory : GC;
408 import std.algorithm.comparison : min;
5fee5ec3 409 static trustedCreateFileW(scope const(wchar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode,
b4c522fa 410 SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition,
5fee5ec3 411 DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
b4c522fa
IB
412 {
413 return CreateFileW(namez, dwDesiredAccess, dwShareMode,
414 lpSecurityAttributes, dwCreationDisposition,
415 dwFlagsAndAttributes, hTemplateFile);
416
417 }
5fee5ec3 418 static trustedCloseHandle(HANDLE hObject)
b4c522fa
IB
419 {
420 return CloseHandle(hObject);
421 }
5fee5ec3 422 static trustedGetFileSize(HANDLE hFile, out ulong fileSize)
b4c522fa
IB
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 }
5fee5ec3 431 static trustedReadFile(HANDLE hFile, void *lpBuffer, ulong nNumberOfBytesToRead)
b4c522fa
IB
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);
5fee5ec3 459 auto buf = () { return GC.malloc(size, GC.BlkAttr.NO_SCAN)[0 .. size]; } ();
b4c522fa
IB
460
461 scope(failure)
462 {
5fee5ec3 463 () { GC.free(buf.ptr); } ();
b4c522fa
IB
464 }
465
466 if (size)
467 cenforce(trustedReadFile(h, &buf[0], size), name, namez);
468 return buf[0 .. size];
469}
470
471version (linux) @safe unittest
472{
473 // A file with "zero" length that doesn't have 0 length at all
5fee5ec3 474 auto s = std.file.readText("/proc/cpuinfo");
b4c522fa
IB
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
5fee5ec3
IB
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.
b4c522fa 494
5fee5ec3
IB
495 Params:
496 S = the string type of the file
497 name = string or range of characters representing the file _name
b4c522fa 498
5fee5ec3 499 Returns: Array of characters read.
b4c522fa 500
5fee5ec3
IB
501 Throws: $(LREF FileException) if there is an error reading the file,
502 $(REF UTFException, std, utf) on UTF decoding error.
503+/
504S readText(S = string, R)(auto ref R name)
505if (isSomeString!S && (isInputRange!R && !isInfinite!R && isSomeChar!(ElementType!R) || is(StringTypeOf!R)))
b4c522fa 506{
5fee5ec3
IB
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);
b4c522fa
IB
588 validate(result);
589 return result;
590}
591
5fee5ec3 592/// Read file with UTF-8 text.
b4c522fa
IB
593@safe unittest
594{
b4c522fa
IB
595 write(deleteme, "abc"); // deleteme is the name of a temporary file
596 scope(exit) remove(deleteme);
597 string content = readText(deleteme);
5fee5ec3 598 assert(content == "abc");
b4c522fa
IB
599}
600
5fee5ec3
IB
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
b4c522fa 615{
5fee5ec3
IB
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);
b4c522fa
IB
625}
626
627@safe unittest
628{
629 static assert(__traits(compiles, readText(TestAliasedString(null))));
630}
631
5fee5ec3
IB
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
b4c522fa 727/*********************************************
5fee5ec3 728Write `buffer` to file `name`.
b4c522fa
IB
729
730Creates the file if it does not already exist.
731
732Params:
733 name = string or range of characters representing the file _name
734 buffer = data to be written to file
735
5fee5ec3 736Throws: $(LREF FileException) on error.
b4c522fa
IB
737
738See_also: $(REF toFile, std,stdio)
739 */
740void write(R)(R name, const void[] buffer)
741if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
742 !isConvertibleToString!R)
743{
5fee5ec3 744 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
745 writeImpl(name, name.tempCString!FSChar(), buffer, false);
746 else
747 writeImpl(null, name.tempCString!FSChar(), buffer, false);
748}
749
750///
5fee5ec3 751@safe unittest
b4c522fa
IB
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
5fee5ec3
IB
761 const bytes = read(deleteme);
762 const fileInts = () @trusted { return cast(int[]) bytes; }();
763 assert(fileInts == a);
b4c522fa
IB
764}
765
766/// ditto
767void write(R)(auto ref R name, const void[] buffer)
768if (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/*********************************************
5fee5ec3 779Appends `buffer` to file `name`.
b4c522fa
IB
780
781Creates the file if it does not already exist.
782
783Params:
784 name = string or range of characters representing the file _name
785 buffer = data to be appended to file
786
5fee5ec3 787Throws: $(LREF FileException) on error.
b4c522fa
IB
788 */
789void append(R)(R name, const void[] buffer)
790if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
791 !isConvertibleToString!R)
792{
5fee5ec3 793 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
794 writeImpl(name, name.tempCString!FSChar(), buffer, true);
795 else
796 writeImpl(null, name.tempCString!FSChar(), buffer, true);
797}
798
799///
5fee5ec3 800@safe unittest
b4c522fa
IB
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);
5fee5ec3
IB
812 const bytes = read(deleteme);
813 const fileInts = () @trusted { return cast(int[]) bytes; }();
814 assert(fileInts == a ~ b);
b4c522fa
IB
815}
816
817/// ditto
818void append(R)(auto ref R name, const void[] buffer)
819if (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
5fee5ec3 829// POSIX implementation helper for write and append
b4c522fa 830
5fee5ec3
IB
831version (Posix) private void writeImpl(scope const(char)[] name, scope const(FSChar)* namez,
832 scope const(void)[] buffer, bool append) @trusted
b4c522fa
IB
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
5fee5ec3
IB
862version (Windows) private void writeImpl(scope const(char)[] name, scope const(FSChar)* namez,
863 scope const(void)[] buffer, bool append) @trusted
b4c522fa
IB
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/***************************************************
5fee5ec3 903 * Rename file `from` _to `to`, moving it between directories if required.
b4c522fa 904 * If the target file exists, it is overwritten.
5fee5ec3
IB
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 *
b4c522fa
IB
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
5fee5ec3
IB
916 *
917 * Throws: $(LREF FileException) on error.
b4c522fa
IB
918 */
919void rename(RF, RT)(RF from, RT to)
920if ((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
5fee5ec3 929 static if (isNarrowString!RF && is(immutable ElementEncodingType!RF == immutable char))
b4c522fa
IB
930 alias f = from;
931 else
932 enum string f = null;
933
5fee5ec3 934 static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable char))
b4c522fa
IB
935 alias t = to;
936 else
937 enum string t = null;
938
939 renameImpl(f, t, fromz, toz);
940}
941
942/// ditto
943void rename(RF, RT)(auto ref RF from, auto ref RT to)
944if (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
5fee5ec3
IB
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
975private void renameImpl(scope const(char)[] f, scope const(char)[] t,
976 scope const(FSChar)* fromz, scope const(FSChar)* toz) @trusted
b4c522fa
IB
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();
5fee5ec3 1013
b4c522fa
IB
1014 write(t1, "1");
1015 rename(t1, t2);
1016 assert(readText(t2) == "1");
5fee5ec3 1017
b4c522fa
IB
1018 write(t1, "2");
1019 rename(t1, t2.byWchar);
1020 assert(readText(t2) == "2");
1021}
1022
b4c522fa 1023/***************************************************
5fee5ec3 1024Delete file `name`.
b4c522fa
IB
1025
1026Params:
1027 name = string or range of characters representing the file _name
1028
5fee5ec3 1029Throws: $(LREF FileException) on error.
b4c522fa
IB
1030 */
1031void remove(R)(R name)
1032if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1033 !isConvertibleToString!R)
1034{
5fee5ec3 1035 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
1036 removeImpl(name, name.tempCString!FSChar());
1037 else
1038 removeImpl(null, name.tempCString!FSChar());
1039}
1040
1041/// ditto
1042void remove(R)(auto ref R name)
1043if (isConvertibleToString!R)
1044{
1045 remove!(StringTypeOf!R)(name);
1046}
1047
5fee5ec3
IB
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
b4c522fa
IB
1060@safe unittest
1061{
1062 static assert(__traits(compiles, remove(TestAliasedString("foo"))));
1063}
1064
5fee5ec3 1065private void removeImpl(scope const(char)[] name, scope const(FSChar)* namez) @trusted
b4c522fa
IB
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
1086version (Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(R)(R name)
1087if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R))
1088{
1089 auto namez = name.tempCString!FSChar();
1090
1091 WIN32_FILE_ATTRIBUTE_DATA fad = void;
1092
5fee5ec3 1093 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa 1094 {
5fee5ec3
IB
1095 static void getFA(scope const(char)[] name, scope const(FSChar)* namez,
1096 out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
b4c522fa
IB
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 {
5fee5ec3 1106 static void getFA(scope const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
b4c522fa
IB
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
1120version (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
5fee5ec3
IB
1128/**
1129Get size of file `name` in bytes.
b4c522fa
IB
1130
1131Params:
1132 name = string or range of characters representing the file _name
5fee5ec3
IB
1133Returns:
1134 The size of file in bytes.
1135Throws:
1136 $(LREF FileException) on error (e.g., file not found).
b4c522fa
IB
1137 */
1138ulong getSize(R)(R name)
1139if (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 }
5fee5ec3 1155 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
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
1166ulong getSize(R)(auto ref R name)
1167if (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
5fee5ec3 1177///
b4c522fa
IB
1178@safe unittest
1179{
5fee5ec3
IB
1180 scope(exit) deleteme.remove;
1181
b4c522fa
IB
1182 // create a file of size 1
1183 write(deleteme, "a");
5fee5ec3
IB
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;
b4c522fa
IB
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
b4c522fa
IB
1203// Reads a time field from a stat_t with full precision.
1204version (Posix)
5fee5ec3 1205private SysTime statTimeToStdTime(char which)(ref const stat_t statbuf)
b4c522fa
IB
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/++
5fee5ec3 1226 Get the access and modified times of file or folder `name`.
b4c522fa
IB
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:
5fee5ec3 1234 $(LREF FileException) on error.
b4c522fa
IB
1235 +/
1236void getTimes(R)(R name,
1237 out SysTime accessTime,
1238 out SysTime modificationTime)
1239if (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
5fee5ec3 1262 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
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
1274void getTimes(R)(auto ref R name,
1275 out SysTime accessTime,
1276 out SysTime modificationTime)
1277if (isConvertibleToString!R)
1278{
1279 return getTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
1280}
1281
5fee5ec3
IB
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
b4c522fa
IB
1304@safe unittest
1305{
1306 SysTime atime, mtime;
1307 static assert(__traits(compiles, getTimes(TestAliasedString("foo"), atime, mtime)));
1308}
1309
5fee5ec3 1310@safe unittest
b4c522fa
IB
1311{
1312 import std.stdio : writefln;
1313
1314 auto currTime = Clock.currTime();
1315
1316 write(deleteme, "a");
5fee5ec3 1317 scope(exit) assert(deleteme.exists), deleteme.remove;
b4c522fa 1318
5fee5ec3
IB
1319 SysTime accessTime1;
1320 SysTime modificationTime1;
b4c522fa
IB
1321
1322 getTimes(deleteme, accessTime1, modificationTime1);
1323
5fee5ec3 1324 enum leeway = 5.seconds;
b4c522fa
IB
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
1365version (StdDdoc)
1366{
1367 /++
1368 $(BLUE This function is Windows-Only.)
1369
5fee5ec3 1370 Get creation/access/modified times of file `name`.
b4c522fa 1371
5fee5ec3
IB
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.
b4c522fa
IB
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:
5fee5ec3 1382 $(LREF FileException) on error.
b4c522fa
IB
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}
1391else 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
1420version (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
5fee5ec3
IB
1490version (Darwin)
1491private
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}
b4c522fa
IB
1504
1505/++
5fee5ec3 1506 Set access/modified times of file or folder `name`.
b4c522fa
IB
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:
5fee5ec3 1514 $(LREF FileException) on error.
b4c522fa
IB
1515 +/
1516void setTimes(R)(R name,
1517 SysTime accessTime,
1518 SysTime modificationTime)
1519if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1520 !isConvertibleToString!R)
1521{
5fee5ec3
IB
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}
b4c522fa 1529
5fee5ec3
IB
1530///
1531@safe unittest
1532{
1533 import std.datetime : DateTime, hnsecs, SysTime;
b4c522fa 1534
5fee5ec3
IB
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
1550void setTimes(R)(auto ref R name,
1551 SysTime accessTime,
1552 SysTime modificationTime)
1553if (isConvertibleToString!R)
1554{
1555 setTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
1556}
b4c522fa 1557
5fee5ec3
IB
1558private 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;
b4c522fa
IB
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);
5fee5ec3 1575 auto h = CreateFileW(namez, defaults);
b4c522fa 1576
b4c522fa
IB
1577 cenforce(h != INVALID_HANDLE_VALUE, names, namez);
1578
1579 scope(exit)
5fee5ec3 1580 cenforce(CloseHandle(h), names, namez);
b4c522fa 1581
5fee5ec3 1582 cenforce(SetFileTime(h, null, &ta, &tm), names, namez);
b4c522fa 1583 }
5fee5ec3 1584 else
b4c522fa 1585 {
b4c522fa
IB
1586 static if (is(typeof(&utimensat)))
1587 {
b4c522fa 1588 timespec[2] t = void;
b4c522fa
IB
1589 t[0] = accessTime.toTimeSpec();
1590 t[1] = modificationTime.toTimeSpec();
5fee5ec3 1591 cenforce(utimensat(AT_FDCWD, namez, t, 0) == 0, names, namez);
b4c522fa
IB
1592 }
1593 else
1594 {
5fee5ec3 1595 version (Darwin)
b4c522fa 1596 {
5fee5ec3
IB
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.
b4c522fa
IB
1608 }
1609 timeval[2] t = void;
b4c522fa
IB
1610 t[0] = accessTime.toTimeVal();
1611 t[1] = modificationTime.toTimeVal();
5fee5ec3 1612 cenforce(utimes(namez, t) == 0, names, namez);
b4c522fa
IB
1613 }
1614 }
1615}
1616
b4c522fa
IB
1617@safe unittest
1618{
1619 if (false) // Test instatiation
1620 setTimes(TestAliasedString("foo"), SysTime.init, SysTime.init);
1621}
1622
5fee5ec3 1623@safe unittest
b4c522fa
IB
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
5fee5ec3
IB
1659 Params:
1660 name = the name of the file to check
1661 Returns:
1662 A $(REF SysTime,std,datetime,systime).
b4c522fa 1663 Throws:
5fee5ec3 1664 $(LREF FileException) if the given file does not exist.
b4c522fa
IB
1665+/
1666SysTime timeLastModified(R)(R name)
1667if (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
5fee5ec3 1688 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
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
1699SysTime timeLastModified(R)(auto ref R name)
1700if (isConvertibleToString!R)
1701{
1702 return timeLastModified!(StringTypeOf!R)(name);
1703}
1704
5fee5ec3
IB
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
b4c522fa
IB
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
5fee5ec3 1725 file does not exist, returns `returnIfMissing`.
b4c522fa
IB
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
5fee5ec3
IB
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
b4c522fa
IB
1735 correctly prompts building it.
1736
1737 Params:
5fee5ec3 1738 name = The name of the file to get the modification time for.
b4c522fa 1739 returnIfMissing = The time to return if the given file does not exist.
5fee5ec3
IB
1740 Returns:
1741 A $(REF SysTime,std,datetime,systime).
b4c522fa
IB
1742
1743Example:
1744--------------------
5fee5ec3 1745if (source.timeLastModified >= target.timeLastModified(SysTime.min))
b4c522fa
IB
1746{
1747 // must (re)build
1748}
1749else
1750{
1751 // target is up-to-date
1752}
1753--------------------
1754+/
1755SysTime timeLastModified(R)(R name, SysTime returnIfMissing)
1756if (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
5fee5ec3 1785///
b4c522fa
IB
1786@safe unittest
1787{
5fee5ec3
IB
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
1802version (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}
1829else 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");
b4c522fa
IB
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.
b1a207c6
IB
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
b4c522fa 1889version (FreeBSD) {} else
c8bf6646 1890version (DragonFlyBSD) {} else
b4c522fa 1891version (OSX) {} else
5fee5ec3 1892@safe unittest
b4c522fa
IB
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;
5fee5ec3 1907 () @trusted { Thread.sleep(20.msecs); }();
b4c522fa
IB
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 */
1919bool exists(R)(R name)
1920if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
1921 !isConvertibleToString!R)
1922{
1923 return existsImpl(name.tempCString!FSChar());
1924}
1925
1926/// ditto
1927bool exists(R)(auto ref R name)
1928if (isConvertibleToString!R)
1929{
1930 return exists!(StringTypeOf!R)(name);
1931}
1932
5fee5ec3
IB
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
b4c522fa
IB
1946private 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
5fee5ec3 1983///
b4c522fa
IB
1984@safe unittest
1985{
5fee5ec3
IB
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);
b4c522fa
IB
1991}
1992
5fee5ec3
IB
1993// https://issues.dlang.org/show_bug.cgi?id=16573
1994@safe unittest
b4c522fa
IB
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
5fee5ec3 2003 Note that the file attributes on Windows and POSIX systems are
b4c522fa
IB
2004 completely different. On Windows, they're what is returned by
2005 $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx,
5fee5ec3
IB
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`)
b4c522fa
IB
2009 function.
2010
5fee5ec3 2011 On POSIX systems, if the given file is a symbolic link, then
b4c522fa
IB
2012 attributes are the attributes of the file pointed to by the symbolic
2013 link.
2014
2015 Params:
5fee5ec3
IB
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.
b4c522fa
IB
2020 +/
2021uint getAttributes(R)(R name)
2022if (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
5fee5ec3 2034 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
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
5fee5ec3 2051 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
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
2062uint getAttributes(R)(auto ref R name)
2063if (isConvertibleToString!R)
2064{
2065 return getAttributes!(StringTypeOf!R)(name);
2066}
2067
5fee5ec3
IB
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
b4c522fa
IB
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:
5fee5ec3 2124 $(LREF FileException) on error.
b4c522fa
IB
2125 +/
2126uint getLinkAttributes(R)(R name)
2127if (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;
5fee5ec3 2142 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
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
2152uint getLinkAttributes(R)(auto ref R name)
2153if (isConvertibleToString!R)
2154{
2155 return getLinkAttributes!(StringTypeOf!R)(name);
2156}
2157
5fee5ec3
IB
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
b4c522fa
IB
2216@safe unittest
2217{
2218 static assert(__traits(compiles, getLinkAttributes(TestAliasedString(null))));
2219}
2220
2221/++
2222 Set the _attributes of the given file.
2223
5fee5ec3
IB
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
b4c522fa
IB
2228 Params:
2229 name = the file _name
2230 attributes = the _attributes to set the file to
2231
2232 Throws:
5fee5ec3 2233 $(LREF FileException) if the given file does not exist.
b4c522fa
IB
2234 +/
2235void setAttributes(R)(R name, uint attributes)
2236if (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 }
5fee5ec3 2246 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
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);
5fee5ec3 2260 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
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
2269void setAttributes(R)(auto ref R name, uint attributes)
2270if (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
5fee5ec3
IB
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
b4c522fa
IB
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:
5fee5ec3 2342 $(LREF FileException) if the given file does not exist.
b4c522fa
IB
2343 +/
2344@property bool isDir(R)(R name)
2345if (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)
2360if (isConvertibleToString!R)
2361{
2362 return name.isDir!(StringTypeOf!R);
2363}
2364
5fee5ec3
IB
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
b4c522fa
IB
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
5fee5ec3 2410@safe unittest
b4c522fa
IB
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
5fee5ec3 2433+/
b4c522fa
IB
2434bool 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
5fee5ec3
IB
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
b4c522fa
IB
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,
5fee5ec3 2504 either `isFile` or `isDir` will return true for any given file.
b4c522fa 2505
5fee5ec3
IB
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
b4c522fa 2509 particular file (in which case, it's a special file). You can use
5fee5ec3
IB
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
b4c522fa
IB
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:
5fee5ec3
IB
2522 $(LREF FileException) if the given file does not exist.
2523+/
b4c522fa
IB
2524@property bool isFile(R)(R name)
2525if (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)
2536if (isConvertibleToString!R)
2537{
2538 return isFile!(StringTypeOf!R)(name);
2539}
2540
5fee5ec3
IB
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
b4c522fa
IB
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
5fee5ec3 2597 `attrIsFile` or `attrIsDir` will return `true` for the
b4c522fa
IB
2598 _attributes of any given file.
2599
5fee5ec3
IB
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`
b4c522fa
IB
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
5fee5ec3 2605 it is (see the man page for `stat` for more information).
b4c522fa
IB
2606
2607 Params:
2608 attributes = The file _attributes.
2609
2610 Returns:
2611 true if the given file _attributes are for a file
2612
2613Example:
2614--------------------
2615assert(attrIsFile(getAttributes("/etc/fonts/fonts.conf")));
2616assert(attrIsFile(getLinkAttributes("/etc/fonts/fonts.conf")));
2617--------------------
2618 +/
2619bool 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
5fee5ec3
IB
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
b4c522fa
IB
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
5fee5ec3 2688 On Windows, returns `true` when the file is either a symbolic link or a
b4c522fa
IB
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:
5fee5ec3 2698 $(LREF FileException) if the given file does not exist.
b4c522fa
IB
2699 +/
2700@property bool isSymlink(R)(R name)
2701if (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)
2712if (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
5fee5ec3
IB
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
b4c522fa
IB
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
5fee5ec3 2823 On Windows, return `true` when the file is either a symbolic link or a
b4c522fa
IB
2824 junction point.
2825
2826 Params:
2827 attributes = The file attributes.
2828
2829 Returns:
2830 true if attributes are for a symbolic link
2831
2832Example:
2833--------------------
2834core.sys.posix.unistd.symlink("/etc/fonts/fonts.conf", "/tmp/alink");
2835
2836assert(!getAttributes("/tmp/alink").isSymlink);
2837assert(getLinkAttributes("/tmp/alink").isSymlink);
2838--------------------
2839 +/
2840bool 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
5fee5ec3
IB
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/**
2873Change directory to `pathname`. Equivalent to `cd` on
2874Windows and POSIX.
b4c522fa 2875
5fee5ec3
IB
2876Params:
2877 pathname = the directory to step into
2878
2879Throws: $(LREF FileException) on error.
b4c522fa
IB
2880 */
2881void chdir(R)(R pathname)
2882if (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 }
5fee5ec3 2902 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
2903 alias pathStr = pathname;
2904 else
2905 string pathStr = null;
2906 cenforce(trustedChdir(pathz), pathStr, pathz);
2907}
2908
2909/// ditto
2910void chdir(R)(auto ref R pathname)
2911if (isConvertibleToString!R)
2912{
2913 return chdir!(StringTypeOf!R)(pathname);
2914}
2915
5fee5ec3
IB
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
b4c522fa
IB
2937@safe unittest
2938{
2939 static assert(__traits(compiles, chdir(TestAliasedString(null))));
2940}
2941
5fee5ec3
IB
2942/**
2943Make a new directory `pathname`.
2944
2945Params:
2946 pathname = the path of the directory to make
b4c522fa 2947
5fee5ec3
IB
2948Throws:
2949 $(LREF FileException) on POSIX or $(LREF WindowsException) on Windows
2950 if an error occured.
b4c522fa
IB
2951 */
2952void mkdir(R)(R pathname)
2953if (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 }
5fee5ec3 2965 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
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 }
5fee5ec3 2979 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
2980 alias pathStr = pathname;
2981 else
2982 string pathStr = null;
2983 cenforce(trustedMkdir(pathz, octal!777) == 0, pathStr, pathz);
2984 }
2985}
2986
2987/// ditto
2988void mkdir(R)(auto ref R pathname)
2989if (isConvertibleToString!R)
2990{
2991 return mkdir!(StringTypeOf!R)(pathname);
2992}
2993
2994@safe unittest
2995{
5a0aa603 2996 import std.file : mkdir;
b4c522fa
IB
2997 static assert(__traits(compiles, mkdir(TestAliasedString(null))));
2998}
2999
5fee5ec3
IB
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
b4c522fa
IB
3019// Same as mkdir but ignores "already exists" errors.
3020// Returns: "true" if the directory was created,
3021// "false" if it already existed.
5fee5ec3 3022private bool ensureDirExists()(scope const(char)[] pathname)
b4c522fa
IB
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
5fee5ec3
IB
3045/**
3046Make directory and all parent directories as needed.
3047
3048Does nothing if the directory specified by
3049`pathname` already exists.
3050
3051Params:
3052 pathname = the full path of the directory to create
b4c522fa 3053
5fee5ec3
IB
3054Throws: $(LREF FileException) on error.
3055 */
3056void mkdirRecurse(scope const(char)[] pathname) @safe
b4c522fa
IB
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
5fee5ec3
IB
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
b4c522fa
IB
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
5fee5ec3 3129 // https://issues.dlang.org/show_bug.cgi?id=3570
b4c522fa
IB
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/****************************************************
5fee5ec3 3149Remove directory `pathname`.
b4c522fa
IB
3150
3151Params:
3152 pathname = Range or string specifying the directory name
3153
5fee5ec3 3154Throws: $(LREF FileException) on error.
b4c522fa
IB
3155 */
3156void rmdir(R)(R pathname)
3157if (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 }
5fee5ec3 3177 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
3178 alias pathStr = pathname;
3179 else
3180 string pathStr = null;
3181 cenforce(trustedRmdir(pathz), pathStr, pathz);
3182}
3183
3184/// ditto
3185void rmdir(R)(auto ref R pathname)
3186if (isConvertibleToString!R)
3187{
3188 rmdir!(StringTypeOf!R)(pathname);
3189}
3190
3191@safe unittest
3192{
3193 static assert(__traits(compiles, rmdir(TestAliasedString(null))));
3194}
3195
5fee5ec3
IB
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
b4c522fa 3207/++
5fee5ec3 3208 $(BLUE This function is POSIX-Only.)
b4c522fa
IB
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:
5fee5ec3 3220 $(LREF FileException) on error (which includes if the _symlink already
b4c522fa
IB
3221 exists).
3222 +/
3223version (StdDdoc) void symlink(RO, RL)(RO original, RL link)
3224if ((isInputRange!RO && !isInfinite!RO && isSomeChar!(ElementEncodingType!RO) ||
3225 isConvertibleToString!RO) &&
3226 (isInputRange!RL && !isInfinite!RL && isSomeChar!(ElementEncodingType!RL) ||
3227 isConvertibleToString!RL));
3228else version (Posix) void symlink(RO, RL)(RO original, RL link)
3229if ((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
3251version (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
3294version (Posix) @safe unittest
3295{
3296 static assert(__traits(compiles,
3297 symlink(TestAliasedString(null), TestAliasedString(null))));
3298}
3299
3300
3301/++
5fee5ec3 3302 $(BLUE This function is POSIX-Only.)
b4c522fa
IB
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:
5fee5ec3 3310 $(LREF FileException) on error.
b4c522fa
IB
3311 +/
3312version (StdDdoc) string readLink(R)(R link)
3313if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) ||
3314 isConvertibleToString!R);
3315else version (Posix) string readLink(R)(R link)
3316if (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
3365version (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
3385version (Posix) @safe unittest
3386{
3387 static assert(__traits(compiles, readLink(TestAliasedString("foo"))));
3388}
3389
3390version (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);
5fee5ec3 3399 InputRange!(ElementType!string) linkr = inputRangeObject(link);
b4c522fa
IB
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.
5fee5ec3 3409 * Throws: $(LREF FileException) on error.
b4c522fa 3410 */
5fee5ec3 3411version (Windows) string getcwd() @trusted
b4c522fa
IB
3412{
3413 import std.conv : to;
5fee5ec3 3414 import std.experimental.checkedint : checked;
b4c522fa
IB
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 */
5fee5ec3
IB
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;
b4c522fa
IB
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 {
5fee5ec3
IB
3436 auto cn = checked(n);
3437 auto ptr = cast(wchar*) malloc((cn * wchar.sizeof).get);
b4c522fa 3438 scope(exit) free(ptr);
5fee5ec3
IB
3439 immutable n2 = GetCurrentDirectoryW(cn.get, ptr);
3440 cenforce(n2 && n2 < cn, "getcwd");
b4c522fa
IB
3441 return ptr[0 .. n2].to!string;
3442 }
3443}
5fee5ec3 3444else version (Solaris) string getcwd() @trusted
b4c522fa
IB
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}
5fee5ec3 3454else version (Posix) string getcwd() @trusted
b4c522fa
IB
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
5fee5ec3
IB
3462///
3463@safe unittest
b4c522fa
IB
3464{
3465 auto s = getcwd();
3466 assert(s.length);
3467}
3468
b4c522fa
IB
3469/**
3470 * Returns the full path of the current executable.
3471 *
5fee5ec3
IB
3472 * Returns:
3473 * The path of the executable as a `string`.
3474 *
b4c522fa
IB
3475 * Throws:
3476 * $(REF1 Exception, object)
3477 */
5fee5ec3 3478@trusted string thisExePath()
b4c522fa 3479{
5fee5ec3 3480 version (Darwin)
b4c522fa 3481 {
5fee5ec3 3482 import core.sys.darwin.mach.dyld : _NSGetExecutablePath;
b4c522fa
IB
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 }
b1a207c6
IB
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 }
b4c522fa
IB
3542 else version (FreeBSD)
3543 {
b1a207c6 3544 import core.sys.freebsd.sys.sysctl : sysctl, CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME;
b4c522fa 3545 import std.exception : errnoEnforce, assumeUnique;
b4c522fa
IB
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 {
b1a207c6
IB
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;
b4c522fa 3575 }
b1a207c6 3576 else version (OpenBSD)
c8bf6646 3577 {
b1a207c6
IB
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 }
c8bf6646 3613 }
b4c522fa
IB
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
5fee5ec3 3626///
b4c522fa
IB
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
3637version (StdDdoc)
3638{
3639 /++
5fee5ec3 3640 Info on a file, similar to what you'd get from stat on a POSIX system.
b4c522fa
IB
3641 +/
3642 struct DirEntry
3643 {
5fee5ec3 3644 @safe:
b4c522fa 3645 /++
5fee5ec3 3646 Constructs a `DirEntry` for the given file (or directory).
b4c522fa
IB
3647
3648 Params:
3649 path = The file (or directory) to get a DirEntry for.
3650
3651 Throws:
5fee5ec3 3652 $(LREF FileException) if the file does not exist.
b4c522fa
IB
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 /++
5fee5ec3 3666 Returns the path to the file represented by this `DirEntry`.
b4c522fa
IB
3667
3668Example:
3669--------------------
3670auto de1 = DirEntry("/etc/fonts/fonts.conf");
3671assert(de1.name == "/etc/fonts/fonts.conf");
3672
3673auto de2 = DirEntry("/usr/share/include");
3674assert(de2.name == "/usr/share/include");
3675--------------------
3676 +/
5fee5ec3 3677 @property string name() const return scope;
b4c522fa
IB
3678
3679
3680 /++
5fee5ec3 3681 Returns whether the file represented by this `DirEntry` is a
b4c522fa
IB
3682 directory.
3683
3684Example:
3685--------------------
3686auto de1 = DirEntry("/etc/fonts/fonts.conf");
3687assert(!de1.isDir);
3688
3689auto de2 = DirEntry("/usr/share/include");
3690assert(de2.isDir);
3691--------------------
3692 +/
5fee5ec3 3693 @property bool isDir() scope;
b4c522fa
IB
3694
3695
3696 /++
5fee5ec3 3697 Returns whether the file represented by this `DirEntry` is a file.
b4c522fa
IB
3698
3699 On Windows, if a file is not a directory, then it's a file. So,
5fee5ec3 3700 either `isFile` or `isDir` will return `true`.
b4c522fa 3701
5fee5ec3 3702 On POSIX systems, if `isFile` is `true`, that indicates that
b4c522fa 3703 the file is a regular file (e.g. not a block not device). So, on
5fee5ec3
IB
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
b4c522fa
IB
3707 information about a special file (see the stat man page for more
3708 details).
3709
3710Example:
3711--------------------
3712auto de1 = DirEntry("/etc/fonts/fonts.conf");
3713assert(de1.isFile);
3714
3715auto de2 = DirEntry("/usr/share/include");
3716assert(!de2.isFile);
3717--------------------
3718 +/
5fee5ec3 3719 @property bool isFile() scope;
b4c522fa
IB
3720
3721 /++
5fee5ec3 3722 Returns whether the file represented by this `DirEntry` is a
b4c522fa
IB
3723 symbolic link.
3724
5fee5ec3 3725 On Windows, return `true` when the file is either a symbolic
b4c522fa
IB
3726 link or a junction point.
3727 +/
5fee5ec3 3728 @property bool isSymlink() scope;
b4c522fa
IB
3729
3730 /++
5fee5ec3 3731 Returns the size of the the file represented by this `DirEntry`
b4c522fa
IB
3732 in bytes.
3733 +/
5fee5ec3 3734 @property ulong size() scope;
b4c522fa
IB
3735
3736 /++
3737 $(BLUE This function is Windows-Only.)
3738
3739 Returns the creation time of the file represented by this
5fee5ec3 3740 `DirEntry`.
b4c522fa 3741 +/
5fee5ec3 3742 @property SysTime timeCreated() const scope;
b4c522fa
IB
3743
3744 /++
5fee5ec3 3745 Returns the time that the file represented by this `DirEntry` was
b4c522fa
IB
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
5fee5ec3
IB
3750 `timeLastAccessed` will return the same value as
3751 `timeLastModified`.
b4c522fa 3752 +/
5fee5ec3 3753 @property SysTime timeLastAccessed() scope;
b4c522fa
IB
3754
3755 /++
5fee5ec3 3756 Returns the time that the file represented by this `DirEntry` was
b4c522fa
IB
3757 last modified.
3758 +/
5fee5ec3 3759 @property SysTime timeLastModified() scope;
b4c522fa
IB
3760
3761 /++
5fee5ec3 3762 $(BLUE This function is POSIX-Only.)
b4c522fa 3763
5fee5ec3
IB
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
b4c522fa 3773 completely different. On, Windows, they're what is returned by
5fee5ec3 3774 `GetFileAttributes`
b4c522fa 3775 $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx, GetFileAttributes)
5fee5ec3
IB
3776 Whereas, an POSIX systems, they're the `st_mode` value which is
3777 part of the `stat` struct gotten by calling `stat`.
b4c522fa 3778
5fee5ec3 3779 On POSIX systems, if the file represented by this `DirEntry` is a
b4c522fa
IB
3780 symbolic link, then _attributes are the _attributes of the file
3781 pointed to by the symbolic link.
3782 +/
5fee5ec3 3783 @property uint attributes() scope;
b4c522fa
IB
3784
3785 /++
5fee5ec3
IB
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`.
b4c522fa 3790
5fee5ec3 3791 On Windows, `linkAttributes` is identical to `attributes`. It
b4c522fa
IB
3792 exists on Windows so that you don't have to special-case code for
3793 Windows when dealing with symbolic links.
3794 +/
5fee5ec3 3795 @property uint linkAttributes() scope;
b4c522fa
IB
3796
3797 version (Windows)
3798 alias stat_t = void*;
3799
3800 /++
5fee5ec3 3801 $(BLUE This function is POSIX-Only.)
b4c522fa 3802
5fee5ec3 3803 The `stat` struct gotten from calling `stat`.
b4c522fa 3804 +/
5fee5ec3 3805 @property stat_t statBuf() scope;
b4c522fa
IB
3806 }
3807}
3808else version (Windows)
3809{
3810 struct DirEntry
3811 {
5fee5ec3 3812 @safe:
b4c522fa
IB
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
5fee5ec3 3835 private this(string path, WIN32_FIND_DATAW *fd) @trusted
b4c522fa
IB
3836 {
3837 import core.stdc.wchar_ : wcslen;
3838 import std.conv : to;
3839 import std.datetime.systime : FILETIMEToSysTime;
3840 import std.path : buildPath;
3841
5fee5ec3
IB
3842 fd.cFileName[$ - 1] = 0;
3843
3844 size_t clength = wcslen(&fd.cFileName[0]);
b4c522fa
IB
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
5fee5ec3 3853 @property string name() const pure nothrow return scope
b4c522fa
IB
3854 {
3855 return _name;
3856 }
3857
5fee5ec3 3858 @property bool isDir() const pure nothrow scope
b4c522fa
IB
3859 {
3860 return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
3861 }
3862
5fee5ec3 3863 @property bool isFile() const pure nothrow scope
b4c522fa
IB
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
5fee5ec3 3871 @property bool isSymlink() const pure nothrow scope
b4c522fa
IB
3872 {
3873 return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
3874 }
3875
5fee5ec3 3876 @property ulong size() const pure nothrow scope
b4c522fa
IB
3877 {
3878 return _size;
3879 }
3880
5fee5ec3 3881 @property SysTime timeCreated() const pure nothrow scope
b4c522fa
IB
3882 {
3883 return cast(SysTime)_timeCreated;
3884 }
3885
5fee5ec3 3886 @property SysTime timeLastAccessed() const pure nothrow scope
b4c522fa
IB
3887 {
3888 return cast(SysTime)_timeLastAccessed;
3889 }
3890
5fee5ec3 3891 @property SysTime timeLastModified() const pure nothrow scope
b4c522fa
IB
3892 {
3893 return cast(SysTime)_timeLastModified;
3894 }
3895
5fee5ec3 3896 @property uint attributes() const pure nothrow scope
b4c522fa
IB
3897 {
3898 return _attributes;
3899 }
3900
5fee5ec3 3901 @property uint linkAttributes() const pure nothrow scope
b4c522fa
IB
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}
3917else version (Posix)
3918{
3919 struct DirEntry
3920 {
5fee5ec3 3921 @safe:
b4c522fa
IB
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
5fee5ec3 3937 private this(string path, core.sys.posix.dirent.dirent* fd) @safe
b4c522fa
IB
3938 {
3939 import std.path : buildPath;
3940
c8bf6646
IB
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])());
b4c522fa
IB
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
5fee5ec3 3976 @property string name() const pure nothrow return scope
b4c522fa
IB
3977 {
3978 return _name;
3979 }
3980
5fee5ec3 3981 @property bool isDir() scope
b4c522fa
IB
3982 {
3983 _ensureStatOrLStatDone();
3984
3985 return (_statBuf.st_mode & S_IFMT) == S_IFDIR;
3986 }
3987
5fee5ec3 3988 @property bool isFile() scope
b4c522fa
IB
3989 {
3990 _ensureStatOrLStatDone();
3991
3992 return (_statBuf.st_mode & S_IFMT) == S_IFREG;
3993 }
3994
5fee5ec3 3995 @property bool isSymlink() scope
b4c522fa
IB
3996 {
3997 _ensureLStatDone();
3998
3999 return (_lstatMode & S_IFMT) == S_IFLNK;
4000 }
4001
5fee5ec3 4002 @property ulong size() scope
b4c522fa
IB
4003 {
4004 _ensureStatDone();
4005 return _statBuf.st_size;
4006 }
4007
5fee5ec3 4008 @property SysTime timeStatusChanged() scope
b4c522fa
IB
4009 {
4010 _ensureStatDone();
4011
4012 return statTimeToStdTime!'c'(_statBuf);
4013 }
4014
5fee5ec3 4015 @property SysTime timeLastAccessed() scope
b4c522fa
IB
4016 {
4017 _ensureStatDone();
4018
4019 return statTimeToStdTime!'a'(_statBuf);
4020 }
4021
5fee5ec3 4022 @property SysTime timeLastModified() scope
b4c522fa
IB
4023 {
4024 _ensureStatDone();
4025
4026 return statTimeToStdTime!'m'(_statBuf);
4027 }
4028
5fee5ec3 4029 @property uint attributes() scope
b4c522fa
IB
4030 {
4031 _ensureStatDone();
4032
4033 return _statBuf.st_mode;
4034 }
4035
5fee5ec3 4036 @property uint linkAttributes() scope
b4c522fa
IB
4037 {
4038 _ensureLStatDone();
4039
4040 return _lstatMode;
4041 }
4042
5fee5ec3 4043 @property stat_t statBuf() scope
b4c522fa
IB
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 +/
5fee5ec3 4055 void _ensureStatDone() @trusted scope
b4c522fa
IB
4056 {
4057 import std.exception : enforce;
4058
b4c522fa
IB
4059 if (_didStat)
4060 return;
4061
5fee5ec3 4062 enforce(stat(_name.tempCString(), &_statBuf) == 0,
b4c522fa
IB
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 +/
5fee5ec3 4075 void _ensureStatOrLStatDone() @trusted scope
b4c522fa
IB
4076 {
4077 if (_didStat)
4078 return;
4079
5fee5ec3 4080 if (stat(_name.tempCString(), &_statBuf) != 0)
b4c522fa
IB
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 +/
5fee5ec3 4097 void _ensureLStatDone() @trusted scope
b4c522fa
IB
4098 {
4099 import std.exception : enforce;
4100
4101 if (_didLStat)
4102 return;
4103
4104 stat_t statbuf = void;
b4c522fa
IB
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
5fee5ec3
IB
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.
b4c522fa
IB
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 {
5fee5ec3 4181 // https://issues.dlang.org/show_bug.cgi?id=8298
b4c522fa
IB
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
4208alias PreserveAttributes = Flag!"preserveAttributes";
4209
4210version (StdDdoc)
4211{
5fee5ec3 4212 /// Defaults to `Yes.preserveAttributes` on Windows, and the opposite on all other platforms.
b4c522fa
IB
4213 PreserveAttributes preserveAttributesDefault;
4214}
4215else version (Windows)
4216{
4217 enum preserveAttributesDefault = Yes.preserveAttributes;
4218}
4219else
4220{
4221 enum preserveAttributesDefault = No.preserveAttributes;
4222}
4223
4224/***************************************************
5fee5ec3
IB
4225Copy file `from` _to file `to`. File timestamps are preserved.
4226File attributes are preserved, if `preserve` equals `Yes.preserveAttributes`.
4227On Windows only `Yes.preserveAttributes` (the default on Windows) is supported.
b4c522fa
IB
4228If the target file exists, it is overwritten.
4229
4230Params:
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
5fee5ec3 4235Throws: $(LREF FileException) on error.
b4c522fa
IB
4236 */
4237void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault)
4238if (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
5fee5ec3 4245 static if (isNarrowString!RF && is(immutable ElementEncodingType!RF == immutable char))
b4c522fa
IB
4246 alias f = from;
4247 else
4248 enum string f = null;
4249
5fee5ec3 4250 static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable char))
b4c522fa
IB
4251 alias t = to;
4252 else
4253 enum string t = null;
4254
4255 copyImpl(f, t, fromz, toz, preserve);
4256}
4257
4258/// ditto
4259void copy(RF, RT)(auto ref RF from, auto ref RT to, PreserveAttributes preserve = preserveAttributesDefault)
4260if (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
5fee5ec3
IB
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
b4c522fa
IB
4290{
4291 assert(__traits(compiles, copy("from.txt", "to.txt")));
4292}
4293
5fee5ec3
IB
4294private void copyImpl(scope const(char)[] f, scope const(char)[] t,
4295 scope const(FSChar)* fromz, scope const(FSChar)* toz,
4296 PreserveAttributes preserve) @trusted
b4c522fa
IB
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;
5fee5ec3 4306 import std.format : format;
b4c522fa 4307
5fee5ec3
IB
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)]);
b4c522fa
IB
4315 if (!t)
4316 t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
4317
5fee5ec3 4318 throw new FileException(format!"Copy from %s to %s"(f, t));
b4c522fa
IB
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
5fee5ec3 4381 setTimesImpl(t, toz, statbufr.statTimeToStdTime!'a', statbufr.statTimeToStdTime!'m');
b4c522fa
IB
4382 }
4383}
4384
5fee5ec3 4385// https://issues.dlang.org/show_bug.cgi?id=14817
b4c522fa
IB
4386@safe unittest
4387{
5fee5ec3 4388 import std.algorithm, std.file;
b4c522fa
IB
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");
5fee5ec3
IB
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));
b4c522fa
IB
4409}
4410
5fee5ec3
IB
4411// https://issues.dlang.org/show_bug.cgi?id=11434
4412@safe version (Posix) @safe unittest
b4c522fa
IB
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
5fee5ec3
IB
4424// https://issues.dlang.org/show_bug.cgi?id=15865
4425@safe unittest
b4c522fa
IB
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
5fee5ec3
IB
4435// https://issues.dlang.org/show_bug.cgi?id=19834
4436version (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
b4c522fa
IB
4448/++
4449 Remove directory and all of its content and subdirectories,
4450 recursively.
4451
5fee5ec3
IB
4452 Params:
4453 pathname = the path of the directory to completely remove
4454 de = The $(LREF DirEntry) to remove
4455
b4c522fa 4456 Throws:
5fee5ec3 4457 $(LREF FileException) if there is an error (including if the given
b4c522fa
IB
4458 file is not a directory).
4459 +/
5fee5ec3 4460void rmdirRecurse(scope const(char)[] pathname) @safe
b4c522fa
IB
4461{
4462 //No references to pathname will be kept after rmdirRecurse,
4463 //so the cast is safe
5fee5ec3 4464 rmdirRecurse(DirEntry((() @trusted => cast(string) pathname)()));
b4c522fa
IB
4465}
4466
5fee5ec3
IB
4467/// ditto
4468void rmdirRecurse(ref DirEntry de) @safe
b4c522fa
IB
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 {
5fee5ec3
IB
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 }();
b4c522fa
IB
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.
5fee5ec3 4503void rmdirRecurse(DirEntry de) @safe
b4c522fa
IB
4504{
4505 rmdirRecurse(de);
4506}
4507
5fee5ec3
IB
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
b4c522fa
IB
4523version (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
4532version (Posix) @system unittest
4533{
4534 import std.exception : enforce, collectException;
5fee5ec3 4535
b4c522fa
IB
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);
5fee5ec3
IB
4549 const linkTarget = deleteme ~ "/link";
4550 symlink(deleteme ~ "/a/b/c", linkTarget);
b4c522fa
IB
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 */
4581enum 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
5fee5ec3 4596 Note that `SpanMode.breadth` will not result in all directory
b4c522fa
IB
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
5fee5ec3
IB
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
b4c522fa
IB
4635private struct DirIteratorImpl
4636{
5fee5ec3 4637 @safe:
b4c522fa
IB
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;
5fee5ec3
IB
4645 DirHandle[] _stack;
4646 DirEntry[] _stashed; //used in depth first mode
4647
b4c522fa 4648 //stack helpers
5fee5ec3
IB
4649 void pushExtra(DirEntry de)
4650 {
4651 _stashed ~= de;
4652 }
4653
b4c522fa 4654 //ditto
5fee5ec3
IB
4655 bool hasExtra()
4656 {
4657 return _stashed.length != 0;
4658 }
4659
b4c522fa
IB
4660 //ditto
4661 DirEntry popExtra()
4662 {
4663 DirEntry de;
5fee5ec3
IB
4664 de = _stashed[$-1];
4665 _stashed.popBack();
b4c522fa 4666 return de;
b4c522fa 4667 }
5fee5ec3 4668
b4c522fa
IB
4669 version (Windows)
4670 {
5fee5ec3 4671 WIN32_FIND_DATAW _findinfo;
b4c522fa
IB
4672 struct DirHandle
4673 {
4674 string dirpath;
4675 HANDLE h;
4676 }
4677
5fee5ec3 4678 bool stepIn(string directory) @safe
b4c522fa
IB
4679 {
4680 import std.path : chainPath;
5fee5ec3
IB
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 }
b4c522fa 4687
5fee5ec3 4688 HANDLE h = trustedFindFirstFileW(searchPattern, &_findinfo);
b4c522fa 4689 cenforce(h != INVALID_HANDLE_VALUE, directory);
5fee5ec3
IB
4690 _stack ~= DirHandle(directory, h);
4691 return toNext(false, &_findinfo);
b4c522fa
IB
4692 }
4693
4694 bool next()
4695 {
5fee5ec3 4696 if (_stack.length == 0)
b4c522fa 4697 return false;
5fee5ec3 4698 return toNext(true, &_findinfo);
b4c522fa
IB
4699 }
4700
5fee5ec3 4701 bool toNext(bool fetch, WIN32_FIND_DATAW* findinfo) @trusted
b4c522fa
IB
4702 {
4703 import core.stdc.wchar_ : wcscmp;
4704
4705 if (fetch)
4706 {
5fee5ec3 4707 if (FindNextFileW(_stack[$-1].h, findinfo) == FALSE)
b4c522fa
IB
4708 {
4709 popDirStack();
4710 return false;
4711 }
4712 }
5fee5ec3
IB
4713 while (wcscmp(&findinfo.cFileName[0], ".") == 0 ||
4714 wcscmp(&findinfo.cFileName[0], "..") == 0)
4715 if (FindNextFileW(_stack[$-1].h, findinfo) == FALSE)
b4c522fa
IB
4716 {
4717 popDirStack();
4718 return false;
4719 }
5fee5ec3 4720 _cur = DirEntry(_stack[$-1].dirpath, findinfo);
b4c522fa
IB
4721 return true;
4722 }
4723
5fee5ec3 4724 void popDirStack() @trusted
b4c522fa 4725 {
5fee5ec3
IB
4726 assert(_stack.length != 0);
4727 FindClose(_stack[$-1].h);
4728 _stack.popBack();
b4c522fa
IB
4729 }
4730
5fee5ec3 4731 void releaseDirStack() @trusted
b4c522fa 4732 {
5fee5ec3 4733 foreach (d; _stack)
b4c522fa
IB
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 {
5fee5ec3
IB
4752 static auto trustedOpendir(string dir) @trusted
4753 {
4754 return opendir(dir.tempCString());
4755 }
4756
4757 auto h = directory.length ? trustedOpendir(directory) : trustedOpendir(".");
b4c522fa 4758 cenforce(h, directory);
5fee5ec3 4759 _stack ~= (DirHandle(directory, h));
b4c522fa
IB
4760 return next();
4761 }
4762
5fee5ec3 4763 bool next() @trusted
b4c522fa 4764 {
5fee5ec3 4765 if (_stack.length == 0)
b4c522fa 4766 return false;
5fee5ec3
IB
4767
4768 for (dirent* fdata; (fdata = readdir(_stack[$-1].h)) != null; )
b4c522fa
IB
4769 {
4770 // Skip "." and ".."
5fee5ec3
IB
4771 if (core.stdc.string.strcmp(&fdata.d_name[0], ".") &&
4772 core.stdc.string.strcmp(&fdata.d_name[0], ".."))
b4c522fa 4773 {
5fee5ec3 4774 _cur = DirEntry(_stack[$-1].dirpath, fdata);
b4c522fa
IB
4775 return true;
4776 }
4777 }
5fee5ec3 4778
b4c522fa
IB
4779 popDirStack();
4780 return false;
4781 }
4782
5fee5ec3 4783 void popDirStack() @trusted
b4c522fa 4784 {
5fee5ec3
IB
4785 assert(_stack.length != 0);
4786 closedir(_stack[$-1].h);
4787 _stack.popBack();
b4c522fa
IB
4788 }
4789
5fee5ec3 4790 void releaseDirStack() @trusted
b4c522fa 4791 {
5fee5ec3 4792 foreach (d; _stack)
b4c522fa
IB
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;
b4c522fa 4807
5fee5ec3 4808 static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
b4c522fa
IB
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 }
5fee5ec3
IB
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
b4c522fa
IB
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
4882struct DirIterator
4883{
5fee5ec3 4884@safe:
b4c522fa
IB
4885private:
4886 RefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl;
5fee5ec3 4887 this(string pathname, SpanMode mode, bool followSymlink) @trusted
b4c522fa
IB
4888 {
4889 impl = typeof(impl)(pathname, mode, followSymlink);
4890 }
4891public:
5fee5ec3
IB
4892 @property bool empty() { return impl.empty; }
4893 @property DirEntry front() { return impl.front; }
4894 void popFront() { impl.popFront(); }
b4c522fa
IB
4895}
4896/++
5fee5ec3
IB
4897 Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
4898 of `DirEntry` that lazily iterates a given directory,
b4c522fa 4899 also provides two ways of foreach iteration. The iteration variable can be of
5fee5ec3 4900 type `string` if only the name is needed, or `DirEntry`
b4c522fa
IB
4901 if additional details are needed. The span _mode dictates how the
4902 directory is traversed. The name of each iterated directory entry
5fee5ec3
IB
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.
b4c522fa
IB
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
5fee5ec3 4919 iterated in depth-first post-order ($(LREF depth)),
b4c522fa
IB
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
5fee5ec3
IB
4927 Returns:
4928 An $(REF_ALTTEXT input range, isInputRange,std,range,primitives) of
4929 $(LREF DirEntry).
4930
b4c522fa 4931 Throws:
5fee5ec3 4932 $(LREF FileException) if the directory does not exist.
b4c522fa
IB
4933
4934Example:
4935--------------------
4936// Iterate a directory in depth
4937foreach (string name; dirEntries("destroy/me", SpanMode.depth))
4938{
4939 remove(name);
4940}
4941
4942// Iterate the current directory in breadth
4943foreach (string name; dirEntries("", SpanMode.breadth))
4944{
4945 writeln(name);
4946}
4947
4948// Iterate a directory and get detailed info about it
4949foreach (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
4955auto dFiles = dirEntries("", SpanMode.depth).filter!(f => f.name.endsWith(".d"));
4956foreach (d; dFiles)
4957 writeln(d.name);
4958
4959// Hook it up with std.parallelism to compile them all in parallel:
4960foreach (d; parallel(dFiles, 1)) //passes by 1 file to each thread
4961{
4962 string cmd = "dmd -c " ~ d.name;
4963 writeln(cmd);
5fee5ec3 4964 std.process.executeShell(cmd);
b4c522fa
IB
4965}
4966
4967// Iterate over all D source files in current directory and all its
4968// subdirectories
4969auto dFiles = dirEntries("","*.{d,di}",SpanMode.depth);
4970foreach (d; dFiles)
4971 writeln(d.name);
4972--------------------
4973 +/
4974auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
4975{
4976 return DirIterator(path, mode, followSymlink);
4977}
4978
5fee5ec3 4979/// Duplicate functionality of D1's `std.file.listdir()`:
b4c522fa
IB
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)
5fee5ec3 4991 .map!((return a) => std.path.baseName(a.name))
b4c522fa
IB
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;
5a0aa603
IB
5011 import std.path : buildPath, absolutePath;
5012 import std.file : dirEntries;
b4c522fa
IB
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
5fee5ec3 5021 string testdir = tempDir.buildPath("deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID));
b4c522fa
IB
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(
5fee5ec3 5034 map!((return a) => absolutePath(a.name))(dirEntries(relpath, mode)),
b4c522fa
IB
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
5fee5ec3 5055 // https://issues.dlang.org/show_bug.cgi?id=7264
b4c522fa
IB
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 }
5fee5ec3 5064 // https://issues.dlang.org/show_bug.cgi?id=7138
b4c522fa
IB
5065 auto a = array(dirEntries(testdir, SpanMode.shallow));
5066
5fee5ec3 5067 // https://issues.dlang.org/show_bug.cgi?id=11392
b4c522fa
IB
5068 auto dFiles = dirEntries(testdir, SpanMode.shallow);
5069 foreach (d; dFiles){}
5070
5fee5ec3 5071 // https://issues.dlang.org/show_bug.cgi?id=15146
b4c522fa
IB
5072 dirEntries("", SpanMode.shallow).walkLength();
5073}
5074
5075/// Ditto
5076auto 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
5fee5ec3
IB
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}
b4c522fa
IB
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 */
5219Select!(Types.length == 1, Types[0][], Tuple!(Types)[])
5fee5ec3 5220slurp(Types...)(string filename, scope const(char)[] format)
b4c522fa
IB
5221{
5222 import std.array : appender;
5223 import std.conv : text;
5224 import std.exception : enforce;
5fee5ec3 5225 import std.format.read : formattedRead;
b4c522fa 5226 import std.stdio : File;
5fee5ec3 5227 import std.string : stripRight;
b4c522fa
IB
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);
5fee5ec3 5236 enforce(line.stripRight("\r").empty,
b4c522fa
IB
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
5fee5ec3
IB
5265@system unittest
5266{
5267 import std.typecons : tuple;
b4c522fa 5268
5fee5ec3
IB
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}
b4c522fa 5277
b4c522fa 5278
5fee5ec3
IB
5279/**
5280Returns the path to a directory for temporary files.
b4c522fa
IB
5281
5282The return value of the function is cached, so the procedures described
5fee5ec3 5283below will only be performed the first time the function is called. All
b4c522fa
IB
5284subsequent runs will return the same string, regardless of whether
5285environment variables and directory structures have changed in the
5286meantime.
5287
5fee5ec3
IB
5288The POSIX `tempDir` algorithm is inspired by Python's
5289$(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, `tempfile.tempdir`).
5290
5291Returns:
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.
b4c522fa
IB
5308*/
5309string 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 }
b4c522fa
IB
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}
5fee5ec3
IB
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/**
5367Returns the available disk space based on a given path.
5368On Windows, `path` must be a directory; on POSIX systems, it can be a file or directory.
5369
5370Params:
5371 path = on Windows, it must be a directory; on POSIX it can be a file or directory
5372Returns:
5373 Available space in bytes
5374
5375Throws:
5376 $(LREF FileException) in case of failure
5377*/
5378ulong 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}