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