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