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