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