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