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