1 // Written in the D programming language. 2 3 /** 4 $(SCRIPT inhibitQuickIndex = 1;) 5 $(DIVC quickindex, 6 $(BOOKTABLE, 7 $(TR $(TH Category) $(TH Symbols)) 8 $(TR $(TD File handles) $(TD 9 $(MYREF __popen) 10 $(MYREF File) 11 $(MYREF isFileHandle) 12 $(MYREF openNetwork) 13 $(MYREF stderr) 14 $(MYREF stdin) 15 $(MYREF stdout) 16 )) 17 $(TR $(TD Reading) $(TD 18 $(MYREF chunks) 19 $(MYREF lines) 20 $(MYREF readf) 21 $(MYREF readfln) 22 $(MYREF readln) 23 )) 24 $(TR $(TD Writing) $(TD 25 $(MYREF toFile) 26 $(MYREF write) 27 $(MYREF writef) 28 $(MYREF writefln) 29 $(MYREF writeln) 30 )) 31 $(TR $(TD Misc) $(TD 32 $(MYREF KeepTerminator) 33 $(MYREF LockType) 34 $(MYREF StdioException) 35 )) 36 )) 37 38 Standard I/O functions that extend $(LINK2 https://dlang.org/phobos/core_stdc_stdio.html, core.stdc.stdio). $(B core.stdc.stdio) 39 is $(D_PARAM public)ally imported when importing $(B std.stdio). 40 41 There are three layers of I/O: 42 $(OL 43 $(LI The lowest layer is the operating system layer. The two main schemes are Windows and Posix.) 44 $(LI C's $(TT stdio.h) which unifies the two operating system schemes.) 45 $(LI $(TT std.stdio), this module, unifies the various $(TT stdio.h) implementations into 46 a high level package for D programs.) 47 ) 48 49 Source: $(PHOBOSSRC std/stdio.d) 50 Copyright: Copyright The D Language Foundation 2007-. 51 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 52 Authors: $(HTTP digitalmars.com, Walter Bright), 53 $(HTTP erdani.org, Andrei Alexandrescu), 54 Alex Rønne Petersen 55 Macros: 56 CSTDIO=$(HTTP cplusplus.com/reference/cstdio/$1/, $1) 57 */ 58 module std.stdio; 59 60 /* 61 # Glossary 62 63 The three layers have many terms for their data structures and types. 64 Here we try to bring some sanity to them for the intrepid code spelunker. 65 66 ## Windows 67 68 Handle 69 70 A Windows handle is an opaque object of type HANDLE. 71 The `HANDLE` for standard devices can be retrieved with 72 Windows `GetStdHandle()`. 73 74 ## Posix 75 76 file descriptor, aka fileno, aka fildes 77 78 An int from 0..`FOPEN_MAX`, which is an index into some internal data 79 structure. 80 0 is for `stdin`, 1 for `stdout`, 2 for `stderr`. 81 Negative values usually indicate an error. 82 83 ## stdio.h 84 85 `FILE` 86 87 A struct that encapsulates the C library's view of the operating system 88 files. A `FILE` should only be referred to via a pointer. 89 90 `fileno` 91 92 A field of `FILE` which is the Posix file descriptor for Posix systems, and 93 and an index into an array of file `HANDLE`s for Windows. 94 This array is how Posix behavior is emulated on Windows. 95 For Digital Mars C, that array is `__osfhnd[]`, and is initialized 96 at program start by the C runtime library. 97 In this module, they are typed as `fileno_t`. 98 99 `stdin`, `stdout`, `stderr` 100 101 Global pointers to `FILE` representing standard input, output, and error streams. 102 Being global means there are synchronization issues when multiple threads 103 are doing I/O on the same streams. 104 105 ## std.stdio 106 107 */ 108 109 import core.stdc.stddef : wchar_t; 110 public import core.stdc.stdio; 111 import std.algorithm.mutation : copy; 112 import std.meta : allSatisfy; 113 import std.range : ElementEncodingType, empty, front, isBidirectionalRange, 114 isInputRange, isSomeFiniteCharInputRange, put; 115 import std.traits : isSomeChar, isSomeString, Unqual; 116 import std.typecons : Flag, No, Yes; 117 118 /++ 119 If flag `KeepTerminator` is set to `KeepTerminator.yes`, then the delimiter 120 is included in the strings returned. 121 +/ 122 alias KeepTerminator = Flag!"keepTerminator"; 123 124 version (CRuntime_Microsoft) 125 { 126 } 127 else version (CRuntime_Glibc) 128 { 129 } 130 else version (CRuntime_Bionic) 131 { 132 version = GENERIC_IO; 133 } 134 else version (CRuntime_Musl) 135 { 136 version = GENERIC_IO; 137 } 138 else version (CRuntime_UClibc) 139 { 140 version = GENERIC_IO; 141 } 142 else version (OSX) 143 { 144 version = GENERIC_IO; 145 version = Darwin; 146 } 147 else version (iOS) 148 { 149 version = GENERIC_IO; 150 version = Darwin; 151 } 152 else version (TVOS) 153 { 154 version = GENERIC_IO; 155 version = Darwin; 156 } 157 else version (WatchOS) 158 { 159 version = GENERIC_IO; 160 version = Darwin; 161 } 162 else version (FreeBSD) 163 { 164 version = GENERIC_IO; 165 } 166 else version (NetBSD) 167 { 168 version = GENERIC_IO; 169 } 170 else version (OpenBSD) 171 { 172 version = GENERIC_IO; 173 } 174 else version (DragonFlyBSD) 175 { 176 version = GENERIC_IO; 177 } 178 else version (Solaris) 179 { 180 version = GENERIC_IO; 181 } 182 else 183 { 184 static assert(0, "unsupported operating system"); 185 } 186 187 // Character type used for operating system filesystem APIs 188 version (Windows) 189 { 190 private alias FSChar = wchar; 191 } 192 else 193 { 194 private alias FSChar = char; 195 } 196 197 private alias fileno_t = int; // file descriptor, fildes, fileno 198 199 version (Windows) 200 { 201 // core.stdc.stdio.fopen expects file names to be 202 // encoded in CP_ACP on Windows instead of UTF-8. 203 /+ Waiting for druntime pull 299 204 +/ 205 extern (C) nothrow @nogc FILE* _wfopen(scope const wchar* filename, scope const wchar* mode); 206 extern (C) nothrow @nogc FILE* _wfreopen(scope const wchar* filename, scope const wchar* mode, FILE* fp); 207 208 import core.sys.windows.basetsd : HANDLE; 209 } 210 211 version (Posix) 212 { 213 static import core.sys.posix.stdio; // getdelim, flockfile 214 } 215 216 version (CRuntime_Microsoft) 217 { 218 private alias _FPUTC = _fputc_nolock; 219 private alias _FPUTWC = _fputwc_nolock; 220 private alias _FGETC = _fgetc_nolock; 221 private alias _FGETWC = _fgetwc_nolock; 222 private alias _FLOCK = _lock_file; 223 private alias _FUNLOCK = _unlock_file; 224 } 225 else version (CRuntime_Glibc) 226 { 227 private alias _FPUTC = fputc_unlocked; 228 private alias _FPUTWC = fputwc_unlocked; 229 private alias _FGETC = fgetc_unlocked; 230 private alias _FGETWC = fgetwc_unlocked; 231 private alias _FLOCK = core.sys.posix.stdio.flockfile; 232 private alias _FUNLOCK = core.sys.posix.stdio.funlockfile; 233 } 234 else version (GENERIC_IO) 235 { 236 nothrow: 237 @nogc: 238 239 extern (C) private 240 { 241 static import core.stdc.wchar_; 242 243 pragma(mangle, fputc.mangleof) int _FPUTC(int c, _iobuf* fp); 244 pragma(mangle, core.stdc.wchar_.fputwc.mangleof) int _FPUTWC(wchar_t c, _iobuf* fp); 245 pragma(mangle, fgetc.mangleof) int _FGETC(_iobuf* fp); 246 pragma(mangle, core.stdc.wchar_.fgetwc.mangleof) int _FGETWC(_iobuf* fp); 247 } 248 249 version (Posix) 250 { 251 private alias _FLOCK = core.sys.posix.stdio.flockfile; 252 private alias _FUNLOCK = core.sys.posix.stdio.funlockfile; 253 } 254 else 255 { 256 static assert(0, "don't know how to lock files on GENERIC_IO"); 257 } 258 } 259 else 260 { 261 static assert(0, "unsupported C I/O system"); 262 } 263 264 private extern (C) @nogc nothrow 265 { 266 pragma(mangle, _FPUTC.mangleof) int trustedFPUTC(int ch, _iobuf* h) @trusted; 267 pragma(mangle, _FPUTWC.mangleof) int trustedFPUTWC(wchar_t ch, _iobuf* h) @trusted; 268 } 269 270 //------------------------------------------------------------------------------ 271 private struct ByRecordImpl(Fields...) 272 { 273 private: 274 import std.typecons : Tuple; 275 276 File file; 277 char[] line; 278 Tuple!(Fields) current; 279 string format; 280 281 public: 282 this(File f, string format) 283 { 284 assert(f.isOpen); 285 file = f; 286 this.format = format; 287 popFront(); // prime the range 288 } 289 290 /// Range primitive implementations. 291 @property bool empty() 292 { 293 return !file.isOpen; 294 } 295 296 /// Ditto 297 @property ref Tuple!(Fields) front() 298 { 299 return current; 300 } 301 302 /// Ditto 303 void popFront() 304 { 305 import std.conv : text; 306 import std.exception : enforce; 307 import std.format.read : formattedRead; 308 import std.string : chomp; 309 310 enforce(file.isOpen, "ByRecord: File must be open"); 311 file.readln(line); 312 if (!line.length) 313 { 314 file.detach(); 315 } 316 else 317 { 318 line = chomp(line); 319 formattedRead(line, format, ¤t); 320 enforce(line.empty, text("Leftover characters in record: `", 321 line, "'")); 322 } 323 } 324 } 325 326 template byRecord(Fields...) 327 { 328 auto byRecord(File f, string format) 329 { 330 return typeof(return)(f, format); 331 } 332 } 333 334 /** 335 Encapsulates a `FILE*`. Generally D does not attempt to provide 336 thin wrappers over equivalent functions in the C standard library, but 337 manipulating `FILE*` values directly is unsafe and error-prone in 338 many ways. The `File` type ensures safe manipulation, automatic 339 file closing, and a lot of convenience. 340 341 The underlying `FILE*` handle is maintained in a reference-counted 342 manner, such that as soon as the last `File` variable bound to a 343 given `FILE*` goes out of scope, the underlying `FILE*` is 344 automatically closed. 345 346 Example: 347 ---- 348 // test.d 349 import std.stdio; 350 351 void main(string[] args) 352 { 353 auto f = File("test.txt", "w"); // open for writing 354 f.write("Hello"); 355 if (args.length > 1) 356 { 357 auto g = f; // now g and f write to the same file 358 // internal reference count is 2 359 g.write(", ", args[1]); 360 // g exits scope, reference count decreases to 1 361 } 362 f.writeln("!"); 363 // f exits scope, reference count falls to zero, 364 // underlying `FILE*` is closed. 365 } 366 ---- 367 $(CONSOLE 368 % rdmd test.d Jimmy 369 % cat test.txt 370 Hello, Jimmy! 371 % __ 372 ) 373 */ 374 struct File 375 { 376 import core.atomic : atomicOp, atomicStore, atomicLoad; 377 import std.range.primitives : ElementEncodingType; 378 import std.traits : isScalarType, isArray; 379 enum Orientation { unknown, narrow, wide } 380 381 private struct Impl 382 { 383 FILE * handle = null; // Is null iff this Impl is closed by another File 384 shared uint refs = uint.max / 2; 385 bool isPopened; // true iff the stream has been created by popen() 386 Orientation orientation; 387 } 388 private Impl* _p; 389 private string _name; 390 391 package this(FILE* handle, string name, uint refs = 1, bool isPopened = false) @trusted @nogc nothrow 392 { 393 import core.stdc.stdlib : malloc; 394 395 assert(!_p); 396 _p = cast(Impl*) malloc(Impl.sizeof); 397 if (!_p) 398 { 399 import core.exception : onOutOfMemoryError; 400 onOutOfMemoryError(); 401 } 402 initImpl(handle, name, refs, isPopened); 403 } 404 405 private void initImpl(FILE* handle, string name, uint refs = 1, bool isPopened = false) @nogc nothrow pure @safe 406 { 407 assert(_p); 408 _p.handle = handle; 409 atomicStore(_p.refs, refs); 410 _p.isPopened = isPopened; 411 _p.orientation = Orientation.unknown; 412 _name = name; 413 } 414 415 /** 416 Constructor taking the name of the file to open and the open mode. 417 418 Copying one `File` object to another results in the two `File` 419 objects referring to the same underlying file. 420 421 The destructor automatically closes the file as soon as no `File` 422 object refers to it anymore. 423 424 Params: 425 name = range or string representing the file _name 426 stdioOpenmode = range or string represting the open mode 427 (with the same semantics as in the C standard library 428 $(CSTDIO fopen) function) 429 430 Throws: `ErrnoException` if the file could not be opened. 431 */ 432 this(string name, scope const(char)[] stdioOpenmode = "rb") @safe 433 { 434 import std.conv : text; 435 import std.exception : errnoEnforce; 436 437 this(errnoEnforce(_fopen(name, stdioOpenmode), 438 text("Cannot open file `", name, "' in mode `", 439 stdioOpenmode, "'")), 440 name); 441 442 // MSVCRT workaround (https://issues.dlang.org/show_bug.cgi?id=14422) 443 version (CRuntime_Microsoft) 444 { 445 setAppendWin(stdioOpenmode); 446 } 447 } 448 449 /// ditto 450 this(R1, R2)(R1 name) 451 if (isSomeFiniteCharInputRange!R1) 452 { 453 import std.conv : to; 454 this(name.to!string, "rb"); 455 } 456 457 /// ditto 458 this(R1, R2)(R1 name, R2 mode) 459 if (isSomeFiniteCharInputRange!R1 && 460 isSomeFiniteCharInputRange!R2) 461 { 462 import std.conv : to; 463 this(name.to!string, mode.to!string); 464 } 465 466 @safe unittest 467 { 468 static import std.file; 469 import std.utf : byChar; 470 auto deleteme = testFilename(); 471 auto f = File(deleteme.byChar, "w".byChar); 472 f.close(); 473 std.file.remove(deleteme); 474 } 475 476 ~this() @safe 477 { 478 detach(); 479 } 480 481 this(this) @safe pure nothrow @nogc 482 { 483 if (!_p) return; 484 assert(atomicLoad(_p.refs)); 485 atomicOp!"+="(_p.refs, 1); 486 } 487 488 /** 489 Assigns a file to another. The target of the assignment gets detached 490 from whatever file it was attached to, and attaches itself to the new 491 file. 492 */ 493 ref File opAssign(File rhs) @safe return 494 { 495 import std.algorithm.mutation : swap; 496 497 swap(this, rhs); 498 return this; 499 } 500 501 // https://issues.dlang.org/show_bug.cgi?id=20129 502 @safe unittest 503 { 504 File[int] aa; 505 aa.require(0, File.init); 506 } 507 508 /** 509 Detaches from the current file (throwing on failure), and then attempts to 510 _open file `name` with mode `stdioOpenmode`. The mode has the 511 same semantics as in the C standard library $(CSTDIO fopen) function. 512 513 Throws: `ErrnoException` in case of error. 514 */ 515 void open(string name, scope const(char)[] stdioOpenmode = "rb") @trusted 516 { 517 resetFile(name, stdioOpenmode, false); 518 } 519 520 // https://issues.dlang.org/show_bug.cgi?id=20585 521 @system unittest 522 { 523 File f; 524 try 525 f.open("doesn't exist"); 526 catch (Exception _e) 527 { 528 } 529 530 assert(!f.isOpen); 531 532 f.close(); // to check not crash here 533 } 534 535 private void resetFile(string name, scope const(char)[] stdioOpenmode, bool isPopened) @trusted 536 { 537 import core.stdc.stdlib : malloc; 538 import std.exception : enforce; 539 import std.conv : text; 540 import std.exception : errnoEnforce; 541 542 if (_p !is null) 543 { 544 detach(); 545 } 546 547 FILE* handle; 548 version (Posix) 549 { 550 if (isPopened) 551 { 552 errnoEnforce(handle = _popen(name, stdioOpenmode), 553 "Cannot run command `"~name~"'"); 554 } 555 else 556 { 557 errnoEnforce(handle = _fopen(name, stdioOpenmode), 558 text("Cannot open file `", name, "' in mode `", 559 stdioOpenmode, "'")); 560 } 561 } 562 else 563 { 564 assert(isPopened == false); 565 errnoEnforce(handle = _fopen(name, stdioOpenmode), 566 text("Cannot open file `", name, "' in mode `", 567 stdioOpenmode, "'")); 568 } 569 _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory"); 570 initImpl(handle, name, 1, isPopened); 571 version (CRuntime_Microsoft) 572 { 573 setAppendWin(stdioOpenmode); 574 } 575 } 576 577 private void closeHandles() @trusted 578 { 579 assert(_p); 580 import std.exception : errnoEnforce; 581 582 version (Posix) 583 { 584 import core.sys.posix.stdio : pclose; 585 import std.format : format; 586 587 if (_p.isPopened) 588 { 589 auto res = pclose(_p.handle); 590 errnoEnforce(res != -1, 591 "Could not close pipe `"~_name~"'"); 592 _p.handle = null; 593 return; 594 } 595 } 596 if (_p.handle) 597 { 598 auto handle = _p.handle; 599 _p.handle = null; 600 // fclose disassociates the FILE* even in case of error (https://issues.dlang.org/show_bug.cgi?id=19751) 601 errnoEnforce(.fclose(handle) == 0, 602 "Could not close file `"~_name~"'"); 603 } 604 } 605 606 version (CRuntime_Microsoft) 607 { 608 private void setAppendWin(scope const(char)[] stdioOpenmode) @safe 609 { 610 bool append, update; 611 foreach (c; stdioOpenmode) 612 if (c == 'a') 613 append = true; 614 else 615 if (c == '+') 616 update = true; 617 if (append && !update) 618 seek(size); 619 } 620 } 621 622 /** 623 Reuses the `File` object to either open a different file, or change 624 the file mode. If `name` is `null`, the mode of the currently open 625 file is changed; otherwise, a new file is opened, reusing the C 626 `FILE*`. The function has the same semantics as in the C standard 627 library $(CSTDIO freopen) function. 628 629 Note: Calling `reopen` with a `null` `name` is not implemented 630 in all C runtimes. 631 632 Throws: `ErrnoException` in case of error. 633 */ 634 void reopen(string name, scope const(char)[] stdioOpenmode = "rb") @trusted 635 { 636 import std.conv : text; 637 import std.exception : enforce, errnoEnforce; 638 import std.internal.cstring : tempCString; 639 640 enforce(isOpen, "Attempting to reopen() an unopened file"); 641 642 auto namez = (name == null ? _name : name).tempCString!FSChar(); 643 auto modez = stdioOpenmode.tempCString!FSChar(); 644 645 FILE* fd = _p.handle; 646 version (Windows) 647 fd = _wfreopen(namez, modez, fd); 648 else 649 fd = freopen(namez, modez, fd); 650 651 errnoEnforce(fd, name 652 ? text("Cannot reopen file `", name, "' in mode `", stdioOpenmode, "'") 653 : text("Cannot reopen file in mode `", stdioOpenmode, "'")); 654 655 if (name !is null) 656 _name = name; 657 } 658 659 @safe unittest // Test changing filename 660 { 661 import std.exception : assertThrown, assertNotThrown; 662 static import std.file; 663 664 auto deleteme = testFilename(); 665 std.file.write(deleteme, "foo"); 666 scope(exit) std.file.remove(deleteme); 667 auto f = File(deleteme); 668 assert(f.readln() == "foo"); 669 670 auto deleteme2 = testFilename(); 671 std.file.write(deleteme2, "bar"); 672 scope(exit) std.file.remove(deleteme2); 673 f.reopen(deleteme2); 674 assert(f.name == deleteme2); 675 assert(f.readln() == "bar"); 676 f.close(); 677 } 678 679 version (CRuntime_Microsoft) {} else // Not implemented 680 @safe unittest // Test changing mode 681 { 682 import std.exception : assertThrown, assertNotThrown; 683 static import std.file; 684 685 auto deleteme = testFilename(); 686 std.file.write(deleteme, "foo"); 687 scope(exit) std.file.remove(deleteme); 688 auto f = File(deleteme, "r+"); 689 assert(f.readln() == "foo"); 690 f.reopen(null, "w"); 691 f.write("bar"); 692 f.seek(0); 693 f.reopen(null, "a"); 694 f.write("baz"); 695 assert(f.name == deleteme); 696 f.close(); 697 assert(std.file.readText(deleteme) == "barbaz"); 698 } 699 700 /** 701 Detaches from the current file (throwing on failure), and then runs a command 702 by calling the C standard library function $(HTTP 703 pubs.opengroup.org/onlinepubs/7908799/xsh/popen.html, popen). 704 705 Throws: `ErrnoException` in case of error. 706 */ 707 version (Posix) void popen(string command, scope const(char)[] stdioOpenmode = "r") @safe 708 { 709 resetFile(command, stdioOpenmode ,true); 710 } 711 712 /** 713 First calls `detach` (throwing on failure), then attempts to 714 associate the given file descriptor with the `File`, and sets the file's name to `null`. 715 716 The mode must be compatible with the mode of the file descriptor. 717 718 Throws: `ErrnoException` in case of error. 719 Params: 720 fd = File descriptor to associate with this `File`. 721 stdioOpenmode = Mode to associate with this File. The mode has the same 722 semantics as in the POSIX library function $(HTTP 723 pubs.opengroup.org/onlinepubs/7908799/xsh/fdopen.html, fdopen) 724 and must be compatible with `fd`. 725 */ 726 void fdopen(int fd, scope const(char)[] stdioOpenmode = "rb") @safe 727 { 728 fdopen(fd, stdioOpenmode, null); 729 } 730 731 package void fdopen(int fd, scope const(char)[] stdioOpenmode, string name) @trusted 732 { 733 import std.exception : errnoEnforce; 734 import std.internal.cstring : tempCString; 735 736 auto modez = stdioOpenmode.tempCString(); 737 detach(); 738 739 version (CRuntime_Microsoft) 740 { 741 auto fp = _fdopen(fd, modez); 742 errnoEnforce(fp); 743 } 744 else version (Posix) 745 { 746 import core.sys.posix.stdio : fdopen; 747 auto fp = fdopen(fd, modez); 748 errnoEnforce(fp); 749 } 750 else 751 static assert(0, "no fdopen() available"); 752 753 this = File(fp, name); 754 } 755 756 // Declare a dummy HANDLE to allow generating documentation 757 // for Windows-only methods. 758 version (StdDdoc) { version (Windows) {} else alias HANDLE = int; } 759 760 /** 761 First calls `detach` (throwing on failure), and then attempts to 762 associate the given Windows `HANDLE` with the `File`. The mode must 763 be compatible with the access attributes of the handle. Windows only. 764 765 Throws: `ErrnoException` in case of error. 766 */ 767 version (StdDdoc) 768 void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode); 769 770 version (Windows) 771 void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode) 772 { 773 import core.stdc.stdint : intptr_t; 774 import std.exception : errnoEnforce; 775 import std.format : format; 776 777 // Create file descriptors from the handles 778 int mode; 779 modeLoop: 780 foreach (c; stdioOpenmode) 781 switch (c) 782 { 783 case 'r': mode |= _O_RDONLY; break; 784 case '+': mode &=~_O_RDONLY; break; 785 case 'a': mode |= _O_APPEND; break; 786 case 'b': mode |= _O_BINARY; break; 787 case 't': mode |= _O_TEXT; break; 788 case ',': break modeLoop; 789 default: break; 790 } 791 792 auto fd = _open_osfhandle(cast(intptr_t) handle, mode); 793 794 errnoEnforce(fd >= 0, "Cannot open Windows HANDLE"); 795 fdopen(fd, stdioOpenmode, "HANDLE(%s)".format(handle)); 796 } 797 798 799 /** Returns `true` if the file is opened. */ 800 @property bool isOpen() const @safe pure nothrow 801 { 802 return _p !is null && _p.handle; 803 } 804 805 /** 806 Returns `true` if the file is at end (see $(CSTDIO feof)). 807 808 Throws: `Exception` if the file is not opened. 809 */ 810 @property bool eof() const @trusted pure 811 { 812 import std.exception : enforce; 813 814 enforce(_p && _p.handle, "Calling eof() against an unopened file."); 815 return .feof(cast(FILE*) _p.handle) != 0; 816 } 817 818 /** 819 Returns the name last used to initialize this `File`, if any. 820 821 Some functions that create or initialize the `File` set the name field to `null`. 822 Examples include $(LREF tmpfile), $(LREF wrapFile), and $(LREF fdopen). See the 823 documentation of those functions for details. 824 825 Returns: The name last used to initialize this this file, or `null` otherwise. 826 */ 827 @property string name() const @safe pure nothrow return 828 { 829 return _name; 830 } 831 832 /** 833 If the file is closed or not yet opened, returns `true`. Otherwise, returns 834 $(CSTDIO ferror) for the file handle. 835 */ 836 @property bool error() const @trusted pure nothrow 837 { 838 return !isOpen || .ferror(cast(FILE*) _p.handle); 839 } 840 841 @safe unittest 842 { 843 // https://issues.dlang.org/show_bug.cgi?id=12349 844 static import std.file; 845 auto deleteme = testFilename(); 846 auto f = File(deleteme, "w"); 847 scope(exit) std.file.remove(deleteme); 848 849 f.close(); 850 assert(f.error); 851 } 852 853 /** 854 Detaches from the underlying file. If the sole owner, calls `close`. 855 856 Throws: `ErrnoException` on failure if closing the file. 857 */ 858 void detach() @trusted 859 { 860 import core.stdc.stdlib : free; 861 862 if (!_p) return; 863 scope(exit) _p = null; 864 865 if (atomicOp!"-="(_p.refs, 1) == 0) 866 { 867 scope(exit) free(_p); 868 closeHandles(); 869 } 870 } 871 872 @safe unittest 873 { 874 static import std.file; 875 876 auto deleteme = testFilename(); 877 scope(exit) std.file.remove(deleteme); 878 auto f = File(deleteme, "w"); 879 { 880 auto f2 = f; 881 f2.detach(); 882 } 883 assert(f._p.refs == 1); 884 f.close(); 885 } 886 887 /** 888 If the file was closed or not yet opened, succeeds vacuously. Otherwise 889 closes the file (by calling $(CSTDIO fclose)), 890 throwing on error. Even if an exception is thrown, afterwards the $(D 891 File) object is empty. This is different from `detach` in that it 892 always closes the file; consequently, all other `File` objects 893 referring to the same handle will see a closed file henceforth. 894 895 Throws: `ErrnoException` on error. 896 */ 897 void close() @trusted 898 { 899 import core.stdc.stdlib : free; 900 import std.exception : errnoEnforce; 901 902 if (!_p) return; // succeed vacuously 903 scope(exit) 904 { 905 if (atomicOp!"-="(_p.refs, 1) == 0) 906 free(_p); 907 _p = null; // start a new life 908 } 909 if (!_p.handle) return; // Impl is closed by another File 910 911 scope(exit) _p.handle = null; // nullify the handle anyway 912 closeHandles(); 913 } 914 915 /** 916 If the file is closed or not yet opened, succeeds vacuously. Otherwise, returns 917 $(CSTDIO clearerr) for the file handle. 918 */ 919 void clearerr() @safe pure nothrow 920 { 921 _p is null || _p.handle is null || 922 .clearerr(_p.handle); 923 } 924 925 /** 926 Flushes the C `FILE` buffers. 927 928 Calls $(CSTDIO fflush) for the file handle. 929 930 Throws: `Exception` if the file is not opened or if the call to `fflush` fails. 931 */ 932 void flush() @trusted 933 { 934 import std.exception : enforce, errnoEnforce; 935 936 enforce(isOpen, "Attempting to flush() in an unopened file"); 937 errnoEnforce(.fflush(_p.handle) == 0); 938 } 939 940 @safe unittest 941 { 942 // https://issues.dlang.org/show_bug.cgi?id=12349 943 import std.exception : assertThrown; 944 static import std.file; 945 946 auto deleteme = testFilename(); 947 auto f = File(deleteme, "w"); 948 scope(exit) std.file.remove(deleteme); 949 950 f.close(); 951 assertThrown(f.flush()); 952 } 953 954 /** 955 Forces any data buffered by the OS to be written to disk. 956 Call $(LREF flush) before calling this function to flush the C `FILE` buffers first. 957 958 This function calls 959 $(HTTP msdn.microsoft.com/en-us/library/windows/desktop/aa364439%28v=vs.85%29.aspx, 960 `FlushFileBuffers`) on Windows, 961 $(HTTP developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html, 962 `F_FULLFSYNC fcntl`) on Darwin and 963 $(HTTP pubs.opengroup.org/onlinepubs/7908799/xsh/fsync.html, 964 `fsync`) on POSIX for the file handle. 965 966 Throws: `Exception` if the file is not opened or if the OS call fails. 967 */ 968 void sync() @trusted 969 { 970 import std.exception : enforce; 971 972 enforce(isOpen, "Attempting to sync() an unopened file"); 973 974 version (Windows) 975 { 976 import core.sys.windows.winbase : FlushFileBuffers; 977 wenforce(FlushFileBuffers(windowsHandle), "FlushFileBuffers failed"); 978 } 979 else version (Darwin) 980 { 981 import core.sys.darwin.fcntl : fcntl, F_FULLFSYNC; 982 import std.exception : errnoEnforce; 983 errnoEnforce(fcntl(fileno, F_FULLFSYNC, 0) != -1, "fcntl failed"); 984 } 985 else 986 { 987 import core.sys.posix.unistd : fsync; 988 import std.exception : errnoEnforce; 989 errnoEnforce(fsync(fileno) == 0, "fsync failed"); 990 } 991 } 992 993 /** 994 Calls $(CSTDIO fread) for the 995 file handle. The number of items to read and the size of 996 each item is inferred from the size and type of the input array, respectively. 997 998 Returns: The slice of `buffer` containing the data that was actually read. 999 This will be shorter than `buffer` if EOF was reached before the buffer 1000 could be filled. If the buffer is empty, it will be returned. 1001 1002 Throws: `ErrnoException` if the file is not opened or the call to `fread` fails. 1003 1004 `rawRead` always reads in binary mode on Windows. 1005 */ 1006 T[] rawRead(T)(T[] buffer) 1007 { 1008 import std.exception : enforce, errnoEnforce; 1009 1010 if (!buffer.length) 1011 return buffer; 1012 enforce(isOpen, "Attempting to read from an unopened file"); 1013 version (Windows) 1014 { 1015 immutable fileno_t fd = .fileno(_p.handle); 1016 immutable mode = ._setmode(fd, _O_BINARY); 1017 scope(exit) ._setmode(fd, mode); 1018 } 1019 immutable freadResult = trustedFread(_p.handle, buffer); 1020 assert(freadResult <= buffer.length); // fread return guarantee 1021 if (freadResult != buffer.length) // error or eof 1022 { 1023 errnoEnforce(!error); 1024 return buffer[0 .. freadResult]; 1025 } 1026 return buffer; 1027 } 1028 1029 /// 1030 @system unittest 1031 { 1032 static import std.file; 1033 1034 auto testFile = std.file.deleteme(); 1035 std.file.write(testFile, "\r\n\n\r\n"); 1036 scope(exit) std.file.remove(testFile); 1037 1038 auto f = File(testFile, "r"); 1039 auto buf = f.rawRead(new char[5]); 1040 f.close(); 1041 assert(buf == "\r\n\n\r\n"); 1042 } 1043 1044 // https://issues.dlang.org/show_bug.cgi?id=24685 1045 static assert(!__traits(compiles, (File f) @safe { int*[1] bar; f.rawRead(bar[]); })); 1046 1047 // https://issues.dlang.org/show_bug.cgi?id=21729 1048 @system unittest 1049 { 1050 import std.exception : assertThrown; 1051 1052 File f; 1053 ubyte[1] u; 1054 assertThrown(f.rawRead(u)); 1055 } 1056 1057 // https://issues.dlang.org/show_bug.cgi?id=21728 1058 @system unittest 1059 { 1060 static if (__traits(compiles, { import std.process : pipe; })) // not available for iOS 1061 { 1062 import std.process : pipe; 1063 import std.exception : assertThrown; 1064 1065 auto p = pipe(); 1066 p.readEnd.close; 1067 ubyte[1] u; 1068 assertThrown(p.readEnd.rawRead(u)); 1069 } 1070 } 1071 1072 // https://issues.dlang.org/show_bug.cgi?id=13893 1073 @system unittest 1074 { 1075 import std.exception : assertNotThrown; 1076 1077 File f; 1078 ubyte[0] u; 1079 assertNotThrown(f.rawRead(u)); 1080 } 1081 1082 /** 1083 Calls $(CSTDIO fwrite) for the file 1084 handle. The number of items to write and the size of each 1085 item is inferred from the size and type of the input array, respectively. An 1086 error is thrown if the buffer could not be written in its entirety. 1087 1088 `rawWrite` always writes in binary mode on Windows. 1089 1090 Throws: `ErrnoException` if the file is not opened or if the call to `fwrite` fails. 1091 */ 1092 void rawWrite(T)(in T[] buffer) 1093 { 1094 import std.conv : text; 1095 import std.exception : errnoEnforce; 1096 1097 version (Windows) 1098 { 1099 immutable fileno_t fd = .fileno(_p.handle); 1100 immutable oldMode = ._setmode(fd, _O_BINARY); 1101 1102 if (oldMode != _O_BINARY) 1103 { 1104 // need to flush the data that was written with the original mode 1105 ._setmode(fd, oldMode); 1106 flush(); // before changing translation mode ._setmode(fd, _O_BINARY); 1107 ._setmode(fd, _O_BINARY); 1108 } 1109 1110 scope (exit) 1111 { 1112 if (oldMode != _O_BINARY) 1113 { 1114 flush(); 1115 ._setmode(fd, oldMode); 1116 } 1117 } 1118 } 1119 1120 auto result = trustedFwrite(_p.handle, buffer); 1121 if (result == result.max) result = 0; 1122 errnoEnforce(result == buffer.length, 1123 text("Wrote ", result, " instead of ", buffer.length, 1124 " objects of type ", T.stringof, " to file `", 1125 _name, "'")); 1126 } 1127 1128 /// 1129 @system unittest 1130 { 1131 static import std.file; 1132 1133 auto testFile = std.file.deleteme(); 1134 auto f = File(testFile, "w"); 1135 scope(exit) std.file.remove(testFile); 1136 1137 f.rawWrite("\r\n\n\r\n"); 1138 f.close(); 1139 assert(std.file.read(testFile) == "\r\n\n\r\n"); 1140 } 1141 1142 /** 1143 Calls $(CSTDIO fseek) 1144 for the file handle to move its position indicator. 1145 1146 Params: 1147 offset = Binary files: Number of bytes to offset from origin.$(BR) 1148 Text files: Either zero, or a value returned by $(LREF tell). 1149 origin = Binary files: Position used as reference for the offset, must be 1150 one of $(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio), 1151 $(REF_ALTTEXT SEEK_CUR, SEEK_CUR, core,stdc,stdio) or 1152 $(REF_ALTTEXT SEEK_END, SEEK_END, core,stdc,stdio).$(BR) 1153 Text files: Shall necessarily be 1154 $(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio). 1155 1156 Throws: `Exception` if the file is not opened. 1157 `ErrnoException` if the call to `fseek` fails. 1158 */ 1159 void seek(long offset, int origin = SEEK_SET) @trusted 1160 { 1161 import std.conv : to, text; 1162 import std.exception : enforce, errnoEnforce; 1163 1164 // Some libc sanitize the whence input (e.g. glibc), but some don't, 1165 // e.g. Microsoft runtime crashes on an invalid origin, 1166 // and Musl additionally accept SEEK_DATA & SEEK_HOLE (Linux extension). 1167 // To provide a consistent behavior cross platform, we use the glibc check 1168 // See also https://issues.dlang.org/show_bug.cgi?id=19797 1169 enforce(origin == SEEK_SET || origin == SEEK_CUR || origin == SEEK_END, 1170 "Invalid `origin` argument passed to `seek`, must be one of: SEEK_SET, SEEK_CUR, SEEK_END"); 1171 1172 enforce(isOpen, "Attempting to seek() in an unopened file"); 1173 version (Windows) 1174 { 1175 version (CRuntime_Microsoft) 1176 { 1177 alias fseekFun = _fseeki64; 1178 alias off_t = long; 1179 } 1180 else 1181 { 1182 alias fseekFun = fseek; 1183 alias off_t = int; 1184 } 1185 } 1186 else version (Posix) 1187 { 1188 import core.sys.posix.stdio : fseeko, off_t; 1189 alias fseekFun = fseeko; 1190 } 1191 errnoEnforce(fseekFun(_p.handle, to!off_t(offset), origin) == 0, 1192 "Could not seek in file `"~_name~"'"); 1193 } 1194 1195 @system unittest 1196 { 1197 import std.conv : text; 1198 static import std.file; 1199 import std.exception; 1200 1201 auto deleteme = testFilename(); 1202 auto f = File(deleteme, "w+"); 1203 scope(exit) { f.close(); std.file.remove(deleteme); } 1204 f.rawWrite("abcdefghijklmnopqrstuvwxyz"); 1205 f.seek(7); 1206 assert(f.readln() == "hijklmnopqrstuvwxyz"); 1207 1208 version (CRuntime_Bionic) 1209 auto bigOffset = int.max - 100; 1210 else 1211 auto bigOffset = cast(ulong) int.max + 100; 1212 f.seek(bigOffset); 1213 assert(f.tell == bigOffset, text(f.tell)); 1214 // Uncomment the tests below only if you want to wait for 1215 // a long time 1216 // f.rawWrite("abcdefghijklmnopqrstuvwxyz"); 1217 // f.seek(-3, SEEK_END); 1218 // assert(f.readln() == "xyz"); 1219 1220 assertThrown(f.seek(0, ushort.max)); 1221 } 1222 1223 /** 1224 Calls $(CSTDIO ftell) 1225 for the managed file handle, which returns the current value of 1226 the position indicator of the file handle. 1227 1228 Throws: `Exception` if the file is not opened. 1229 `ErrnoException` if the call to `ftell` fails. 1230 */ 1231 @property ulong tell() const @trusted 1232 { 1233 import std.exception : enforce, errnoEnforce; 1234 1235 enforce(isOpen, "Attempting to tell() in an unopened file"); 1236 version (Windows) 1237 { 1238 version (CRuntime_Microsoft) 1239 immutable result = _ftelli64(cast(FILE*) _p.handle); 1240 else 1241 immutable result = ftell(cast(FILE*) _p.handle); 1242 } 1243 else version (Posix) 1244 { 1245 import core.sys.posix.stdio : ftello; 1246 immutable result = ftello(cast(FILE*) _p.handle); 1247 } 1248 errnoEnforce(result != -1, 1249 "Query ftell() failed for file `"~_name~"'"); 1250 return result; 1251 } 1252 1253 /// 1254 @system unittest 1255 { 1256 import std.conv : text; 1257 static import std.file; 1258 1259 auto testFile = std.file.deleteme(); 1260 std.file.write(testFile, "abcdefghijklmnopqrstuvwqxyz"); 1261 scope(exit) { std.file.remove(testFile); } 1262 1263 auto f = File(testFile); 1264 auto a = new ubyte[4]; 1265 f.rawRead(a); 1266 assert(f.tell == 4, text(f.tell)); 1267 } 1268 1269 /** 1270 Calls $(CSTDIO rewind) for the file handle. 1271 1272 Throws: `Exception` if the file is not opened. 1273 */ 1274 void rewind() @safe 1275 { 1276 import std.exception : enforce; 1277 1278 enforce(isOpen, "Attempting to rewind() an unopened file"); 1279 .rewind(_p.handle); 1280 } 1281 1282 /** 1283 Calls $(CSTDIO setvbuf) for the file handle. 1284 1285 Throws: `Exception` if the file is not opened. 1286 `ErrnoException` if the call to `setvbuf` fails. 1287 */ 1288 void setvbuf(size_t size, int mode = _IOFBF) @trusted 1289 { 1290 import std.exception : enforce, errnoEnforce; 1291 1292 enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); 1293 errnoEnforce(.setvbuf(_p.handle, null, mode, size) == 0, 1294 "Could not set buffering for file `"~_name~"'"); 1295 } 1296 1297 /** 1298 Calls $(CSTDIO setvbuf) for the file handle. 1299 1300 Throws: `Exception` if the file is not opened. 1301 `ErrnoException` if the call to `setvbuf` fails. 1302 */ 1303 void setvbuf(void[] buf, int mode = _IOFBF) @trusted 1304 { 1305 import std.exception : enforce, errnoEnforce; 1306 1307 enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); 1308 errnoEnforce(.setvbuf(_p.handle, 1309 cast(char*) buf.ptr, mode, buf.length) == 0, 1310 "Could not set buffering for file `"~_name~"'"); 1311 } 1312 1313 1314 version (Windows) 1315 { 1316 import core.sys.windows.winbase : OVERLAPPED; 1317 import core.sys.windows.winnt : BOOL, ULARGE_INTEGER; 1318 import std.windows.syserror : wenforce; 1319 1320 private BOOL lockImpl(alias F, Flags...)(ulong start, ulong length, 1321 Flags flags) 1322 { 1323 if (!start && !length) 1324 length = ulong.max; 1325 ULARGE_INTEGER liStart = void, liLength = void; 1326 liStart.QuadPart = start; 1327 liLength.QuadPart = length; 1328 OVERLAPPED overlapped; 1329 overlapped.Offset = liStart.LowPart; 1330 overlapped.OffsetHigh = liStart.HighPart; 1331 overlapped.hEvent = null; 1332 return F(windowsHandle, flags, 0, liLength.LowPart, 1333 liLength.HighPart, &overlapped); 1334 } 1335 } 1336 version (Posix) 1337 { 1338 private int lockImpl(int operation, short l_type, 1339 ulong start, ulong length) 1340 { 1341 import core.sys.posix.fcntl : fcntl, flock, off_t; 1342 import core.sys.posix.unistd : getpid; 1343 import std.conv : to; 1344 1345 flock fl = void; 1346 fl.l_type = l_type; 1347 fl.l_whence = SEEK_SET; 1348 fl.l_start = to!off_t(start); 1349 fl.l_len = to!off_t(length); 1350 fl.l_pid = getpid(); 1351 return fcntl(fileno, operation, &fl); 1352 } 1353 } 1354 1355 /** 1356 Locks the specified file segment. If the file segment is already locked 1357 by another process, waits until the existing lock is released. 1358 If both `start` and `length` are zero, the entire file is locked. 1359 1360 Locks created using `lock` and `tryLock` have the following properties: 1361 $(UL 1362 $(LI All locks are automatically released when the process terminates.) 1363 $(LI Locks are not inherited by child processes.) 1364 $(LI Closing a file will release all locks associated with the file. On POSIX, 1365 even locks acquired via a different `File` will be released as well.) 1366 $(LI Not all NFS implementations correctly implement file locking.) 1367 ) 1368 */ 1369 void lock(LockType lockType = LockType.readWrite, 1370 ulong start = 0, ulong length = 0) 1371 { 1372 import std.exception : enforce; 1373 1374 enforce(isOpen, "Attempting to call lock() on an unopened file"); 1375 version (Posix) 1376 { 1377 import core.sys.posix.fcntl : F_RDLCK, F_SETLKW, F_WRLCK; 1378 import std.exception : errnoEnforce; 1379 immutable short type = lockType == LockType.readWrite 1380 ? F_WRLCK : F_RDLCK; 1381 errnoEnforce(lockImpl(F_SETLKW, type, start, length) != -1, 1382 "Could not set lock for file `"~_name~"'"); 1383 } 1384 else 1385 version (Windows) 1386 { 1387 import core.sys.windows.winbase : LockFileEx, LOCKFILE_EXCLUSIVE_LOCK; 1388 immutable type = lockType == LockType.readWrite ? 1389 LOCKFILE_EXCLUSIVE_LOCK : 0; 1390 wenforce(lockImpl!LockFileEx(start, length, type), 1391 "Could not set lock for file `"~_name~"'"); 1392 } 1393 else 1394 static assert(false); 1395 } 1396 1397 /** 1398 Attempts to lock the specified file segment. 1399 If both `start` and `length` are zero, the entire file is locked. 1400 Returns: `true` if the lock was successful, and `false` if the 1401 specified file segment was already locked. 1402 */ 1403 bool tryLock(LockType lockType = LockType.readWrite, 1404 ulong start = 0, ulong length = 0) 1405 { 1406 import std.exception : enforce; 1407 1408 enforce(isOpen, "Attempting to call tryLock() on an unopened file"); 1409 version (Posix) 1410 { 1411 import core.stdc.errno : EACCES, EAGAIN, errno; 1412 import core.sys.posix.fcntl : F_RDLCK, F_SETLK, F_WRLCK; 1413 import std.exception : errnoEnforce; 1414 immutable short type = lockType == LockType.readWrite 1415 ? F_WRLCK : F_RDLCK; 1416 immutable res = lockImpl(F_SETLK, type, start, length); 1417 if (res == -1 && (errno == EACCES || errno == EAGAIN)) 1418 return false; 1419 errnoEnforce(res != -1, "Could not set lock for file `"~_name~"'"); 1420 return true; 1421 } 1422 else 1423 version (Windows) 1424 { 1425 import core.sys.windows.winbase : GetLastError, LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, 1426 LOCKFILE_FAIL_IMMEDIATELY; 1427 import core.sys.windows.winerror : ERROR_IO_PENDING, ERROR_LOCK_VIOLATION; 1428 immutable type = lockType == LockType.readWrite 1429 ? LOCKFILE_EXCLUSIVE_LOCK : 0; 1430 immutable res = lockImpl!LockFileEx(start, length, 1431 type | LOCKFILE_FAIL_IMMEDIATELY); 1432 if (!res && (GetLastError() == ERROR_IO_PENDING 1433 || GetLastError() == ERROR_LOCK_VIOLATION)) 1434 return false; 1435 wenforce(res, "Could not set lock for file `"~_name~"'"); 1436 return true; 1437 } 1438 else 1439 static assert(false); 1440 } 1441 1442 /** 1443 Removes the lock over the specified file segment. 1444 */ 1445 void unlock(ulong start = 0, ulong length = 0) 1446 { 1447 import std.exception : enforce; 1448 1449 enforce(isOpen, "Attempting to call unlock() on an unopened file"); 1450 version (Posix) 1451 { 1452 import core.sys.posix.fcntl : F_SETLK, F_UNLCK; 1453 import std.exception : errnoEnforce; 1454 errnoEnforce(lockImpl(F_SETLK, F_UNLCK, start, length) != -1, 1455 "Could not remove lock for file `"~_name~"'"); 1456 } 1457 else 1458 version (Windows) 1459 { 1460 import core.sys.windows.winbase : UnlockFileEx; 1461 wenforce(lockImpl!UnlockFileEx(start, length), 1462 "Could not remove lock for file `"~_name~"'"); 1463 } 1464 else 1465 static assert(false); 1466 } 1467 1468 version (Windows) 1469 @system unittest 1470 { 1471 static import std.file; 1472 auto deleteme = testFilename(); 1473 scope(exit) std.file.remove(deleteme); 1474 auto f = File(deleteme, "wb"); 1475 assert(f.tryLock()); 1476 auto g = File(deleteme, "wb"); 1477 assert(!g.tryLock()); 1478 assert(!g.tryLock(LockType.read)); 1479 f.unlock(); 1480 f.lock(LockType.read); 1481 assert(!g.tryLock()); 1482 assert(g.tryLock(LockType.read)); 1483 f.unlock(); 1484 g.unlock(); 1485 } 1486 1487 version (Posix) 1488 @system unittest 1489 { 1490 static if (__traits(compiles, { import std.process : spawnProcess; })) 1491 { 1492 static import std.file; 1493 auto deleteme = testFilename(); 1494 scope(exit) std.file.remove(deleteme); 1495 1496 // Since locks are per-process, we cannot test lock failures within 1497 // the same process. fork() is used to create a second process. 1498 static void runForked(void delegate() code) 1499 { 1500 import core.sys.posix.sys.wait : waitpid; 1501 import core.sys.posix.unistd : fork, _exit; 1502 int child, status; 1503 if ((child = fork()) == 0) 1504 { 1505 code(); 1506 _exit(0); 1507 } 1508 else 1509 { 1510 assert(waitpid(child, &status, 0) != -1); 1511 assert(status == 0, "Fork crashed"); 1512 } 1513 } 1514 1515 auto f = File(deleteme, "w+b"); 1516 1517 runForked 1518 ({ 1519 auto g = File(deleteme, "a+b"); 1520 assert(g.tryLock()); 1521 g.unlock(); 1522 assert(g.tryLock(LockType.read)); 1523 }); 1524 1525 assert(f.tryLock()); 1526 runForked 1527 ({ 1528 auto g = File(deleteme, "a+b"); 1529 assert(!g.tryLock()); 1530 assert(!g.tryLock(LockType.read)); 1531 }); 1532 f.unlock(); 1533 1534 f.lock(LockType.read); 1535 runForked 1536 ({ 1537 auto g = File(deleteme, "a+b"); 1538 assert(!g.tryLock()); 1539 assert(g.tryLock(LockType.read)); 1540 g.unlock(); 1541 }); 1542 f.unlock(); 1543 } // static if 1544 } // unittest 1545 1546 1547 /** 1548 Writes its arguments in text format to the file. 1549 1550 Throws: `Exception` if the file is not opened. 1551 `ErrnoException` on an error writing to the file. 1552 */ 1553 void write(S...)(S args) 1554 { 1555 import std.traits : isBoolean, isIntegral, isAggregateType; 1556 import std.utf : UTFException; 1557 auto w = lockingTextWriter(); 1558 foreach (arg; args) 1559 { 1560 try 1561 { 1562 alias A = typeof(arg); 1563 static if (isAggregateType!A || is(A == enum)) 1564 { 1565 import std.format.write : formattedWrite; 1566 1567 formattedWrite(w, "%s", arg); 1568 } 1569 else static if (isSomeString!A) 1570 { 1571 put(w, arg); 1572 } 1573 else static if (isIntegral!A) 1574 { 1575 import std.conv : toTextRange; 1576 1577 toTextRange(arg, w); 1578 } 1579 else static if (isBoolean!A) 1580 { 1581 put(w, arg ? "true" : "false"); 1582 } 1583 else static if (isSomeChar!A) 1584 { 1585 put(w, arg); 1586 } 1587 else 1588 { 1589 import std.format.write : formattedWrite; 1590 1591 // Most general case 1592 formattedWrite(w, "%s", arg); 1593 } 1594 } 1595 catch (UTFException e) 1596 { 1597 /* Reset the writer so that it doesn't throw another 1598 UTFException on destruction. */ 1599 w.highSurrogate = '\0'; 1600 throw e; 1601 } 1602 } 1603 } 1604 1605 /** 1606 Writes its arguments in text format to the file, followed by a newline. 1607 1608 Throws: `Exception` if the file is not opened. 1609 `ErrnoException` on an error writing to the file. 1610 */ 1611 void writeln(S...)(S args) 1612 { 1613 write(args, '\n'); 1614 } 1615 1616 /** 1617 Writes its arguments in text format to the file, according to the 1618 format string fmt. 1619 1620 Params: 1621 fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format). 1622 When passed as a compile-time argument, the string will be statically checked 1623 against the argument types passed. 1624 args = Items to write. 1625 1626 Throws: `Exception` if the file is not opened. 1627 `ErrnoException` on an error writing to the file. 1628 */ 1629 void writef(alias fmt, A...)(A args) 1630 if (isSomeString!(typeof(fmt))) 1631 { 1632 import std.format : checkFormatException; 1633 1634 alias e = checkFormatException!(fmt, A); 1635 static assert(!e, e); 1636 return this.writef(fmt, args); 1637 } 1638 1639 /// ditto 1640 void writef(Char, A...)(in Char[] fmt, A args) 1641 { 1642 import std.format.write : formattedWrite; 1643 1644 formattedWrite(lockingTextWriter(), fmt, args); 1645 } 1646 1647 /// Equivalent to `file.writef(fmt, args, '\n')`. 1648 void writefln(alias fmt, A...)(A args) 1649 if (isSomeString!(typeof(fmt))) 1650 { 1651 import std.format : checkFormatException; 1652 1653 alias e = checkFormatException!(fmt, A); 1654 static assert(!e, e); 1655 return this.writefln(fmt, args); 1656 } 1657 1658 /// ditto 1659 void writefln(Char, A...)(in Char[] fmt, A args) 1660 { 1661 import std.format.write : formattedWrite; 1662 1663 auto w = lockingTextWriter(); 1664 formattedWrite(w, fmt, args); 1665 w.put('\n'); 1666 } 1667 1668 /** 1669 Read line from the file handle and return it as a specified type. 1670 1671 This version manages its own read buffer, which means one memory allocation per call. If you are not 1672 retaining a reference to the read data, consider the `File.readln(buf)` version, which may offer 1673 better performance as it can reuse its read buffer. 1674 1675 Params: 1676 S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`. 1677 terminator = Line terminator (by default, `'\n'`). 1678 1679 Note: 1680 String terminators are not supported due to ambiguity with readln(buf) below. 1681 1682 Returns: 1683 The line that was read, including the line terminator character. 1684 1685 Throws: 1686 `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. 1687 1688 Example: 1689 --- 1690 // Reads `stdin` and writes it to `stdout`. 1691 import std.stdio; 1692 1693 void main() 1694 { 1695 string line; 1696 while ((line = stdin.readln()) !is null) 1697 write(line); 1698 } 1699 --- 1700 */ 1701 S readln(S = string)(dchar terminator = '\n') @safe 1702 if (isSomeString!S) 1703 { 1704 Unqual!(ElementEncodingType!S)[] buf; 1705 readln(buf, terminator); 1706 return (() @trusted => cast(S) buf)(); 1707 } 1708 1709 @safe unittest 1710 { 1711 import std.algorithm.comparison : equal; 1712 static import std.file; 1713 import std.meta : AliasSeq; 1714 1715 auto deleteme = testFilename(); 1716 std.file.write(deleteme, "hello\nworld\n"); 1717 scope(exit) std.file.remove(deleteme); 1718 static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) 1719 {{ 1720 auto witness = [ "hello\n", "world\n" ]; 1721 auto f = File(deleteme); 1722 uint i = 0; 1723 String buf; 1724 while ((buf = f.readln!String()).length) 1725 { 1726 assert(i < witness.length); 1727 assert(equal(buf, witness[i++])); 1728 } 1729 assert(i == witness.length); 1730 }} 1731 } 1732 1733 @safe unittest 1734 { 1735 static import std.file; 1736 import std.typecons : Tuple; 1737 1738 auto deleteme = testFilename(); 1739 std.file.write(deleteme, "cześć \U0002000D"); 1740 scope(exit) std.file.remove(deleteme); 1741 uint[] lengths = [12,8,7]; 1742 static foreach (uint i, C; Tuple!(char, wchar, dchar).Types) 1743 {{ 1744 immutable(C)[] witness = "cześć \U0002000D"; 1745 auto buf = File(deleteme).readln!(immutable(C)[])(); 1746 assert(buf.length == lengths[i]); 1747 assert(buf == witness); 1748 }} 1749 } 1750 1751 /** 1752 Read line from the file handle and write it to `buf[]`, including 1753 terminating character. 1754 1755 This can be faster than $(D line = File.readln()) because you can reuse 1756 the buffer for each call. Note that reusing the buffer means that you 1757 must copy the previous contents if you wish to retain them. 1758 1759 Params: 1760 buf = Buffer used to store the resulting line data. buf is 1761 enlarged if necessary, then set to the slice exactly containing the line. 1762 terminator = Line terminator (by default, `'\n'`). Use 1763 $(REF newline, std,ascii) for portability (unless the file was opened in 1764 text mode). 1765 1766 Returns: 1767 0 for end of file, otherwise number of characters read. 1768 The return value will always be equal to `buf.length`. 1769 1770 Throws: `StdioException` on I/O error, or `UnicodeException` on Unicode 1771 conversion error. 1772 1773 Example: 1774 --- 1775 // Read lines from `stdin` into a string 1776 // Ignore lines starting with '#' 1777 // Write the string to `stdout` 1778 import std.stdio; 1779 1780 void main() 1781 { 1782 string output; 1783 char[] buf; 1784 1785 while (stdin.readln(buf)) 1786 { 1787 if (buf[0] == '#') 1788 continue; 1789 1790 output ~= buf; 1791 } 1792 1793 write(output); 1794 } 1795 --- 1796 1797 This method can be more efficient than the one in the previous example 1798 because `stdin.readln(buf)` reuses (if possible) memory allocated 1799 for `buf`, whereas $(D line = stdin.readln()) makes a new memory allocation 1800 for every line. 1801 1802 For even better performance you can help `readln` by passing in a 1803 large buffer to avoid memory reallocations. This can be done by reusing the 1804 largest buffer returned by `readln`: 1805 1806 Example: 1807 --- 1808 // Read lines from `stdin` and count words 1809 import std.array, std.stdio; 1810 1811 void main() 1812 { 1813 char[] buf; 1814 size_t words = 0; 1815 1816 while (!stdin.eof) 1817 { 1818 char[] line = buf; 1819 stdin.readln(line); 1820 if (line.length > buf.length) 1821 buf = line; 1822 1823 words += line.split.length; 1824 } 1825 1826 writeln(words); 1827 } 1828 --- 1829 This is actually what $(LREF byLine) does internally, so its usage 1830 is recommended if you want to process a complete file. 1831 */ 1832 size_t readln(C)(ref C[] buf, dchar terminator = '\n') @safe 1833 if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) 1834 { 1835 import std.exception : enforce; 1836 1837 static if (is(C == char)) 1838 { 1839 enforce(_p && _p.handle, "Attempt to read from an unopened file."); 1840 if (_p.orientation == Orientation.unknown) 1841 { 1842 import core.stdc.wchar_ : fwide; 1843 auto w = fwide(_p.handle, 0); 1844 if (w < 0) _p.orientation = Orientation.narrow; 1845 else if (w > 0) _p.orientation = Orientation.wide; 1846 } 1847 return readlnImpl(_p.handle, buf, terminator, _p.orientation); 1848 } 1849 else 1850 { 1851 string s = readln(terminator); 1852 if (!s.length) 1853 { 1854 buf = buf[0 .. 0]; 1855 return 0; 1856 } 1857 1858 import std.utf : codeLength; 1859 buf.length = codeLength!C(s); 1860 size_t idx; 1861 foreach (C c; s) 1862 buf[idx++] = c; 1863 1864 return buf.length; 1865 } 1866 } 1867 1868 @safe unittest 1869 { 1870 static import std.file; 1871 auto deleteme = testFilename(); 1872 std.file.write(deleteme, "123\n456789"); 1873 scope(exit) std.file.remove(deleteme); 1874 1875 auto file = File(deleteme); 1876 char[] buffer = new char[10]; 1877 char[] line = buffer; 1878 file.readln(line); 1879 auto beyond = line.length; 1880 buffer[beyond] = 'a'; 1881 file.readln(line); // should not write buffer beyond line 1882 assert(buffer[beyond] == 'a'); 1883 } 1884 1885 // https://issues.dlang.org/show_bug.cgi?id=15293 1886 @safe unittest 1887 { 1888 // @system due to readln 1889 static import std.file; 1890 auto deleteme = testFilename(); 1891 std.file.write(deleteme, "a\n\naa"); 1892 scope(exit) std.file.remove(deleteme); 1893 1894 auto file = File(deleteme); 1895 char[] buffer; 1896 char[] line; 1897 1898 file.readln(buffer, '\n'); 1899 1900 line = buffer; 1901 file.readln(line, '\n'); 1902 1903 line = buffer; 1904 file.readln(line, '\n'); 1905 1906 assert(line[0 .. 1].capacity == 0); 1907 } 1908 1909 /** ditto */ 1910 size_t readln(C, R)(ref C[] buf, R terminator) @safe 1911 if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && 1912 isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) 1913 { 1914 import std.algorithm.mutation : swap; 1915 import std.algorithm.searching : endsWith; 1916 import std.range.primitives : back; 1917 1918 auto last = terminator.back; 1919 C[] buf2; 1920 swap(buf, buf2); 1921 for (;;) 1922 { 1923 if (!readln(buf2, last) || endsWith(buf2, terminator)) 1924 { 1925 if (buf.empty) 1926 { 1927 buf = buf2; 1928 } 1929 else 1930 { 1931 buf ~= buf2; 1932 } 1933 break; 1934 } 1935 buf ~= buf2; 1936 } 1937 return buf.length; 1938 } 1939 1940 @safe unittest 1941 { 1942 static import std.file; 1943 import std.typecons : Tuple; 1944 1945 auto deleteme = testFilename(); 1946 std.file.write(deleteme, "hello\n\rworld\nhow\n\rare ya"); 1947 scope(exit) std.file.remove(deleteme); 1948 foreach (C; Tuple!(char, wchar, dchar).Types) 1949 { 1950 immutable(C)[][] witness = [ "hello\n\r", "world\nhow\n\r", "are ya" ]; 1951 auto f = File(deleteme); 1952 uint i = 0; 1953 C[] buf; 1954 while (f.readln(buf, "\n\r")) 1955 { 1956 assert(i < witness.length); 1957 assert(buf == witness[i++]); 1958 } 1959 assert(buf.length == 0); 1960 } 1961 } 1962 1963 /** 1964 * Reads formatted _data from the file using $(REF formattedRead, std,_format). 1965 * Params: 1966 * format = The $(REF_ALTTEXT format string, formattedWrite, std, _format). 1967 * When passed as a compile-time argument, the string will be statically checked 1968 * against the argument types passed. 1969 * data = Items to be read. 1970 * Returns: 1971 * Same as `formattedRead`: The number of variables filled. If the input range `r` ends early, 1972 * this number will be less than the number of variables provided. 1973 * Example: 1974 ---- 1975 // test.d 1976 void main() 1977 { 1978 import std.stdio; 1979 auto f = File("input"); 1980 foreach (_; 0 .. 3) 1981 { 1982 int a; 1983 f.readf!" %d"(a); 1984 writeln(++a); 1985 } 1986 } 1987 ---- 1988 $(CONSOLE 1989 % echo "1 2 3" > input 1990 % rdmd test.d 1991 2 1992 3 1993 4 1994 ) 1995 */ 1996 uint readf(alias format, Data...)(auto ref Data data) 1997 if (isSomeString!(typeof(format))) 1998 { 1999 import std.format : checkFormatException; 2000 2001 alias e = checkFormatException!(format, Data); 2002 static assert(!e, e); 2003 return this.readf(format, data); 2004 } 2005 2006 /// ditto 2007 uint readf(Data...)(scope const(char)[] format, auto ref Data data) 2008 { 2009 import std.format.read : formattedRead; 2010 2011 assert(isOpen); 2012 auto input = LockingTextReader(this); 2013 return formattedRead(input, format, data); 2014 } 2015 2016 /// 2017 @system unittest 2018 { 2019 static import std.file; 2020 2021 auto deleteme = std.file.deleteme(); 2022 std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); 2023 scope(exit) std.file.remove(deleteme); 2024 string s; 2025 auto f = File(deleteme); 2026 f.readf!"%s\n"(s); 2027 assert(s == "hello", "["~s~"]"); 2028 f.readf("%s\n", s); 2029 assert(s == "world", "["~s~"]"); 2030 2031 bool b1, b2; 2032 f.readf("%s\n%s\n", b1, b2); 2033 assert(b1 == true && b2 == false); 2034 } 2035 2036 // backwards compatibility with pointers 2037 @system unittest 2038 { 2039 // @system due to readf 2040 static import std.file; 2041 2042 auto deleteme = testFilename(); 2043 std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); 2044 scope(exit) std.file.remove(deleteme); 2045 string s; 2046 auto f = File(deleteme); 2047 f.readf("%s\n", &s); 2048 assert(s == "hello", "["~s~"]"); 2049 f.readf("%s\n", &s); 2050 assert(s == "world", "["~s~"]"); 2051 2052 // https://issues.dlang.org/show_bug.cgi?id=11698 2053 bool b1, b2; 2054 f.readf("%s\n%s\n", &b1, &b2); 2055 assert(b1 == true && b2 == false); 2056 } 2057 2058 // backwards compatibility (mixed) 2059 @system unittest 2060 { 2061 // @system due to readf 2062 static import std.file; 2063 2064 auto deleteme = testFilename(); 2065 std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); 2066 scope(exit) std.file.remove(deleteme); 2067 string s1, s2; 2068 auto f = File(deleteme); 2069 f.readf("%s\n%s\n", s1, &s2); 2070 assert(s1 == "hello"); 2071 assert(s2 == "world"); 2072 2073 // https://issues.dlang.org/show_bug.cgi?id=11698 2074 bool b1, b2; 2075 f.readf("%s\n%s\n", &b1, b2); 2076 assert(b1 == true && b2 == false); 2077 } 2078 2079 // Nice error of std.stdio.readf with newlines 2080 // https://issues.dlang.org/show_bug.cgi?id=12260 2081 @system unittest 2082 { 2083 static import std.file; 2084 2085 auto deleteme = testFilename(); 2086 std.file.write(deleteme, "1\n2"); 2087 scope(exit) std.file.remove(deleteme); 2088 int input; 2089 auto f = File(deleteme); 2090 f.readf("%s", &input); 2091 2092 import std.conv : ConvException; 2093 import std.exception : collectException; 2094 assert(collectException!ConvException(f.readf("%s", &input)).msg == 2095 "Unexpected '\\n' when converting from type LockingTextReader to type int"); 2096 } 2097 2098 /** 2099 Reads a line from the file and parses it using $(REF formattedRead, std,format,read). 2100 2101 Params: 2102 format = The $(MREF_ALTTEXT format string, std,format). When passed as a 2103 compile-time argument, the string will be statically checked against the 2104 argument types passed. 2105 data = Items to be read. 2106 2107 Returns: Same as `formattedRead`: the number of variables filled. If the 2108 input ends early, this number will be less that the number of variables 2109 provided. 2110 2111 Example: 2112 --- 2113 // sum_rows.d 2114 void main() 2115 { 2116 import std.stdio; 2117 auto f = File("input"); 2118 int a, b, c; 2119 while (f.readfln("%d %d %d", a, b, c) == 3) 2120 { 2121 writeln(a + b + c); 2122 } 2123 } 2124 --- 2125 $(CONSOLE 2126 % cat << EOF > input 2127 1 2 3 2128 4 5 6 2129 7 8 9 2130 EOF 2131 % rdmd sum_rows.d 2132 6 2133 15 2134 24 2135 ) 2136 */ 2137 uint readfln(alias format, Data...)(auto ref Data data) 2138 if (isSomeString!(typeof(format))) 2139 { 2140 import std.format : checkFormatException; 2141 2142 alias e = checkFormatException!(format, Data); 2143 static assert(!e, e); 2144 return this.readfln(format, data); 2145 } 2146 2147 /// ditto 2148 uint readfln(Data...)(scope const(char)[] format, auto ref Data data) 2149 { 2150 import std.format.read : formattedRead; 2151 import std.string : stripRight; 2152 2153 string line = this.readln.stripRight("\r\n"); 2154 return formattedRead(line, format, data); 2155 } 2156 2157 @system unittest 2158 { 2159 static import std.file; 2160 2161 auto deleteme = testFilename(); 2162 std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); 2163 scope(exit) std.file.remove(deleteme); 2164 string s; 2165 auto f = File(deleteme); 2166 f.readfln!"%s"(s); 2167 assert(s == "hello", "["~s~"]"); 2168 f.readfln("%s", s); 2169 assert(s == "world", "["~s~"]"); 2170 2171 bool b1, b2; 2172 f.readfln("%s", b1); 2173 f.readfln("%s", b2); 2174 assert(b1 == true && b2 == false); 2175 } 2176 2177 /** 2178 Returns a temporary file by calling $(CSTDIO tmpfile). 2179 Note that the created file has no $(LREF name).*/ 2180 static File tmpfile() @safe 2181 { 2182 import std.exception : errnoEnforce; 2183 2184 return File(errnoEnforce(.tmpfile(), 2185 "Could not create temporary file with tmpfile()"), 2186 null); 2187 } 2188 2189 /** 2190 Unsafe function that wraps an existing `FILE*`. The resulting $(D 2191 File) never takes the initiative in closing the file. 2192 Note that the created file has no $(LREF name)*/ 2193 /*private*/ static File wrapFile(FILE* f) @safe 2194 { 2195 import std.exception : enforce; 2196 2197 return File(enforce(f, "Could not wrap null FILE*"), 2198 null, /*uint.max / 2*/ 9999); 2199 } 2200 2201 /** 2202 Returns the `FILE*` corresponding to this object. 2203 */ 2204 FILE* getFP() @safe pure 2205 { 2206 import std.exception : enforce; 2207 2208 enforce(_p && _p.handle, 2209 "Attempting to call getFP() on an unopened file"); 2210 return _p.handle; 2211 } 2212 2213 @system unittest 2214 { 2215 static import core.stdc.stdio; 2216 assert(stdout.getFP() == core.stdc.stdio.stdout); 2217 } 2218 2219 /** 2220 Returns the file number corresponding to this object. 2221 */ 2222 @property fileno_t fileno() const @trusted 2223 { 2224 import std.exception : enforce; 2225 2226 enforce(isOpen, "Attempting to call fileno() on an unopened file"); 2227 return .fileno(cast(FILE*) _p.handle); 2228 } 2229 2230 /** 2231 Returns the underlying operating system `HANDLE` (Windows only). 2232 */ 2233 version (StdDdoc) 2234 @property HANDLE windowsHandle(); 2235 2236 version (Windows) 2237 @property HANDLE windowsHandle() 2238 { 2239 return cast(HANDLE)_get_osfhandle(fileno); 2240 } 2241 2242 2243 // Note: This was documented until 2013/08 2244 /* 2245 Range that reads one line at a time. Returned by $(LREF byLine). 2246 2247 Allows to directly use range operations on lines of a file. 2248 */ 2249 private struct ByLineImpl(Char, Terminator) 2250 { 2251 private: 2252 import std.typecons : borrow, RefCountedAutoInitialize, SafeRefCounted; 2253 2254 /* Ref-counting stops the source range's Impl 2255 * from getting out of sync after the range is copied, e.g. 2256 * when accessing range.front, then using std.range.take, 2257 * then accessing range.front again. */ 2258 alias PImpl = SafeRefCounted!(Impl, RefCountedAutoInitialize.no); 2259 PImpl impl; 2260 2261 static if (isScalarType!Terminator) 2262 enum defTerm = '\n'; 2263 else 2264 enum defTerm = cast(Terminator)"\n"; 2265 2266 public: 2267 this(File f, KeepTerminator kt = No.keepTerminator, 2268 Terminator terminator = defTerm) 2269 { 2270 impl = PImpl(f, kt, terminator); 2271 } 2272 2273 /* Verifiably `@safe` when built with -preview=DIP1000. */ 2274 @property bool empty() @trusted 2275 { 2276 // Using `ref` is actually necessary here. 2277 return impl.borrow!((ref i) => i.empty); 2278 } 2279 2280 /* Verifiably `@safe` when built with -preview=DIP1000. */ 2281 @property Char[] front() @trusted 2282 { 2283 // Using `ref` is likely optional here. 2284 return impl.borrow!((ref i) => i.front); 2285 } 2286 2287 /* Verifiably `@safe` when built with -preview=DIP1000. */ 2288 void popFront() @trusted 2289 { 2290 return impl.borrow!((ref i) => i.popFront()); 2291 } 2292 2293 private: 2294 struct Impl 2295 { 2296 private: 2297 File file; 2298 Char[] line; 2299 Char[] buffer; 2300 Terminator terminator; 2301 KeepTerminator keepTerminator; 2302 bool haveLine; 2303 2304 @safe: 2305 public: 2306 this(File f, KeepTerminator kt, Terminator terminator) 2307 { 2308 file = f; 2309 this.terminator = terminator; 2310 keepTerminator = kt; 2311 } 2312 2313 // Range primitive implementations. 2314 @property bool empty() 2315 { 2316 needLine(); 2317 return line is null; 2318 } 2319 2320 @property Char[] front() 2321 { 2322 needLine(); 2323 return line; 2324 } 2325 2326 void popFront() 2327 { 2328 needLine(); 2329 haveLine = false; 2330 } 2331 2332 private: 2333 void needLine() 2334 { 2335 if (haveLine) 2336 return; 2337 import std.algorithm.searching : endsWith; 2338 assert(file.isOpen); 2339 line = buffer; 2340 file.readln(line, terminator); 2341 if (line.length > buffer.length) 2342 { 2343 buffer = line; 2344 } 2345 if (line.empty) 2346 { 2347 file.detach(); 2348 line = null; 2349 } 2350 else if (keepTerminator == No.keepTerminator 2351 && endsWith(line, terminator)) 2352 { 2353 static if (isScalarType!Terminator) 2354 enum tlen = 1; 2355 else static if (isArray!Terminator) 2356 { 2357 static assert( 2358 is(immutable ElementEncodingType!Terminator == immutable Char)); 2359 const tlen = terminator.length; 2360 } 2361 else 2362 static assert(false); 2363 line = line[0 .. line.length - tlen]; 2364 } 2365 haveLine = true; 2366 } 2367 } 2368 } 2369 2370 /** 2371 Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2372 set up to read from the file handle one line at a time. 2373 2374 The element type for the range will be `Char[]`. Range primitives 2375 may throw `StdioException` on I/O error. 2376 2377 Note: 2378 Each `front` will not persist after $(D 2379 popFront) is called, so the caller must copy its contents (e.g. by 2380 calling `to!string`) when retention is needed. If the caller needs 2381 to retain a copy of every line, use the $(LREF byLineCopy) function 2382 instead. 2383 2384 Params: 2385 Char = Character type for each line, defaulting to `char`. 2386 keepTerminator = Use `Yes.keepTerminator` to include the 2387 terminator at the end of each line. 2388 terminator = Line separator (`'\n'` by default). Use 2389 $(REF newline, std,ascii) for portability (unless the file was opened in 2390 text mode). 2391 2392 Example: 2393 ---- 2394 import std.algorithm, std.stdio, std.string; 2395 // Count words in a file using ranges. 2396 void main() 2397 { 2398 auto file = File("file.txt"); // Open for reading 2399 const wordCount = file.byLine() // Read lines 2400 .map!split // Split into words 2401 .map!(a => a.length) // Count words per line 2402 .sum(); // Total word count 2403 writeln(wordCount); 2404 } 2405 ---- 2406 2407 Example: 2408 ---- 2409 import std.range, std.stdio; 2410 // Read lines using foreach. 2411 void main() 2412 { 2413 auto file = File("file.txt"); // Open for reading 2414 auto range = file.byLine(); 2415 // Print first three lines 2416 foreach (line; range.take(3)) 2417 writeln(line); 2418 // Print remaining lines beginning with '#' 2419 foreach (line; range) 2420 { 2421 if (!line.empty && line[0] == '#') 2422 writeln(line); 2423 } 2424 } 2425 ---- 2426 Notice that neither example accesses the line data returned by 2427 `front` after the corresponding `popFront` call is made (because 2428 the contents may well have changed). 2429 ---- 2430 2431 Windows specific Example: 2432 ---- 2433 import std.stdio; 2434 2435 version (Windows) 2436 void main() 2437 { 2438 2439 foreach (line; File("file.txt").byLine(No.keepTerminator, "\r\n")) 2440 { 2441 writeln("|"~line~"|"); 2442 if (line == "HelloWorld") 2443 writeln("^This Line is here."); 2444 } 2445 2446 } 2447 */ 2448 auto byLine(Terminator = char, Char = char) 2449 (KeepTerminator keepTerminator = No.keepTerminator, 2450 Terminator terminator = '\n') 2451 if (isScalarType!Terminator) 2452 { 2453 return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); 2454 } 2455 2456 /// ditto 2457 auto byLine(Terminator, Char = char) 2458 (KeepTerminator keepTerminator, Terminator terminator) 2459 if (is(immutable ElementEncodingType!Terminator == immutable Char)) 2460 { 2461 return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); 2462 } 2463 2464 @safe unittest 2465 { 2466 static import std.file; 2467 auto deleteme = testFilename(); 2468 std.file.write(deleteme, "hi"); 2469 scope(success) std.file.remove(deleteme); 2470 2471 import std.meta : AliasSeq; 2472 static foreach (T; AliasSeq!(char, wchar, dchar)) 2473 {{ 2474 auto blc = File(deleteme).byLine!(T, T); 2475 assert(blc.front == "hi"); 2476 // check front is cached 2477 assert(blc.front is blc.front); 2478 }} 2479 } 2480 2481 // https://issues.dlang.org/show_bug.cgi?id=19980 2482 @safe unittest 2483 { 2484 static import std.file; 2485 auto deleteme = testFilename(); 2486 std.file.write(deleteme, "Line 1\nLine 2\nLine 3\n"); 2487 scope(success) std.file.remove(deleteme); 2488 2489 auto f = File(deleteme); 2490 f.byLine(); 2491 f.byLine(); 2492 assert(f.byLine().front == "Line 1"); 2493 } 2494 2495 private struct ByLineCopy(Char, Terminator) 2496 { 2497 private: 2498 import std.typecons : borrow, RefCountedAutoInitialize, SafeRefCounted; 2499 2500 /* Ref-counting stops the source range's ByLineCopyImpl 2501 * from getting out of sync after the range is copied, e.g. 2502 * when accessing range.front, then using std.range.take, 2503 * then accessing range.front again. */ 2504 alias Impl = SafeRefCounted!(ByLineCopyImpl!(Char, Terminator), 2505 RefCountedAutoInitialize.no); 2506 Impl impl; 2507 2508 public: 2509 this(File f, KeepTerminator kt, Terminator terminator) 2510 { 2511 impl = Impl(f, kt, terminator); 2512 } 2513 2514 /* Verifiably `@safe` when built with -preview=DIP1000. */ 2515 @property bool empty() @trusted 2516 { 2517 // Using `ref` is actually necessary here. 2518 return impl.borrow!((ref i) => i.empty); 2519 } 2520 2521 /* Verifiably `@safe` when built with -preview=DIP1000. */ 2522 @property Char[] front() @trusted 2523 { 2524 // Using `ref` is likely optional here. 2525 return impl.borrow!((ref i) => i.front); 2526 } 2527 2528 /* Verifiably `@safe` when built with -preview=DIP1000. */ 2529 void popFront() @trusted 2530 { 2531 impl.borrow!((ref i) => i.popFront()); 2532 } 2533 } 2534 2535 private struct ByLineCopyImpl(Char, Terminator) 2536 { 2537 ByLineImpl!(Unqual!Char, Terminator).Impl impl; 2538 bool gotFront; 2539 Char[] line; 2540 2541 public: 2542 this(File f, KeepTerminator kt, Terminator terminator) 2543 { 2544 impl = ByLineImpl!(Unqual!Char, Terminator).Impl(f, kt, terminator); 2545 } 2546 2547 @property bool empty() 2548 { 2549 return impl.empty; 2550 } 2551 2552 @property front() 2553 { 2554 if (!gotFront) 2555 { 2556 line = impl.front.dup; 2557 gotFront = true; 2558 } 2559 return line; 2560 } 2561 2562 void popFront() 2563 { 2564 impl.popFront(); 2565 gotFront = false; 2566 } 2567 } 2568 2569 /** 2570 Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2571 set up to read from the file handle one line 2572 at a time. Each line will be newly allocated. `front` will cache 2573 its value to allow repeated calls without unnecessary allocations. 2574 2575 Note: Due to caching byLineCopy can be more memory-efficient than 2576 `File.byLine.map!idup`. 2577 2578 The element type for the range will be `Char[]`. Range 2579 primitives may throw `StdioException` on I/O error. 2580 2581 Params: 2582 Char = Character type for each line, defaulting to $(D immutable char). 2583 keepTerminator = Use `Yes.keepTerminator` to include the 2584 terminator at the end of each line. 2585 terminator = Line separator (`'\n'` by default). Use 2586 $(REF newline, std,ascii) for portability (unless the file was opened in 2587 text mode). 2588 2589 Example: 2590 ---- 2591 import std.algorithm, std.array, std.stdio; 2592 // Print sorted lines of a file. 2593 void main() 2594 { 2595 auto sortedLines = File("file.txt") // Open for reading 2596 .byLineCopy() // Read persistent lines 2597 .array() // into an array 2598 .sort(); // then sort them 2599 foreach (line; sortedLines) 2600 writeln(line); 2601 } 2602 ---- 2603 See_Also: 2604 $(REF readText, std,file) 2605 */ 2606 auto byLineCopy(Terminator = char, Char = immutable char) 2607 (KeepTerminator keepTerminator = No.keepTerminator, 2608 Terminator terminator = '\n') 2609 if (isScalarType!Terminator) 2610 { 2611 return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); 2612 } 2613 2614 /// ditto 2615 auto byLineCopy(Terminator, Char = immutable char) 2616 (KeepTerminator keepTerminator, Terminator terminator) 2617 if (is(immutable ElementEncodingType!Terminator == immutable Char)) 2618 { 2619 return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); 2620 } 2621 2622 @safe unittest 2623 { 2624 static assert(is(typeof(File("").byLine.front) == char[])); 2625 static assert(is(typeof(File("").byLineCopy.front) == string)); 2626 static assert( 2627 is(typeof(File("").byLineCopy!(char, char).front) == char[])); 2628 } 2629 2630 @safe unittest 2631 { 2632 import std.algorithm.comparison : equal; 2633 static import std.file; 2634 2635 auto deleteme = testFilename(); 2636 std.file.write(deleteme, ""); 2637 scope(success) std.file.remove(deleteme); 2638 2639 // Test empty file 2640 auto f = File(deleteme); 2641 foreach (line; f.byLine()) 2642 { 2643 assert(false); 2644 } 2645 f.detach(); 2646 assert(!f.isOpen); 2647 2648 void test(Terminator)(string txt, in string[] witness, 2649 KeepTerminator kt, Terminator term, bool popFirstLine = false) 2650 { 2651 import std.algorithm.sorting : sort; 2652 import std.array : array; 2653 import std.conv : text; 2654 import std.range.primitives : walkLength; 2655 2656 uint i; 2657 std.file.write(deleteme, txt); 2658 auto f = File(deleteme); 2659 scope(exit) 2660 { 2661 f.close(); 2662 assert(!f.isOpen); 2663 } 2664 auto lines = f.byLine(kt, term); 2665 if (popFirstLine) 2666 { 2667 lines.popFront(); 2668 i = 1; 2669 } 2670 assert(lines.empty || lines.front is lines.front); 2671 foreach (line; lines) 2672 { 2673 assert(line == witness[i++]); 2674 } 2675 assert(i == witness.length, text(i, " != ", witness.length)); 2676 2677 // https://issues.dlang.org/show_bug.cgi?id=11830 2678 auto walkedLength = File(deleteme).byLine(kt, term).walkLength; 2679 assert(walkedLength == witness.length, text(walkedLength, " != ", witness.length)); 2680 2681 // test persistent lines 2682 assert(File(deleteme).byLineCopy(kt, term).array.sort() == witness.dup.sort()); 2683 } 2684 2685 KeepTerminator kt = No.keepTerminator; 2686 test("", null, kt, '\n'); 2687 test("\n", [ "" ], kt, '\n'); 2688 test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n'); 2689 test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n', true); 2690 test("asd\ndef\nasdf\n", [ "asd", "def", "asdf" ], kt, '\n'); 2691 test("foo", [ "foo" ], kt, '\n', true); 2692 test("bob\r\nmarge\r\nsteve\r\n", ["bob", "marge", "steve"], 2693 kt, "\r\n"); 2694 test("sue\r", ["sue"], kt, '\r'); 2695 2696 kt = Yes.keepTerminator; 2697 test("", null, kt, '\n'); 2698 test("\n", [ "\n" ], kt, '\n'); 2699 test("asd\ndef\nasdf", [ "asd\n", "def\n", "asdf" ], kt, '\n'); 2700 test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n'); 2701 test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n', true); 2702 test("foo", [ "foo" ], kt, '\n'); 2703 test("bob\r\nmarge\r\nsteve\r\n", ["bob\r\n", "marge\r\n", "steve\r\n"], 2704 kt, "\r\n"); 2705 test("sue\r", ["sue\r"], kt, '\r'); 2706 } 2707 2708 @safe unittest 2709 { 2710 import std.algorithm.comparison : equal; 2711 import std.range : drop, take; 2712 2713 version (Win64) 2714 { 2715 static import std.file; 2716 2717 /* the C function tmpfile doesn't seem to work, even when called from C */ 2718 auto deleteme = testFilename(); 2719 auto file = File(deleteme, "w+"); 2720 scope(success) std.file.remove(deleteme); 2721 } 2722 else version (CRuntime_Bionic) 2723 { 2724 static import std.file; 2725 2726 /* the C function tmpfile doesn't work when called from a shared 2727 library apk: 2728 https://code.google.com/p/android/issues/detail?id=66815 */ 2729 auto deleteme = testFilename(); 2730 auto file = File(deleteme, "w+"); 2731 scope(success) std.file.remove(deleteme); 2732 } 2733 else 2734 auto file = File.tmpfile(); 2735 file.write("1\n2\n3\n"); 2736 2737 // https://issues.dlang.org/show_bug.cgi?id=9599 2738 file.rewind(); 2739 File.ByLineImpl!(char, char) fbl = file.byLine(); 2740 auto fbl2 = fbl; 2741 assert(fbl.front == "1"); 2742 assert(fbl.front is fbl2.front); 2743 assert(fbl.take(1).equal(["1"])); 2744 assert(fbl.equal(["2", "3"])); 2745 assert(fbl.empty); 2746 assert(file.isOpen); // we still have a valid reference 2747 2748 file.rewind(); 2749 fbl = file.byLine(); 2750 assert(!fbl.drop(2).empty); 2751 assert(fbl.equal(["3"])); 2752 assert(fbl.empty); 2753 assert(file.isOpen); 2754 2755 file.detach(); 2756 assert(!file.isOpen); 2757 } 2758 2759 @safe unittest 2760 { 2761 static import std.file; 2762 auto deleteme = testFilename(); 2763 std.file.write(deleteme, "hi"); 2764 scope(success) std.file.remove(deleteme); 2765 2766 auto blc = File(deleteme).byLineCopy; 2767 assert(!blc.empty); 2768 // check front is cached 2769 assert(blc.front is blc.front); 2770 } 2771 2772 /** 2773 Creates an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2774 set up to parse one line at a time from the file into a tuple. 2775 2776 Range primitives may throw `StdioException` on I/O error. 2777 2778 Params: 2779 format = tuple record $(REF_ALTTEXT _format, formattedRead, std, _format) 2780 2781 Returns: 2782 The input range set up to parse one line at a time into a record tuple. 2783 2784 See_Also: 2785 2786 It is similar to $(LREF byLine) and uses 2787 $(REF_ALTTEXT _format, formattedRead, std, _format) under the hood. 2788 */ 2789 template byRecord(Fields...) 2790 { 2791 auto byRecord(string format) 2792 { 2793 return ByRecordImpl!(Fields)(this, format); 2794 } 2795 } 2796 2797 /// 2798 @system unittest 2799 { 2800 static import std.file; 2801 import std.typecons : tuple; 2802 2803 // prepare test file 2804 auto testFile = std.file.deleteme(); 2805 scope(failure) printf("Failed test at line %d\n", __LINE__); 2806 std.file.write(testFile, "1 2\n4 1\n5 100"); 2807 scope(exit) std.file.remove(testFile); 2808 2809 File f = File(testFile); 2810 scope(exit) f.close(); 2811 2812 auto expected = [tuple(1, 2), tuple(4, 1), tuple(5, 100)]; 2813 uint i; 2814 foreach (e; f.byRecord!(int, int)("%s %s")) 2815 { 2816 assert(e == expected[i++]); 2817 } 2818 } 2819 2820 // Note: This was documented until 2013/08 2821 /* 2822 * Range that reads a chunk at a time. 2823 */ 2824 private struct ByChunkImpl 2825 { 2826 private: 2827 File file_; 2828 ubyte[] chunk_; 2829 2830 void prime() 2831 { 2832 chunk_ = file_.rawRead(chunk_); 2833 if (chunk_.length == 0) 2834 file_.detach(); 2835 } 2836 2837 public: 2838 this(File file, size_t size) 2839 { 2840 this(file, new ubyte[](size)); 2841 } 2842 2843 this(File file, ubyte[] buffer) 2844 { 2845 import std.exception : enforce; 2846 enforce(buffer.length, "size must be larger than 0"); 2847 file_ = file; 2848 chunk_ = buffer; 2849 prime(); 2850 } 2851 2852 // `ByChunk`'s input range primitive operations. 2853 @property nothrow 2854 bool empty() const 2855 { 2856 return !file_.isOpen; 2857 } 2858 2859 /// Ditto 2860 @property nothrow 2861 ubyte[] front() 2862 { 2863 version (assert) 2864 { 2865 import core.exception : RangeError; 2866 if (empty) 2867 throw new RangeError(); 2868 } 2869 return chunk_; 2870 } 2871 2872 /// Ditto 2873 void popFront() 2874 { 2875 version (assert) 2876 { 2877 import core.exception : RangeError; 2878 if (empty) 2879 throw new RangeError(); 2880 } 2881 prime(); 2882 } 2883 } 2884 2885 /** 2886 Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2887 set up to read from the file handle a chunk at a time. 2888 2889 The element type for the range will be `ubyte[]`. Range primitives 2890 may throw `StdioException` on I/O error. 2891 2892 Example: 2893 --------- 2894 void main() 2895 { 2896 // Read standard input 4KB at a time 2897 foreach (ubyte[] buffer; stdin.byChunk(4096)) 2898 { 2899 ... use buffer ... 2900 } 2901 } 2902 --------- 2903 2904 The parameter may be a number (as shown in the example above) dictating the 2905 size of each chunk. Alternatively, `byChunk` accepts a 2906 user-provided buffer that it uses directly. 2907 2908 Example: 2909 --------- 2910 void main() 2911 { 2912 // Read standard input 4KB at a time 2913 foreach (ubyte[] buffer; stdin.byChunk(new ubyte[4096])) 2914 { 2915 ... use buffer ... 2916 } 2917 } 2918 --------- 2919 2920 In either case, the content of the buffer is reused across calls. That means 2921 `front` will not persist after `popFront` is called, so if retention is 2922 needed, the caller must copy its contents (e.g. by calling `buffer.dup`). 2923 2924 In the example above, `buffer.length` is 4096 for all iterations, except 2925 for the last one, in which case `buffer.length` may be less than 4096 (but 2926 always greater than zero). 2927 2928 With the mentioned limitations, `byChunk` works with any algorithm 2929 compatible with input ranges. 2930 2931 Example: 2932 --- 2933 // Efficient file copy, 1MB at a time. 2934 import std.algorithm, std.stdio; 2935 void main() 2936 { 2937 stdin.byChunk(1024 * 1024).copy(stdout.lockingTextWriter()); 2938 } 2939 --- 2940 2941 $(REF joiner, std,algorithm,iteration) can be used to join chunks together into 2942 a single range lazily. 2943 Example: 2944 --- 2945 import std.algorithm, std.stdio; 2946 void main() 2947 { 2948 //Range of ranges 2949 static assert(is(typeof(stdin.byChunk(4096).front) == ubyte[])); 2950 //Range of elements 2951 static assert(is(typeof(stdin.byChunk(4096).joiner.front) == ubyte)); 2952 } 2953 --- 2954 2955 Returns: A call to `byChunk` returns a range initialized with the `File` 2956 object and the appropriate buffer. 2957 2958 Throws: If the user-provided size is zero or the user-provided buffer 2959 is empty, throws an `Exception`. In case of an I/O error throws 2960 `StdioException`. 2961 */ 2962 auto byChunk(size_t chunkSize) 2963 { 2964 return ByChunkImpl(this, chunkSize); 2965 } 2966 /// Ditto 2967 auto byChunk(ubyte[] buffer) 2968 { 2969 return ByChunkImpl(this, buffer); 2970 } 2971 2972 @system unittest 2973 { 2974 static import std.file; 2975 2976 scope(failure) printf("Failed test at line %d\n", __LINE__); 2977 2978 auto deleteme = testFilename(); 2979 std.file.write(deleteme, "asd\ndef\nasdf"); 2980 2981 auto witness = ["asd\n", "def\n", "asdf" ]; 2982 auto f = File(deleteme); 2983 scope(exit) 2984 { 2985 f.close(); 2986 assert(!f.isOpen); 2987 std.file.remove(deleteme); 2988 } 2989 2990 uint i; 2991 foreach (chunk; f.byChunk(4)) 2992 assert(chunk == cast(ubyte[]) witness[i++]); 2993 2994 assert(i == witness.length); 2995 } 2996 2997 @system unittest 2998 { 2999 static import std.file; 3000 3001 scope(failure) printf("Failed test at line %d\n", __LINE__); 3002 3003 auto deleteme = testFilename(); 3004 std.file.write(deleteme, "asd\ndef\nasdf"); 3005 3006 auto witness = ["asd\n", "def\n", "asdf" ]; 3007 auto f = File(deleteme); 3008 scope(exit) 3009 { 3010 f.close(); 3011 assert(!f.isOpen); 3012 std.file.remove(deleteme); 3013 } 3014 3015 uint i; 3016 foreach (chunk; f.byChunk(new ubyte[4])) 3017 assert(chunk == cast(ubyte[]) witness[i++]); 3018 3019 assert(i == witness.length); 3020 } 3021 3022 // Note: This was documented until 2013/08 3023 /* 3024 `Range` that locks the file and allows fast writing to it. 3025 */ 3026 struct LockingTextWriter 3027 { 3028 private: 3029 import std.range.primitives : ElementType, isInfinite, isInputRange; 3030 // Access the FILE* handle through the 'file_' member 3031 // to keep the object alive through refcounting 3032 File file_; 3033 3034 // the unshared version of FILE* handle, extracted from the File object 3035 @property _iobuf* handle_() @trusted { return cast(_iobuf*) file_._p.handle; } 3036 3037 // the file's orientation (byte- or wide-oriented) 3038 int orientation_; 3039 3040 // Buffers for when we need to transcode. 3041 wchar highSurrogate = '\0'; // '\0' indicates empty 3042 void highSurrogateShouldBeEmpty() @safe 3043 { 3044 import std.utf : UTFException; 3045 if (highSurrogate != '\0') 3046 throw new UTFException("unpaired surrogate UTF-16 value"); 3047 } 3048 char[4] rbuf8; 3049 size_t rbuf8Filled = 0; 3050 public: 3051 3052 this(ref File f) @trusted 3053 { 3054 import std.exception : enforce; 3055 3056 enforce(f._p && f._p.handle, "Attempting to write to closed File"); 3057 file_ = f; 3058 FILE* fps = f._p.handle; 3059 3060 version (CRuntime_Microsoft) 3061 { 3062 // Microsoft doesn't implement fwide. Instead, there's the 3063 // concept of ANSI/UNICODE mode. fputc doesn't work in UNICODE 3064 // mode; fputwc has to be used. So that essentially means 3065 // "wide-oriented" for us. 3066 immutable int mode = _setmode(f.fileno, _O_TEXT); 3067 // Set some arbitrary mode to obtain the previous one. 3068 if (mode != -1) // _setmode() succeeded 3069 { 3070 _setmode(f.fileno, mode); // Restore previous mode. 3071 if (mode & (_O_WTEXT | _O_U16TEXT | _O_U8TEXT)) 3072 { 3073 orientation_ = 1; // wide 3074 } 3075 } 3076 } 3077 else 3078 { 3079 import core.stdc.wchar_ : fwide; 3080 orientation_ = fwide(fps, 0); 3081 } 3082 3083 _FLOCK(fps); 3084 } 3085 3086 ~this() @trusted 3087 { 3088 if (auto p = file_._p) 3089 { 3090 if (p.handle) _FUNLOCK(p.handle); 3091 } 3092 file_ = File.init; 3093 /* Destroy file_ before possibly throwing. Else it wouldn't be 3094 destroyed, and its reference count would be wrong. */ 3095 highSurrogateShouldBeEmpty(); 3096 } 3097 3098 this(this) @trusted 3099 { 3100 if (auto p = file_._p) 3101 { 3102 if (p.handle) _FLOCK(p.handle); 3103 } 3104 } 3105 3106 /// Range primitive implementations. 3107 void put(A)(scope A writeme) 3108 if ((isSomeChar!(ElementType!A) || 3109 is(ElementType!A : const(ubyte))) && 3110 isInputRange!A && 3111 !isInfinite!A) 3112 { 3113 import std.exception : errnoEnforce; 3114 3115 alias C = ElementEncodingType!A; 3116 static assert(!is(C == void)); 3117 static if (isSomeString!A && C.sizeof == 1 || is(A : const(ubyte)[])) 3118 { 3119 if (orientation_ <= 0) 3120 { 3121 //file.write(writeme); causes infinite recursion!!! 3122 //file.rawWrite(writeme); 3123 auto result = trustedFwrite(file_._p.handle, writeme); 3124 if (result != writeme.length) errnoEnforce(0); 3125 return; 3126 } 3127 } 3128 3129 // put each element in turn. 3130 foreach (c; writeme) 3131 { 3132 put(c); 3133 } 3134 } 3135 3136 /// ditto 3137 void put(C)(scope C c) @safe 3138 if (isSomeChar!C || is(C : const(ubyte))) 3139 { 3140 import std.utf : decodeFront, encode, stride; 3141 3142 static if (c.sizeof == 1) 3143 { 3144 highSurrogateShouldBeEmpty(); 3145 if (orientation_ <= 0) trustedFPUTC(c, handle_); 3146 else if (c <= 0x7F) trustedFPUTWC(c, handle_); 3147 else if (c >= 0b1100_0000) // start byte of multibyte sequence 3148 { 3149 rbuf8[0] = c; 3150 rbuf8Filled = 1; 3151 } 3152 else // continuation byte of multibyte sequence 3153 { 3154 rbuf8[rbuf8Filled] = c; 3155 ++rbuf8Filled; 3156 if (stride(rbuf8[]) == rbuf8Filled) // sequence is complete 3157 { 3158 char[] str = rbuf8[0 .. rbuf8Filled]; 3159 immutable dchar d = decodeFront(str); 3160 wchar_t[4 / wchar_t.sizeof] wbuf; 3161 immutable size = encode(wbuf, d); 3162 foreach (i; 0 .. size) 3163 trustedFPUTWC(wbuf[i], handle_); 3164 rbuf8Filled = 0; 3165 } 3166 } 3167 } 3168 else static if (c.sizeof == 2) 3169 { 3170 import std.utf : decode; 3171 3172 if (c <= 0x7F) 3173 { 3174 highSurrogateShouldBeEmpty(); 3175 if (orientation_ <= 0) trustedFPUTC(c, handle_); 3176 else trustedFPUTWC(c, handle_); 3177 } 3178 else if (0xD800 <= c && c <= 0xDBFF) // high surrogate 3179 { 3180 highSurrogateShouldBeEmpty(); 3181 highSurrogate = c; 3182 } 3183 else // standalone or low surrogate 3184 { 3185 dchar d = c; 3186 if (highSurrogate != '\0') 3187 { 3188 immutable wchar[2] rbuf = [highSurrogate, c]; 3189 size_t index = 0; 3190 d = decode(rbuf[], index); 3191 highSurrogate = 0; 3192 } 3193 if (orientation_ <= 0) 3194 { 3195 char[4] wbuf; 3196 immutable size = encode(wbuf, d); 3197 foreach (i; 0 .. size) 3198 trustedFPUTC(wbuf[i], handle_); 3199 } 3200 else 3201 { 3202 wchar_t[4 / wchar_t.sizeof] wbuf; 3203 immutable size = encode(wbuf, d); 3204 foreach (i; 0 .. size) 3205 trustedFPUTWC(wbuf[i], handle_); 3206 } 3207 rbuf8Filled = 0; 3208 } 3209 } 3210 else // 32-bit characters 3211 { 3212 import std.utf : encode; 3213 3214 highSurrogateShouldBeEmpty(); 3215 if (orientation_ <= 0) 3216 { 3217 if (c <= 0x7F) 3218 { 3219 trustedFPUTC(c, handle_); 3220 } 3221 else 3222 { 3223 char[4] buf = void; 3224 immutable len = encode(buf, c); 3225 foreach (i ; 0 .. len) 3226 trustedFPUTC(buf[i], handle_); 3227 } 3228 } 3229 else 3230 { 3231 version (Windows) 3232 { 3233 import std.utf : isValidDchar; 3234 3235 assert(isValidDchar(c)); 3236 if (c <= 0xFFFF) 3237 { 3238 trustedFPUTWC(cast(wchar_t) c, handle_); 3239 } 3240 else 3241 { 3242 trustedFPUTWC(cast(wchar_t) 3243 ((((c - 0x10000) >> 10) & 0x3FF) 3244 + 0xD800), handle_); 3245 trustedFPUTWC(cast(wchar_t) 3246 (((c - 0x10000) & 0x3FF) + 0xDC00), 3247 handle_); 3248 } 3249 } 3250 else version (Posix) 3251 { 3252 trustedFPUTWC(cast(wchar_t) c, handle_); 3253 } 3254 else 3255 { 3256 static assert(0); 3257 } 3258 } 3259 } 3260 } 3261 } 3262 3263 /** 3264 * Output range which locks the file when created, and unlocks the file when it goes 3265 * out of scope. 3266 * 3267 * Returns: An $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) 3268 * which accepts string types, `ubyte[]`, individual character types, and 3269 * individual `ubyte`s. 3270 * 3271 * Note: Writing either arrays of `char`s or `ubyte`s is faster than 3272 * writing each character individually from a range. For large amounts of data, 3273 * writing the contents in chunks using an intermediary array can result 3274 * in a speed increase. 3275 * 3276 * Throws: $(REF UTFException, std, utf) if the data given is a `char` range 3277 * and it contains malformed UTF data. 3278 * 3279 * See_Also: $(LREF byChunk) for an example. 3280 */ 3281 auto lockingTextWriter() @safe 3282 { 3283 return LockingTextWriter(this); 3284 } 3285 3286 // An output range which optionally locks the file and puts it into 3287 // binary mode (similar to rawWrite). Because it needs to restore 3288 // the file mode on destruction, it is RefCounted on Windows. 3289 struct BinaryWriterImpl(bool locking) 3290 { 3291 import std.traits : hasIndirections; 3292 private: 3293 // Access the FILE* handle through the 'file_' member 3294 // to keep the object alive through refcounting 3295 File file_; 3296 string name; 3297 3298 version (Windows) 3299 { 3300 fileno_t fd; 3301 int oldMode; 3302 } 3303 3304 public: 3305 // Don't use this, but `File.lockingBinaryWriter()` instead. 3306 // Must be public for RefCounted and emplace() in druntime. 3307 this(scope ref File f) 3308 { 3309 import std.exception : enforce; 3310 file_ = f; 3311 enforce(f._p && f._p.handle); 3312 name = f._name; 3313 FILE* fps = f._p.handle; 3314 static if (locking) 3315 _FLOCK(fps); 3316 3317 version (Windows) 3318 { 3319 .fflush(fps); // before changing translation mode 3320 fd = .fileno(fps); 3321 oldMode = ._setmode(fd, _O_BINARY); 3322 } 3323 } 3324 3325 ~this() 3326 { 3327 if (!file_._p || !file_._p.handle) 3328 return; 3329 3330 FILE* fps = file_._p.handle; 3331 3332 version (Windows) 3333 { 3334 .fflush(fps); // before restoring translation mode 3335 ._setmode(fd, oldMode); 3336 } 3337 3338 _FUNLOCK(fps); 3339 } 3340 3341 void rawWrite(T)(in T[] buffer) 3342 { 3343 import std.conv : text; 3344 import std.exception : errnoEnforce; 3345 3346 auto result = trustedFwrite(file_._p.handle, buffer); 3347 if (result == result.max) result = 0; 3348 errnoEnforce(result == buffer.length, 3349 text("Wrote ", result, " instead of ", buffer.length, 3350 " objects of type ", T.stringof, " to file `", 3351 name, "'")); 3352 } 3353 3354 version (Windows) 3355 { 3356 @disable this(this); 3357 } 3358 else 3359 { 3360 this(this) 3361 { 3362 if (auto p = file_._p) 3363 { 3364 if (p.handle) _FLOCK(p.handle); 3365 } 3366 } 3367 } 3368 3369 void put(T)(auto ref scope const T value) 3370 if (!hasIndirections!T && 3371 !isInputRange!T) 3372 { 3373 rawWrite((&value)[0 .. 1]); 3374 } 3375 3376 void put(T)(scope const(T)[] array) 3377 if (!hasIndirections!T && 3378 !isInputRange!T) 3379 { 3380 rawWrite(array); 3381 } 3382 } 3383 3384 /** Returns an output range that locks the file and allows fast writing to it. 3385 3386 Example: 3387 Produce a grayscale image of the $(LINK2 https://en.wikipedia.org/wiki/Mandelbrot_set, Mandelbrot set) 3388 in binary $(LINK2 https://en.wikipedia.org/wiki/Netpbm_format, Netpbm format) to standard output. 3389 --- 3390 import std.algorithm, std.complex, std.range, std.stdio; 3391 3392 void main() 3393 { 3394 enum size = 500; 3395 writef("P5\n%d %d %d\n", size, size, ubyte.max); 3396 3397 iota(-1, 3, 2.0/size).map!(y => 3398 iota(-1.5, 0.5, 2.0/size).map!(x => 3399 cast(ubyte)(1+ 3400 recurrence!((a, n) => x + y * complex(0, 1) + a[n-1]^^2)(complex(0)) 3401 .take(ubyte.max) 3402 .countUntil!(z => z.re^^2 + z.im^^2 > 4)) 3403 ) 3404 ) 3405 .copy(stdout.lockingBinaryWriter); 3406 } 3407 --- 3408 */ 3409 auto lockingBinaryWriter() 3410 { 3411 alias LockingBinaryWriterImpl = BinaryWriterImpl!true; 3412 3413 version (Windows) 3414 { 3415 import std.typecons : RefCounted; 3416 alias LockingBinaryWriter = RefCounted!LockingBinaryWriterImpl; 3417 } 3418 else 3419 alias LockingBinaryWriter = LockingBinaryWriterImpl; 3420 3421 return LockingBinaryWriter(this); 3422 } 3423 3424 @system unittest 3425 { 3426 import std.algorithm.mutation : reverse; 3427 import std.exception : collectException; 3428 static import std.file; 3429 import std.range : only, retro; 3430 import std.string : format; 3431 3432 auto deleteme = testFilename(); 3433 scope(exit) collectException(std.file.remove(deleteme)); 3434 3435 { 3436 auto writer = File(deleteme, "wb").lockingBinaryWriter(); 3437 auto input = File(deleteme, "rb"); 3438 3439 ubyte[1] byteIn = [42]; 3440 writer.rawWrite(byteIn); 3441 destroy(writer); 3442 3443 ubyte[1] byteOut = input.rawRead(new ubyte[1]); 3444 assert(byteIn[0] == byteOut[0]); 3445 } 3446 3447 auto output = File(deleteme, "wb"); 3448 auto writer = output.lockingBinaryWriter(); 3449 auto input = File(deleteme, "rb"); 3450 3451 T[] readExact(T)(T[] buf) 3452 { 3453 auto result = input.rawRead(buf); 3454 assert(result.length == buf.length, 3455 "Read %d out of %d bytes" 3456 .format(result.length, buf.length)); 3457 return result; 3458 } 3459 3460 // test raw values 3461 ubyte byteIn = 42; 3462 byteIn.only.copy(writer); output.flush(); 3463 ubyte byteOut = readExact(new ubyte[1])[0]; 3464 assert(byteIn == byteOut); 3465 3466 // test arrays 3467 ubyte[] bytesIn = [1, 2, 3, 4, 5]; 3468 bytesIn.copy(writer); output.flush(); 3469 ubyte[] bytesOut = readExact(new ubyte[bytesIn.length]); 3470 scope(failure) .writeln(bytesOut); 3471 assert(bytesIn == bytesOut); 3472 3473 // test ranges of values 3474 bytesIn.retro.copy(writer); output.flush(); 3475 bytesOut = readExact(bytesOut); 3476 bytesOut.reverse(); 3477 assert(bytesIn == bytesOut); 3478 3479 // test string 3480 "foobar".copy(writer); output.flush(); 3481 char[] charsOut = readExact(new char[6]); 3482 assert(charsOut == "foobar"); 3483 3484 // test ranges of arrays 3485 only("foo", "bar").copy(writer); output.flush(); 3486 charsOut = readExact(charsOut); 3487 assert(charsOut == "foobar"); 3488 3489 // test that we are writing arrays as is, 3490 // without UTF-8 transcoding 3491 "foo"d.copy(writer); output.flush(); 3492 dchar[] dcharsOut = readExact(new dchar[3]); 3493 assert(dcharsOut == "foo"); 3494 } 3495 3496 /** Returns the size of the file in bytes, ulong.max if file is not searchable or throws if the operation fails. 3497 Example: 3498 --- 3499 import std.stdio, std.file; 3500 3501 void main() 3502 { 3503 string deleteme = "delete.me"; 3504 auto file_handle = File(deleteme, "w"); 3505 file_handle.write("abc"); //create temporary file 3506 scope(exit) deleteme.remove; //remove temporary file at scope exit 3507 3508 assert(file_handle.size() == 3); //check if file size is 3 bytes 3509 } 3510 --- 3511 */ 3512 @property ulong size() @safe 3513 { 3514 import std.exception : collectException; 3515 3516 ulong pos = void; 3517 if (collectException(pos = tell)) return ulong.max; 3518 scope(exit) seek(pos); 3519 seek(0, SEEK_END); 3520 return tell; 3521 } 3522 } 3523 3524 @system unittest 3525 { 3526 @system struct SystemToString 3527 { 3528 string toString() 3529 { 3530 return "system"; 3531 } 3532 } 3533 3534 @trusted struct TrustedToString 3535 { 3536 string toString() 3537 { 3538 return "trusted"; 3539 } 3540 } 3541 3542 @safe struct SafeToString 3543 { 3544 string toString() 3545 { 3546 return "safe"; 3547 } 3548 } 3549 3550 @system void systemTests() 3551 { 3552 //system code can write to files/stdout with anything! 3553 if (false) 3554 { 3555 auto f = File(); 3556 3557 f.write("just a string"); 3558 f.write("string with arg: ", 47); 3559 f.write(SystemToString()); 3560 f.write(TrustedToString()); 3561 f.write(SafeToString()); 3562 3563 write("just a string"); 3564 write("string with arg: ", 47); 3565 write(SystemToString()); 3566 write(TrustedToString()); 3567 write(SafeToString()); 3568 3569 f.writeln("just a string"); 3570 f.writeln("string with arg: ", 47); 3571 f.writeln(SystemToString()); 3572 f.writeln(TrustedToString()); 3573 f.writeln(SafeToString()); 3574 3575 writeln("just a string"); 3576 writeln("string with arg: ", 47); 3577 writeln(SystemToString()); 3578 writeln(TrustedToString()); 3579 writeln(SafeToString()); 3580 3581 f.writef("string with arg: %s", 47); 3582 f.writef("%s", SystemToString()); 3583 f.writef("%s", TrustedToString()); 3584 f.writef("%s", SafeToString()); 3585 3586 writef("string with arg: %s", 47); 3587 writef("%s", SystemToString()); 3588 writef("%s", TrustedToString()); 3589 writef("%s", SafeToString()); 3590 3591 f.writefln("string with arg: %s", 47); 3592 f.writefln("%s", SystemToString()); 3593 f.writefln("%s", TrustedToString()); 3594 f.writefln("%s", SafeToString()); 3595 3596 writefln("string with arg: %s", 47); 3597 writefln("%s", SystemToString()); 3598 writefln("%s", TrustedToString()); 3599 writefln("%s", SafeToString()); 3600 } 3601 } 3602 3603 @safe void safeTests() 3604 { 3605 auto f = File(); 3606 3607 //safe code can write to files only with @safe and @trusted code... 3608 if (false) 3609 { 3610 f.write("just a string"); 3611 f.write("string with arg: ", 47); 3612 f.write(TrustedToString()); 3613 f.write(SafeToString()); 3614 3615 write("just a string"); 3616 write("string with arg: ", 47); 3617 write(TrustedToString()); 3618 write(SafeToString()); 3619 3620 f.writeln("just a string"); 3621 f.writeln("string with arg: ", 47); 3622 f.writeln(TrustedToString()); 3623 f.writeln(SafeToString()); 3624 3625 writeln("just a string"); 3626 writeln("string with arg: ", 47); 3627 writeln(TrustedToString()); 3628 writeln(SafeToString()); 3629 3630 f.writef("string with arg: %s", 47); 3631 f.writef("%s", TrustedToString()); 3632 f.writef("%s", SafeToString()); 3633 3634 writef("string with arg: %s", 47); 3635 writef("%s", TrustedToString()); 3636 writef("%s", SafeToString()); 3637 3638 f.writefln("string with arg: %s", 47); 3639 f.writefln("%s", TrustedToString()); 3640 f.writefln("%s", SafeToString()); 3641 3642 writefln("string with arg: %s", 47); 3643 writefln("%s", TrustedToString()); 3644 writefln("%s", SafeToString()); 3645 } 3646 3647 static assert(!__traits(compiles, f.write(SystemToString().toString()))); 3648 static assert(!__traits(compiles, f.writeln(SystemToString()))); 3649 static assert(!__traits(compiles, f.writef("%s", SystemToString()))); 3650 static assert(!__traits(compiles, f.writefln("%s", SystemToString()))); 3651 3652 static assert(!__traits(compiles, write(SystemToString().toString()))); 3653 static assert(!__traits(compiles, writeln(SystemToString()))); 3654 static assert(!__traits(compiles, writef("%s", SystemToString()))); 3655 static assert(!__traits(compiles, writefln("%s", SystemToString()))); 3656 } 3657 3658 systemTests(); 3659 safeTests(); 3660 } 3661 3662 @safe unittest 3663 { 3664 import std.exception : collectException; 3665 static import std.file; 3666 3667 auto deleteme = testFilename(); 3668 scope(exit) collectException(std.file.remove(deleteme)); 3669 std.file.write(deleteme, "1 2 3"); 3670 auto f = File(deleteme); 3671 assert(f.size == 5); 3672 assert(f.tell == 0); 3673 } 3674 3675 @safe unittest 3676 { 3677 static import std.file; 3678 import std.range : chain, only, repeat; 3679 import std.range.primitives : isOutputRange; 3680 3681 auto deleteme = testFilename(); 3682 scope(exit) std.file.remove(deleteme); 3683 3684 { 3685 auto writer = File(deleteme, "w").lockingTextWriter(); 3686 static assert(isOutputRange!(typeof(writer), dchar)); 3687 writer.put("日本語"); 3688 writer.put("日本語"w); 3689 writer.put("日本語"d); 3690 writer.put('日'); 3691 writer.put(chain(only('本'), only('語'))); 3692 // https://issues.dlang.org/show_bug.cgi?id=11945 3693 writer.put(repeat('#', 12)); 3694 // https://issues.dlang.org/show_bug.cgi?id=17229 3695 writer.put(cast(immutable(ubyte)[])"日本語"); 3696 } 3697 assert(File(deleteme).readln() == "日本語日本語日本語日本語############日本語"); 3698 } 3699 3700 @safe unittest // wchar -> char 3701 { 3702 static import std.file; 3703 import std.exception : assertThrown; 3704 import std.utf : UTFException; 3705 3706 auto deleteme = testFilename(); 3707 scope(exit) std.file.remove(deleteme); 3708 3709 { 3710 auto writer = File(deleteme, "w").lockingTextWriter(); 3711 writer.put("\U0001F608"w); 3712 } 3713 assert(std.file.readText!string(deleteme) == "\U0001F608"); 3714 3715 // Test invalid input: unpaired high surrogate 3716 { 3717 immutable wchar surr = "\U0001F608"w[0]; 3718 auto f = File(deleteme, "w"); 3719 assertThrown!UTFException(() { 3720 auto writer = f.lockingTextWriter(); 3721 writer.put('x'); 3722 writer.put(surr); 3723 assertThrown!UTFException(writer.put(char('y'))); 3724 assertThrown!UTFException(writer.put(wchar('y'))); 3725 assertThrown!UTFException(writer.put(dchar('y'))); 3726 assertThrown!UTFException(writer.put(surr)); 3727 // First `surr` is still unpaired at this point. `writer` gets 3728 // destroyed now, and the destructor throws a UTFException for 3729 // the unpaired surrogate. 3730 } ()); 3731 } 3732 assert(std.file.readText!string(deleteme) == "x"); 3733 3734 // Test invalid input: unpaired low surrogate 3735 { 3736 immutable wchar surr = "\U0001F608"w[1]; 3737 auto writer = File(deleteme, "w").lockingTextWriter(); 3738 assertThrown!UTFException(writer.put(surr)); 3739 writer.put('y'); 3740 assertThrown!UTFException(writer.put(surr)); 3741 } 3742 assert(std.file.readText!string(deleteme) == "y"); 3743 } 3744 3745 @safe unittest // https://issues.dlang.org/show_bug.cgi?id=18801 3746 { 3747 static import std.file; 3748 import std.string : stripLeft; 3749 3750 auto deleteme = testFilename(); 3751 scope(exit) std.file.remove(deleteme); 3752 3753 { 3754 auto writer = File(deleteme, "w,ccs=UTF-8").lockingTextWriter(); 3755 writer.put("foo"); 3756 } 3757 assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foo"); 3758 3759 { 3760 auto writer = File(deleteme, "a,ccs=UTF-8").lockingTextWriter(); 3761 writer.put("bar"); 3762 } 3763 assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foobar"); 3764 } 3765 @safe unittest // char/wchar -> wchar_t 3766 { 3767 import core.stdc.locale : LC_CTYPE, setlocale; 3768 import core.stdc.wchar_ : fwide; 3769 import core.stdc.string : strlen; 3770 import std.algorithm.searching : any, endsWith; 3771 import std.conv : text; 3772 import std.meta : AliasSeq; 3773 import std.string : fromStringz, stripLeft; 3774 static import std.file; 3775 auto deleteme = testFilename(); 3776 scope(exit) std.file.remove(deleteme); 3777 const char* oldCt = () @trusted { 3778 const(char)* p = setlocale(LC_CTYPE, null); 3779 // Subsequent calls to `setlocale` might invalidate this return value, 3780 // so duplicate it. 3781 // See: https://github.com/dlang/phobos/pull/7660 3782 return p ? p[0 .. strlen(p) + 1].idup.ptr : null; 3783 }(); 3784 const utf8 = ["en_US.UTF-8", "C.UTF-8", ".65001"].any!((loc) @trusted { 3785 return setlocale(LC_CTYPE, loc.ptr).fromStringz.endsWith(loc); 3786 }); 3787 scope(exit) () @trusted { setlocale(LC_CTYPE, oldCt); } (); 3788 alias strs = AliasSeq!("xä\U0001F607", "yö\U0001F608"w); 3789 { 3790 auto f = File(deleteme, "w"); 3791 version (CRuntime_Microsoft) 3792 { 3793 () @trusted { _setmode(fileno(f.getFP()), _O_U8TEXT); } (); 3794 } 3795 else 3796 { 3797 assert(fwide(f.getFP(), 1) == 1); 3798 } 3799 auto writer = f.lockingTextWriter(); 3800 assert(writer.orientation_ == 1); 3801 static foreach (s; strs) writer.put(s); 3802 } 3803 assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == 3804 text(strs)); 3805 } 3806 @safe unittest // https://issues.dlang.org/show_bug.cgi?id=18789 3807 { 3808 static import std.file; 3809 auto deleteme = testFilename(); 3810 scope(exit) std.file.remove(deleteme); 3811 // converting to char 3812 { 3813 auto f = File(deleteme, "w"); 3814 f.writeln("\U0001F608"w); // UTFException 3815 } 3816 // converting to wchar_t 3817 { 3818 auto f = File(deleteme, "w,ccs=UTF-16LE"); 3819 // from char 3820 f.writeln("ö"); // writes garbage 3821 f.writeln("\U0001F608"); // ditto 3822 // from wchar 3823 f.writeln("\U0001F608"w); // leads to ErrnoException 3824 } 3825 } 3826 3827 @safe unittest 3828 { 3829 import std.exception : collectException; 3830 auto e = collectException({ File f; f.writeln("Hello!"); }()); 3831 assert(e && e.msg == "Attempting to write to closed File"); 3832 } 3833 3834 @safe unittest // https://issues.dlang.org/show_bug.cgi?id=21592 3835 { 3836 import std.exception : collectException; 3837 import std.utf : UTFException; 3838 static import std.file; 3839 auto deleteme = testFilename(); 3840 scope(exit) std.file.remove(deleteme); 3841 auto f = File(deleteme, "w"); 3842 auto e = collectException!UTFException(f.writeln(wchar(0xD801))); 3843 assert(e.next is null); 3844 } 3845 3846 version (StdStressTest) 3847 { 3848 // https://issues.dlang.org/show_bug.cgi?id=15768 3849 @system unittest 3850 { 3851 import std.parallelism : parallel; 3852 import std.range : iota; 3853 3854 auto deleteme = testFilename(); 3855 stderr = File(deleteme, "w"); 3856 3857 foreach (t; 1_000_000.iota.parallel) 3858 { 3859 stderr.write("aaa"); 3860 } 3861 } 3862 } 3863 3864 /// Used to specify the lock type for `File.lock` and `File.tryLock`. 3865 enum LockType 3866 { 3867 /** 3868 * Specifies a _read (shared) lock. A _read lock denies all processes 3869 * write access to the specified region of the file, including the 3870 * process that first locks the region. All processes can _read the 3871 * locked region. Multiple simultaneous _read locks are allowed, as 3872 * long as there are no exclusive locks. 3873 */ 3874 read, 3875 3876 /** 3877 * Specifies a read/write (exclusive) lock. A read/write lock denies all 3878 * other processes both read and write access to the locked file region. 3879 * If a segment has an exclusive lock, it may not have any shared locks 3880 * or other exclusive locks. 3881 */ 3882 readWrite 3883 } 3884 3885 struct LockingTextReader 3886 { 3887 private File _f; 3888 private char _front; 3889 private bool _hasChar; 3890 3891 this(File f) 3892 { 3893 import std.exception : enforce; 3894 enforce(f.isOpen, "LockingTextReader: File must be open"); 3895 _f = f; 3896 _FLOCK(_f._p.handle); 3897 } 3898 3899 this(this) 3900 { 3901 _FLOCK(_f._p.handle); 3902 } 3903 3904 ~this() 3905 { 3906 if (_hasChar) 3907 ungetc(_front, cast(FILE*)_f._p.handle); 3908 3909 // File locking has its own reference count 3910 if (_f.isOpen) _FUNLOCK(_f._p.handle); 3911 } 3912 3913 void opAssign(LockingTextReader r) 3914 { 3915 import std.algorithm.mutation : swap; 3916 swap(this, r); 3917 } 3918 3919 @property bool empty() 3920 { 3921 if (!_hasChar) 3922 { 3923 if (!_f.isOpen || _f.eof) 3924 return true; 3925 immutable int c = _FGETC(cast(_iobuf*) _f._p.handle); 3926 if (c == EOF) 3927 { 3928 .destroy(_f); 3929 return true; 3930 } 3931 _front = cast(char) c; 3932 _hasChar = true; 3933 } 3934 return false; 3935 } 3936 3937 @property char front() 3938 { 3939 if (!_hasChar) 3940 { 3941 version (assert) 3942 { 3943 import core.exception : RangeError; 3944 if (empty) 3945 throw new RangeError(); 3946 } 3947 else 3948 { 3949 empty; 3950 } 3951 } 3952 return _front; 3953 } 3954 3955 void popFront() 3956 { 3957 if (!_hasChar) 3958 empty; 3959 _hasChar = false; 3960 } 3961 } 3962 3963 @system unittest 3964 { 3965 // @system due to readf 3966 static import std.file; 3967 import std.range.primitives : isInputRange; 3968 3969 static assert(isInputRange!LockingTextReader); 3970 auto deleteme = testFilename(); 3971 std.file.write(deleteme, "1 2 3"); 3972 scope(exit) std.file.remove(deleteme); 3973 int x; 3974 auto f = File(deleteme); 3975 f.readf("%s ", &x); 3976 assert(x == 1); 3977 f.readf("%d ", &x); 3978 assert(x == 2); 3979 f.readf("%d ", &x); 3980 assert(x == 3); 3981 } 3982 3983 // https://issues.dlang.org/show_bug.cgi?id=13686 3984 @system unittest 3985 { 3986 import std.algorithm.comparison : equal; 3987 static import std.file; 3988 import std.utf : byDchar; 3989 3990 auto deleteme = testFilename(); 3991 std.file.write(deleteme, "Тест"); 3992 scope(exit) std.file.remove(deleteme); 3993 3994 string s; 3995 File(deleteme).readf("%s", &s); 3996 assert(s == "Тест"); 3997 3998 auto ltr = LockingTextReader(File(deleteme)).byDchar; 3999 assert(equal(ltr, "Тест".byDchar)); 4000 } 4001 4002 // https://issues.dlang.org/show_bug.cgi?id=12320 4003 @system unittest 4004 { 4005 static import std.file; 4006 auto deleteme = testFilename(); 4007 std.file.write(deleteme, "ab"); 4008 scope(exit) std.file.remove(deleteme); 4009 auto ltr = LockingTextReader(File(deleteme)); 4010 assert(ltr.front == 'a'); 4011 ltr.popFront(); 4012 assert(ltr.front == 'b'); 4013 ltr.popFront(); 4014 assert(ltr.empty); 4015 } 4016 4017 // https://issues.dlang.org/show_bug.cgi?id=14861 4018 @system unittest 4019 { 4020 // @system due to readf 4021 static import std.file; 4022 auto deleteme = testFilename(); 4023 File fw = File(deleteme, "w"); 4024 for (int i; i != 5000; i++) 4025 fw.writeln(i, ";", "Иванов;Пётр;Петрович"); 4026 fw.close(); 4027 scope(exit) std.file.remove(deleteme); 4028 // Test read 4029 File fr = File(deleteme, "r"); 4030 scope (exit) fr.close(); 4031 int nom; string fam, nam, ot; 4032 // Error format read 4033 while (!fr.eof) 4034 fr.readf("%s;%s;%s;%s\n", &nom, &fam, &nam, &ot); 4035 } 4036 4037 /** 4038 * Indicates whether `T` is a file handle, i.e. the type 4039 * is implicitly convertable to $(LREF File) or a pointer to a 4040 * $(REF FILE, core,stdc,stdio). 4041 * 4042 * Returns: 4043 * `true` if `T` is a file handle, `false` otherwise. 4044 */ 4045 template isFileHandle(T) 4046 { 4047 enum isFileHandle = is(T : FILE*) || 4048 is(T : File); 4049 } 4050 4051 /// 4052 @safe unittest 4053 { 4054 static assert(isFileHandle!(FILE*)); 4055 static assert(isFileHandle!(File)); 4056 } 4057 4058 /** 4059 * Property used by writeln/etc. so it can infer @safe since stdout is __gshared 4060 */ 4061 private @property File trustedStdout() @trusted 4062 { 4063 return stdout; 4064 } 4065 4066 /*********************************** 4067 Writes its arguments in text format to standard output (without a trailing newline). 4068 4069 Params: 4070 args = the items to write to `stdout` 4071 4072 Throws: In case of an I/O error, throws an `StdioException`. 4073 4074 Example: 4075 Reads `stdin` and writes it to `stdout` with an argument 4076 counter. 4077 --- 4078 import std.stdio; 4079 4080 void main() 4081 { 4082 string line; 4083 4084 for (size_t count = 0; (line = readln) !is null; count++) 4085 { 4086 write("Input ", count, ": ", line, "\n"); 4087 } 4088 } 4089 --- 4090 */ 4091 void write(T...)(T args) 4092 if (!is(T[0] : File)) 4093 { 4094 trustedStdout.write(args); 4095 } 4096 4097 @system unittest 4098 { 4099 static import std.file; 4100 4101 scope(failure) printf("Failed test at line %d\n", __LINE__); 4102 void[] buf; 4103 if (false) write(buf); 4104 // test write 4105 auto deleteme = testFilename(); 4106 auto f = File(deleteme, "w"); 4107 f.write("Hello, ", "world number ", 42, "!"); 4108 f.close(); 4109 scope(exit) { std.file.remove(deleteme); } 4110 assert(cast(char[]) std.file.read(deleteme) == "Hello, world number 42!"); 4111 } 4112 4113 /*********************************** 4114 * Equivalent to `write(args, '\n')`. Calling `writeln` without 4115 * arguments is valid and just prints a newline to the standard 4116 * output. 4117 * 4118 * Params: 4119 * args = the items to write to `stdout` 4120 * 4121 * Throws: 4122 * In case of an I/O error, throws an $(LREF StdioException). 4123 * Example: 4124 * Reads `stdin` and writes it to `stdout` with an argument 4125 * counter. 4126 --- 4127 import std.stdio; 4128 4129 void main() 4130 { 4131 string line; 4132 4133 for (size_t count = 0; (line = readln) !is null; count++) 4134 { 4135 writeln("Input ", count, ": ", line); 4136 } 4137 } 4138 --- 4139 */ 4140 void writeln(T...)(T args) 4141 { 4142 static if (T.length == 0) 4143 { 4144 import std.exception : enforce; 4145 4146 enforce(fputc('\n', .trustedStdout._p.handle) != EOF, "fputc failed"); 4147 } 4148 else static if (T.length == 1 && 4149 is(T[0] : const(char)[]) && 4150 (is(T[0] == U[], U) || __traits(isStaticArray, T[0]))) 4151 { 4152 // Specialization for strings - a very frequent case 4153 auto w = .trustedStdout.lockingTextWriter(); 4154 4155 static if (__traits(isStaticArray, T[0])) 4156 { 4157 w.put(args[0][]); 4158 } 4159 else 4160 { 4161 w.put(args[0]); 4162 } 4163 w.put('\n'); 4164 } 4165 else 4166 { 4167 // Most general instance 4168 trustedStdout.write(args, '\n'); 4169 } 4170 } 4171 4172 @safe unittest 4173 { 4174 // Just make sure the call compiles 4175 if (false) writeln(); 4176 4177 if (false) writeln("wyda"); 4178 4179 // https://issues.dlang.org/show_bug.cgi?id=8040 4180 if (false) writeln(null); 4181 if (false) writeln(">", null, "<"); 4182 4183 // https://issues.dlang.org/show_bug.cgi?id=14041 4184 if (false) 4185 { 4186 char[8] a; 4187 writeln(a); 4188 immutable b = a; 4189 b.writeln; 4190 const c = a[]; 4191 c.writeln; 4192 } 4193 } 4194 4195 @system unittest 4196 { 4197 static import std.file; 4198 4199 scope(failure) printf("Failed test at line %d\n", __LINE__); 4200 4201 // test writeln 4202 auto deleteme = testFilename(); 4203 auto f = File(deleteme, "w"); 4204 scope(exit) { std.file.remove(deleteme); } 4205 f.writeln("Hello, ", "world number ", 42, "!"); 4206 f.close(); 4207 version (Windows) 4208 assert(cast(char[]) std.file.read(deleteme) == 4209 "Hello, world number 42!\r\n"); 4210 else 4211 assert(cast(char[]) std.file.read(deleteme) == 4212 "Hello, world number 42!\n"); 4213 4214 // test writeln on stdout 4215 auto saveStdout = stdout; 4216 scope(exit) stdout = saveStdout; 4217 stdout.open(deleteme, "w"); 4218 writeln("Hello, ", "world number ", 42, "!"); 4219 stdout.close(); 4220 version (Windows) 4221 assert(cast(char[]) std.file.read(deleteme) == 4222 "Hello, world number 42!\r\n"); 4223 else 4224 assert(cast(char[]) std.file.read(deleteme) == 4225 "Hello, world number 42!\n"); 4226 4227 stdout.open(deleteme, "w"); 4228 writeln("Hello!"c); 4229 writeln("Hello!"w); // https://issues.dlang.org/show_bug.cgi?id=8386 4230 writeln("Hello!"d); // https://issues.dlang.org/show_bug.cgi?id=8386 4231 writeln("embedded\0null"c); // https://issues.dlang.org/show_bug.cgi?id=8730 4232 stdout.close(); 4233 version (Windows) 4234 assert(cast(char[]) std.file.read(deleteme) == 4235 "Hello!\r\nHello!\r\nHello!\r\nembedded\0null\r\n"); 4236 else 4237 assert(cast(char[]) std.file.read(deleteme) == 4238 "Hello!\nHello!\nHello!\nembedded\0null\n"); 4239 } 4240 4241 @system unittest 4242 { 4243 static import std.file; 4244 4245 auto deleteme = testFilename(); 4246 auto f = File(deleteme, "w"); 4247 scope(exit) { std.file.remove(deleteme); } 4248 4249 enum EI : int { A, B } 4250 enum ED : double { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle 4251 enum EC : char { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle 4252 enum ES : string { A = "aaa", B = "bbb" } 4253 4254 f.writeln(EI.A); // false, but A on 2.058 4255 f.writeln(EI.B); // true, but B on 2.058 4256 4257 f.writeln(ED.A); // A 4258 f.writeln(ED.B); // B 4259 4260 f.writeln(EC.A); // A 4261 f.writeln(EC.B); // B 4262 4263 f.writeln(ES.A); // A 4264 f.writeln(ES.B); // B 4265 4266 f.close(); 4267 version (Windows) 4268 assert(cast(char[]) std.file.read(deleteme) == 4269 "A\r\nB\r\nA\r\nB\r\nA\r\nB\r\nA\r\nB\r\n"); 4270 else 4271 assert(cast(char[]) std.file.read(deleteme) == 4272 "A\nB\nA\nB\nA\nB\nA\nB\n"); 4273 } 4274 4275 @system unittest 4276 { 4277 static auto useInit(T)(T ltw) 4278 { 4279 T val; 4280 val = ltw; 4281 val = T.init; 4282 return val; 4283 } 4284 useInit(stdout.lockingTextWriter()); 4285 } 4286 4287 @system unittest 4288 { 4289 // https://issues.dlang.org/show_bug.cgi?id=21920 4290 void function(string) printer = &writeln!string; 4291 if (false) printer("Hello"); 4292 } 4293 4294 4295 /*********************************** 4296 Writes formatted data to standard output (without a trailing newline). 4297 4298 Params: 4299 fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format). 4300 When passed as a compile-time argument, the string will be statically checked 4301 against the argument types passed. 4302 args = Items to write. 4303 4304 Note: In older versions of Phobos, it used to be possible to write: 4305 4306 ------ 4307 writef(stderr, "%s", "message"); 4308 ------ 4309 4310 to print a message to `stderr`. This syntax is no longer supported, and has 4311 been superceded by: 4312 4313 ------ 4314 stderr.writef("%s", "message"); 4315 ------ 4316 4317 */ 4318 void writef(alias fmt, A...)(A args) 4319 if (isSomeString!(typeof(fmt))) 4320 { 4321 import std.format : checkFormatException; 4322 4323 alias e = checkFormatException!(fmt, A); 4324 static assert(!e, e); 4325 return .writef(fmt, args); 4326 } 4327 4328 /// ditto 4329 void writef(Char, A...)(in Char[] fmt, A args) 4330 { 4331 trustedStdout.writef(fmt, args); 4332 } 4333 4334 @system unittest 4335 { 4336 static import std.file; 4337 4338 scope(failure) printf("Failed test at line %d\n", __LINE__); 4339 4340 // test writef 4341 auto deleteme = testFilename(); 4342 auto f = File(deleteme, "w"); 4343 scope(exit) { std.file.remove(deleteme); } 4344 f.writef!"Hello, %s world number %s!"("nice", 42); 4345 f.close(); 4346 assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); 4347 // test write on stdout 4348 auto saveStdout = stdout; 4349 scope(exit) stdout = saveStdout; 4350 stdout.open(deleteme, "w"); 4351 writef!"Hello, %s world number %s!"("nice", 42); 4352 stdout.close(); 4353 assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); 4354 } 4355 4356 /*********************************** 4357 * Equivalent to $(D writef(fmt, args, '\n')). 4358 */ 4359 void writefln(alias fmt, A...)(A args) 4360 if (isSomeString!(typeof(fmt))) 4361 { 4362 import std.format : checkFormatException; 4363 4364 alias e = checkFormatException!(fmt, A); 4365 static assert(!e, e); 4366 return .writefln(fmt, args); 4367 } 4368 4369 /// ditto 4370 void writefln(Char, A...)(in Char[] fmt, A args) 4371 { 4372 trustedStdout.writefln(fmt, args); 4373 } 4374 4375 @system unittest 4376 { 4377 static import std.file; 4378 4379 scope(failure) printf("Failed test at line %d\n", __LINE__); 4380 4381 // test File.writefln 4382 auto deleteme = testFilename(); 4383 auto f = File(deleteme, "w"); 4384 scope(exit) { std.file.remove(deleteme); } 4385 f.writefln!"Hello, %s world number %s!"("nice", 42); 4386 f.close(); 4387 version (Windows) 4388 assert(cast(char[]) std.file.read(deleteme) == 4389 "Hello, nice world number 42!\r\n"); 4390 else 4391 assert(cast(char[]) std.file.read(deleteme) == 4392 "Hello, nice world number 42!\n", 4393 cast(char[]) std.file.read(deleteme)); 4394 4395 // test writefln 4396 auto saveStdout = stdout; 4397 scope(exit) stdout = saveStdout; 4398 stdout.open(deleteme, "w"); 4399 writefln!"Hello, %s world number %s!"("nice", 42); 4400 stdout.close(); 4401 version (Windows) 4402 assert(cast(char[]) std.file.read(deleteme) == 4403 "Hello, nice world number 42!\r\n"); 4404 else 4405 assert(cast(char[]) std.file.read(deleteme) == 4406 "Hello, nice world number 42!\n"); 4407 } 4408 4409 /** 4410 * Reads formatted data from `stdin` using $(REF formattedRead, std,_format). 4411 * Params: 4412 * format = The $(REF_ALTTEXT format string, formattedWrite, std, _format). 4413 * When passed as a compile-time argument, the string will be statically checked 4414 * against the argument types passed. 4415 * args = Items to be read. 4416 * Returns: 4417 * Same as `formattedRead`: The number of variables filled. If the input range `r` ends early, 4418 * this number will be less than the number of variables provided. 4419 * Example: 4420 ---- 4421 // test.d 4422 void main() 4423 { 4424 import std.stdio; 4425 foreach (_; 0 .. 3) 4426 { 4427 int a; 4428 readf!" %d"(a); 4429 writeln(++a); 4430 } 4431 } 4432 ---- 4433 $(CONSOLE 4434 % echo "1 2 3" | rdmd test.d 4435 2 4436 3 4437 4 4438 ) 4439 */ 4440 uint readf(alias format, A...)(auto ref A args) 4441 if (isSomeString!(typeof(format))) 4442 { 4443 import std.format : checkFormatException; 4444 4445 alias e = checkFormatException!(format, A); 4446 static assert(!e, e); 4447 return .readf(format, args); 4448 } 4449 4450 /// ditto 4451 uint readf(A...)(scope const(char)[] format, auto ref A args) 4452 { 4453 return stdin.readf(format, args); 4454 } 4455 4456 @system unittest 4457 { 4458 float f; 4459 if (false) readf("%s", &f); 4460 4461 char a; 4462 wchar b; 4463 dchar c; 4464 if (false) readf("%s %s %s", a, b, c); 4465 // backwards compatibility with pointers 4466 if (false) readf("%s %s %s", a, &b, c); 4467 if (false) readf("%s %s %s", &a, &b, &c); 4468 } 4469 4470 /********************************** 4471 * Read line from `stdin`. 4472 * 4473 * This version manages its own read buffer, which means one memory allocation per call. If you are not 4474 * retaining a reference to the read data, consider the `readln(buf)` version, which may offer 4475 * better performance as it can reuse its read buffer. 4476 * 4477 * Returns: 4478 * The line that was read, including the line terminator character. 4479 * Params: 4480 * S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`. 4481 * terminator = Line terminator (by default, `'\n'`). 4482 * Note: 4483 * String terminators are not supported due to ambiguity with readln(buf) below. 4484 * Throws: 4485 * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. 4486 * Example: 4487 * Reads `stdin` and writes it to `stdout`. 4488 --- 4489 import std.stdio; 4490 4491 void main() 4492 { 4493 string line; 4494 while ((line = readln()) !is null) 4495 write(line); 4496 } 4497 --- 4498 */ 4499 S readln(S = string)(dchar terminator = '\n') 4500 if (isSomeString!S) 4501 { 4502 return stdin.readln!S(terminator); 4503 } 4504 4505 /********************************** 4506 * Read line from `stdin` and write it to buf[], including terminating character. 4507 * 4508 * This can be faster than $(D line = readln()) because you can reuse 4509 * the buffer for each call. Note that reusing the buffer means that you 4510 * must copy the previous contents if you wish to retain them. 4511 * 4512 * Returns: 4513 * `size_t` 0 for end of file, otherwise number of characters read 4514 * Params: 4515 * buf = Buffer used to store the resulting line data. buf is resized as necessary. 4516 * terminator = Line terminator (by default, `'\n'`). Use $(REF newline, std,ascii) 4517 * for portability (unless the file was opened in text mode). 4518 * Throws: 4519 * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. 4520 * Example: 4521 * Reads `stdin` and writes it to `stdout`. 4522 --- 4523 import std.stdio; 4524 4525 void main() 4526 { 4527 char[] buf; 4528 while (readln(buf)) 4529 write(buf); 4530 } 4531 --- 4532 */ 4533 size_t readln(C)(ref C[] buf, dchar terminator = '\n') 4534 if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) 4535 { 4536 return stdin.readln(buf, terminator); 4537 } 4538 4539 /** ditto */ 4540 size_t readln(C, R)(ref C[] buf, R terminator) 4541 if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && 4542 isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) 4543 { 4544 return stdin.readln(buf, terminator); 4545 } 4546 4547 @safe unittest 4548 { 4549 import std.meta : AliasSeq; 4550 4551 //we can't actually test readln, so at the very least, 4552 //we test compilability 4553 void foo() 4554 { 4555 readln(); 4556 readln('\t'); 4557 static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) 4558 { 4559 readln!String(); 4560 readln!String('\t'); 4561 } 4562 static foreach (String; AliasSeq!(char[], wchar[], dchar[])) 4563 {{ 4564 String buf; 4565 readln(buf); 4566 readln(buf, '\t'); 4567 readln(buf, "<br />"); 4568 }} 4569 } 4570 } 4571 4572 /** 4573 Reads a line from `stdin` and parses it using $(REF formattedRead, std,format,read). 4574 4575 Params: 4576 format = The $(MREF_ALTTEXT format string, std,format). When passed as a 4577 compile-time argument, the string will be statically checked against the 4578 argument types passed. 4579 data = Items to be read. 4580 4581 Returns: Same as `formattedRead`: the number of variables filled. If the 4582 input ends early, this number will be less that the number of variables 4583 provided. 4584 4585 Example: 4586 --- 4587 // sum_rows.d 4588 void main() 4589 { 4590 import std.stdio; 4591 int a, b, c; 4592 while (readfln("%d %d %d", a, b, c) == 3) 4593 { 4594 writeln(a + b + c); 4595 } 4596 } 4597 --- 4598 $(CONSOLE 4599 % cat << EOF > input 4600 1 2 3 4601 4 5 6 4602 7 8 9 4603 EOF 4604 % rdmd sum_rows.d < input 4605 6 4606 15 4607 24 4608 ) 4609 */ 4610 uint readfln(alias format, Data...)(auto ref Data data) 4611 { 4612 import std.format : checkFormatException; 4613 4614 alias e = checkFormatException!(format, Data); 4615 static assert(!e, e); 4616 return .readfln(format, data); 4617 } 4618 4619 /// ditto 4620 uint readfln(Data...)(scope const(char)[] format, auto ref Data data) 4621 { 4622 return stdin.readfln(format, data); 4623 } 4624 4625 @system unittest 4626 { 4627 float f; 4628 string s; 4629 char c; 4630 int n; 4631 if (false) readfln("%f %s %c %d", f, s, c, n); 4632 if (false) readfln!"%f %s %c %d"(f, s, c, n); 4633 4634 } 4635 4636 /* 4637 * Convenience function that forwards to `core.sys.posix.stdio.fopen` 4638 * (to `_wfopen` on Windows) 4639 * with appropriately-constructed C-style strings. 4640 */ 4641 private FILE* _fopen(R1, R2)(R1 name, R2 mode = "r") 4642 if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) && 4643 (isSomeFiniteCharInputRange!R2 || isSomeString!R2)) 4644 { 4645 import std.internal.cstring : tempCString; 4646 4647 auto namez = name.tempCString!FSChar(); 4648 auto modez = mode.tempCString!FSChar(); 4649 4650 static _fopenImpl(scope const(FSChar)* namez, scope const(FSChar)* modez) @trusted nothrow @nogc 4651 { 4652 version (Windows) 4653 { 4654 return _wfopen(namez, modez); 4655 } 4656 else version (Posix) 4657 { 4658 /* 4659 * The new opengroup large file support API is transparently 4660 * included in the normal C bindings. https://www.opengroup.org/platform/lfs.html#1.0 4661 * if _FILE_OFFSET_BITS in druntime is 64, off_t is 64 bit and 4662 * the normal functions work fine. If not, then large file support 4663 * probably isn't available. Do not use the old transitional API 4664 * (the native extern(C) fopen64, https://unix.org/version2/whatsnew/lfs20mar.html#3.0) 4665 */ 4666 import core.sys.posix.stdio : fopen; 4667 return fopen(namez, modez); 4668 } 4669 else 4670 { 4671 return fopen(namez, modez); 4672 } 4673 } 4674 return _fopenImpl(namez, modez); 4675 } 4676 4677 version (Posix) 4678 { 4679 /*********************************** 4680 * Convenience function that forwards to `core.sys.posix.stdio.popen` 4681 * with appropriately-constructed C-style strings. 4682 */ 4683 FILE* _popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc 4684 if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) && 4685 (isSomeFiniteCharInputRange!R2 || isSomeString!R2)) 4686 { 4687 import std.internal.cstring : tempCString; 4688 4689 auto namez = name.tempCString!FSChar(); 4690 auto modez = mode.tempCString!FSChar(); 4691 4692 static popenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc 4693 { 4694 import core.sys.posix.stdio : popen; 4695 return popen(namez, modez); 4696 } 4697 return popenImpl(namez, modez); 4698 } 4699 } 4700 4701 /* 4702 * Convenience function that forwards to `core.stdc.stdio.fwrite` 4703 */ 4704 private auto trustedFwrite(T)(FILE* f, const T[] obj) @trusted 4705 { 4706 return fwrite(obj.ptr, T.sizeof, obj.length, f); 4707 } 4708 4709 /* 4710 * Convenience function that forwards to `core.stdc.stdio.fread` 4711 */ 4712 private auto trustedFread(T)(FILE* f, T[] obj) @trusted 4713 if (!imported!"std.traits".hasIndirections!T) 4714 { 4715 return fread(obj.ptr, T.sizeof, obj.length, f); 4716 } 4717 4718 private auto trustedFread(T)(FILE* f, T[] obj) @system 4719 if (imported!"std.traits".hasIndirections!T) 4720 { 4721 return fread(obj.ptr, T.sizeof, obj.length, f); 4722 } 4723 4724 /** 4725 * Iterates through the lines of a file by using `foreach`. 4726 * 4727 * Example: 4728 * 4729 --------- 4730 void main() 4731 { 4732 foreach (string line; lines(stdin)) 4733 { 4734 ... use line ... 4735 } 4736 } 4737 --------- 4738 The line terminator (`'\n'` by default) is part of the string read (it 4739 could be missing in the last line of the file). Several types are 4740 supported for `line`, and the behavior of `lines` 4741 changes accordingly: 4742 4743 $(OL $(LI If `line` has type `string`, $(D 4744 wstring), or `dstring`, a new string of the respective type 4745 is allocated every read.) $(LI If `line` has type $(D 4746 char[]), `wchar[]`, `dchar[]`, the line's content 4747 will be reused (overwritten) across reads.) $(LI If `line` 4748 has type `immutable(ubyte)[]`, the behavior is similar to 4749 case (1), except that no UTF checking is attempted upon input.) $(LI 4750 If `line` has type `ubyte[]`, the behavior is 4751 similar to case (2), except that no UTF checking is attempted upon 4752 input.)) 4753 4754 In all cases, a two-symbols versions is also accepted, in which case 4755 the first symbol (of integral type, e.g. `ulong` or $(D 4756 uint)) tracks the zero-based number of the current line. 4757 4758 Example: 4759 ---- 4760 foreach (ulong i, string line; lines(stdin)) 4761 { 4762 ... use line ... 4763 } 4764 ---- 4765 4766 In case of an I/O error, an `StdioException` is thrown. 4767 4768 See_Also: 4769 $(LREF byLine) 4770 */ 4771 4772 struct lines 4773 { 4774 private File f; 4775 private dchar terminator = '\n'; 4776 4777 /** 4778 Constructor. 4779 Params: 4780 f = File to read lines from. 4781 terminator = Line separator (`'\n'` by default). 4782 */ 4783 this(File f, dchar terminator = '\n') @safe 4784 { 4785 this.f = f; 4786 this.terminator = terminator; 4787 } 4788 4789 int opApply(D)(scope D dg) 4790 { 4791 import std.traits : Parameters; 4792 alias Parms = Parameters!(dg); 4793 static if (isSomeString!(Parms[$ - 1])) 4794 { 4795 int result = 0; 4796 static if (is(Parms[$ - 1] : const(char)[])) 4797 alias C = char; 4798 else static if (is(Parms[$ - 1] : const(wchar)[])) 4799 alias C = wchar; 4800 else static if (is(Parms[$ - 1] : const(dchar)[])) 4801 alias C = dchar; 4802 C[] line; 4803 static if (Parms.length == 2) 4804 Parms[0] i = 0; 4805 for (;;) 4806 { 4807 import std.conv : to; 4808 4809 if (!f.readln(line, terminator)) break; 4810 auto copy = to!(Parms[$ - 1])(line); 4811 static if (Parms.length == 2) 4812 { 4813 result = dg(i, copy); 4814 ++i; 4815 } 4816 else 4817 { 4818 result = dg(copy); 4819 } 4820 if (result != 0) break; 4821 } 4822 return result; 4823 } 4824 else 4825 { 4826 // raw read 4827 return opApplyRaw(dg); 4828 } 4829 } 4830 // no UTF checking 4831 int opApplyRaw(D)(scope D dg) 4832 { 4833 import std.conv : to; 4834 import std.exception : assumeUnique; 4835 import std.traits : Parameters; 4836 4837 alias Parms = Parameters!(dg); 4838 enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]); 4839 int result = 1; 4840 int c = void; 4841 _FLOCK(f._p.handle); 4842 scope(exit) _FUNLOCK(f._p.handle); 4843 ubyte[] buffer; 4844 static if (Parms.length == 2) 4845 Parms[0] line = 0; 4846 while ((c = _FGETC(cast(_iobuf*) f._p.handle)) != -1) 4847 { 4848 buffer ~= to!(ubyte)(c); 4849 if (c == terminator) 4850 { 4851 static if (duplicate) 4852 auto arg = assumeUnique(buffer); 4853 else 4854 alias arg = buffer; 4855 // unlock the file while calling the delegate 4856 _FUNLOCK(f._p.handle); 4857 scope(exit) _FLOCK(f._p.handle); 4858 static if (Parms.length == 1) 4859 { 4860 result = dg(arg); 4861 } 4862 else 4863 { 4864 result = dg(line, arg); 4865 ++line; 4866 } 4867 if (result) break; 4868 static if (!duplicate) 4869 buffer.length = 0; 4870 } 4871 } 4872 // can only reach when _FGETC returned -1 4873 if (!f.eof) throw new StdioException("Error in reading file"); // error occured 4874 return result; 4875 } 4876 } 4877 4878 @safe unittest 4879 { 4880 /* 4881 As pointed out in <https://github.com/dlang/phobos/issues/10605>, 4882 it's a pity that `byLine()` & co. aren't @safe to use yet. 4883 4884 This is a first attempt at working towards that goal. 4885 For now, this test doesn't do much; as there isn't much to do safely yet. 4886 */ 4887 auto deleteMe = testFilename(); 4888 scope(exit) { imported!"std.file".remove(deleteMe); } 4889 4890 // Setup 4891 { 4892 auto f = File(deleteMe, "w"); 4893 scope(exit) { f.close(); } 4894 foreach (i; 1 .. 11) 4895 f.writeln(i); 4896 } 4897 4898 // Actual tests 4899 { 4900 auto f = File(deleteMe, "r"); 4901 scope(exit) { f.close(); } 4902 4903 auto myLines = lines(f); 4904 foreach (string line; myLines) 4905 continue; 4906 } 4907 4908 4909 { 4910 auto f = File(deleteMe, "r"); 4911 scope(exit) { f.close(); } 4912 4913 auto myByLineCopy = f.byLineCopy; 4914 foreach (line; myByLineCopy) 4915 continue; 4916 } 4917 4918 { 4919 auto f = File(deleteMe, "r"); 4920 scope(exit) { f.close(); } 4921 4922 auto myByLine = f.byLine; 4923 foreach (line; myByLine) 4924 continue; 4925 } 4926 } 4927 4928 @system unittest 4929 { 4930 static import std.file; 4931 import std.meta : AliasSeq; 4932 4933 scope(failure) printf("Failed test at line %d\n", __LINE__); 4934 4935 auto deleteme = testFilename(); 4936 scope(exit) { std.file.remove(deleteme); } 4937 4938 alias TestedWith = 4939 AliasSeq!(string, wstring, dstring, 4940 char[], wchar[], dchar[]); 4941 foreach (T; TestedWith) 4942 { 4943 // test looping with an empty file 4944 std.file.write(deleteme, ""); 4945 auto f = File(deleteme, "r"); 4946 foreach (T line; lines(f)) 4947 { 4948 assert(false); 4949 } 4950 f.close(); 4951 4952 // test looping with a file with three lines 4953 std.file.write(deleteme, "Line one\nline two\nline three\n"); 4954 f.open(deleteme, "r"); 4955 uint i = 0; 4956 foreach (T line; lines(f)) 4957 { 4958 if (i == 0) assert(line == "Line one\n"); 4959 else if (i == 1) assert(line == "line two\n"); 4960 else if (i == 2) assert(line == "line three\n"); 4961 else assert(false); 4962 ++i; 4963 } 4964 f.close(); 4965 4966 // test looping with a file with three lines, last without a newline 4967 std.file.write(deleteme, "Line one\nline two\nline three"); 4968 f.open(deleteme, "r"); 4969 i = 0; 4970 foreach (T line; lines(f)) 4971 { 4972 if (i == 0) assert(line == "Line one\n"); 4973 else if (i == 1) assert(line == "line two\n"); 4974 else if (i == 2) assert(line == "line three"); 4975 else assert(false); 4976 ++i; 4977 } 4978 f.close(); 4979 } 4980 4981 // test with ubyte[] inputs 4982 alias TestedWith2 = AliasSeq!(immutable(ubyte)[], ubyte[]); 4983 foreach (T; TestedWith2) 4984 { 4985 // test looping with an empty file 4986 std.file.write(deleteme, ""); 4987 auto f = File(deleteme, "r"); 4988 foreach (T line; lines(f)) 4989 { 4990 assert(false); 4991 } 4992 f.close(); 4993 4994 // test looping with a file with three lines 4995 std.file.write(deleteme, "Line one\nline two\nline three\n"); 4996 f.open(deleteme, "r"); 4997 uint i = 0; 4998 foreach (T line; lines(f)) 4999 { 5000 if (i == 0) assert(cast(char[]) line == "Line one\n"); 5001 else if (i == 1) assert(cast(char[]) line == "line two\n", 5002 T.stringof ~ " " ~ cast(char[]) line); 5003 else if (i == 2) assert(cast(char[]) line == "line three\n"); 5004 else assert(false); 5005 ++i; 5006 } 5007 f.close(); 5008 5009 // test looping with a file with three lines, last without a newline 5010 std.file.write(deleteme, "Line one\nline two\nline three"); 5011 f.open(deleteme, "r"); 5012 i = 0; 5013 foreach (T line; lines(f)) 5014 { 5015 if (i == 0) assert(cast(char[]) line == "Line one\n"); 5016 else if (i == 1) assert(cast(char[]) line == "line two\n"); 5017 else if (i == 2) assert(cast(char[]) line == "line three"); 5018 else assert(false); 5019 ++i; 5020 } 5021 f.close(); 5022 5023 } 5024 5025 static foreach (T; AliasSeq!(ubyte[])) 5026 { 5027 // test looping with a file with three lines, last without a newline 5028 // using a counter too this time 5029 std.file.write(deleteme, "Line one\nline two\nline three"); 5030 auto f = File(deleteme, "r"); 5031 uint i = 0; 5032 foreach (ulong j, T line; lines(f)) 5033 { 5034 if (i == 0) assert(cast(char[]) line == "Line one\n"); 5035 else if (i == 1) assert(cast(char[]) line == "line two\n"); 5036 else if (i == 2) assert(cast(char[]) line == "line three"); 5037 else assert(false); 5038 ++i; 5039 } 5040 f.close(); 5041 } 5042 } 5043 5044 /** 5045 Iterates through a file a chunk at a time by using `foreach`. 5046 5047 Example: 5048 5049 --------- 5050 void main() 5051 { 5052 foreach (ubyte[] buffer; chunks(stdin, 4096)) 5053 { 5054 ... use buffer ... 5055 } 5056 } 5057 --------- 5058 5059 The content of `buffer` is reused across calls. In the 5060 example above, `buffer.length` is 4096 for all iterations, 5061 except for the last one, in which case `buffer.length` may 5062 be less than 4096 (but always greater than zero). 5063 5064 In case of an I/O error, an `StdioException` is thrown. 5065 */ 5066 auto chunks(File f, size_t size) 5067 { 5068 return ChunksImpl(f, size); 5069 } 5070 private struct ChunksImpl 5071 { 5072 private File f; 5073 private size_t size; 5074 // private string fileName; // Currently, no use 5075 5076 this(File f, size_t size) 5077 in 5078 { 5079 assert(size, "size must be larger than 0"); 5080 } 5081 do 5082 { 5083 this.f = f; 5084 this.size = size; 5085 } 5086 5087 int opApply(D)(scope D dg) 5088 { 5089 import core.stdc.stdlib : alloca; 5090 import std.exception : enforce; 5091 5092 enforce(f.isOpen, "Attempting to read from an unopened file"); 5093 enum maxStackSize = 1024 * 16; 5094 ubyte[] buffer = void; 5095 if (size < maxStackSize) 5096 buffer = (cast(ubyte*) alloca(size))[0 .. size]; 5097 else 5098 buffer = new ubyte[size]; 5099 size_t r = void; 5100 int result = 1; 5101 uint tally = 0; 5102 while ((r = trustedFread(f._p.handle, buffer)) > 0) 5103 { 5104 assert(r <= size); 5105 if (r != size) 5106 { 5107 // error occured 5108 if (!f.eof) throw new StdioException(null); 5109 buffer.length = r; 5110 } 5111 static if (is(typeof(dg(tally, buffer)))) 5112 { 5113 if ((result = dg(tally, buffer)) != 0) break; 5114 } 5115 else 5116 { 5117 if ((result = dg(buffer)) != 0) break; 5118 } 5119 ++tally; 5120 } 5121 return result; 5122 } 5123 } 5124 5125 @system unittest 5126 { 5127 static import std.file; 5128 5129 scope(failure) printf("Failed test at line %d\n", __LINE__); 5130 5131 auto deleteme = testFilename(); 5132 scope(exit) { std.file.remove(deleteme); } 5133 5134 // test looping with an empty file 5135 std.file.write(deleteme, ""); 5136 auto f = File(deleteme, "r"); 5137 foreach (ubyte[] line; chunks(f, 4)) 5138 { 5139 assert(false); 5140 } 5141 f.close(); 5142 5143 // test looping with a file with three lines 5144 std.file.write(deleteme, "Line one\nline two\nline three\n"); 5145 f = File(deleteme, "r"); 5146 uint i = 0; 5147 foreach (ubyte[] line; chunks(f, 3)) 5148 { 5149 if (i == 0) assert(cast(char[]) line == "Lin"); 5150 else if (i == 1) assert(cast(char[]) line == "e o"); 5151 else if (i == 2) assert(cast(char[]) line == "ne\n"); 5152 else break; 5153 ++i; 5154 } 5155 f.close(); 5156 } 5157 5158 // Issue 21730 - null ptr dereferenced in ChunksImpl.opApply (SIGSEGV) 5159 @system unittest 5160 { 5161 import std.exception : assertThrown; 5162 static import std.file; 5163 5164 auto deleteme = testFilename(); 5165 scope(exit) { if (std.file.exists(deleteme)) std.file.remove(deleteme); } 5166 5167 auto err1 = File(deleteme, "w+x"); 5168 err1.close; 5169 std.file.remove(deleteme); 5170 assertThrown(() {foreach (ubyte[] buf; chunks(err1, 4096)) {}}()); 5171 } 5172 5173 /** 5174 Writes an array or range to a file. 5175 Shorthand for $(D data.copy(File(fileName, "wb").lockingBinaryWriter)). 5176 Similar to $(REF write, std,file), strings are written as-is, 5177 rather than encoded according to the `File`'s $(HTTP 5178 en.cppreference.com/w/c/io#Narrow_and_wide_orientation, 5179 orientation). 5180 */ 5181 void toFile(T)(T data, string fileName) 5182 if (is(typeof(copy(data, stdout.lockingBinaryWriter)))) 5183 { 5184 copy(data, File(fileName, "wb").lockingBinaryWriter); 5185 } 5186 5187 @system unittest 5188 { 5189 static import std.file; 5190 5191 auto deleteme = testFilename(); 5192 scope(exit) { std.file.remove(deleteme); } 5193 5194 "Test".toFile(deleteme); 5195 assert(std.file.readText(deleteme) == "Test"); 5196 } 5197 5198 /********************* 5199 * Thrown if I/O errors happen. 5200 */ 5201 class StdioException : Exception 5202 { 5203 static import core.stdc.errno; 5204 /// Operating system error code. 5205 uint errno; 5206 5207 /** 5208 Initialize with a message and an error code. 5209 */ 5210 this(string message, uint e = core.stdc.errno.errno) @trusted 5211 { 5212 import std.exception : errnoString; 5213 errno = e; 5214 auto sysmsg = errnoString(errno); 5215 // If e is 0, we don't use the system error message. (The message 5216 // is "Success", which is rather pointless for an exception.) 5217 super(e == 0 ? message 5218 : (message ? message ~ " (" ~ sysmsg ~ ")" : sysmsg)); 5219 } 5220 5221 /** Convenience functions that throw an `StdioException`. */ 5222 static void opCall(string msg) @safe 5223 { 5224 throw new StdioException(msg); 5225 } 5226 5227 /// ditto 5228 static void opCall() @safe 5229 { 5230 throw new StdioException(null, core.stdc.errno.errno); 5231 } 5232 } 5233 5234 enum StdFileHandle: string 5235 { 5236 stdin = "core.stdc.stdio.stdin", 5237 stdout = "core.stdc.stdio.stdout", 5238 stderr = "core.stdc.stdio.stderr", 5239 } 5240 5241 // Undocumented but public because the std* handles are aliasing it. 5242 @property ref File makeGlobal(StdFileHandle _iob)() 5243 { 5244 __gshared File.Impl impl; 5245 __gshared File result; 5246 5247 // Use an inline spinlock to make sure the initializer is only run once. 5248 // We assume there will be at most uint.max / 2 threads trying to initialize 5249 // `handle` at once and steal the high bit to indicate that the globals have 5250 // been initialized. 5251 static shared uint spinlock; 5252 import core.atomic : atomicLoad, atomicOp, MemoryOrder; 5253 if (atomicLoad!(MemoryOrder.acq)(spinlock) <= uint.max / 2) 5254 { 5255 for (;;) 5256 { 5257 if (atomicLoad!(MemoryOrder.acq)(spinlock) > uint.max / 2) 5258 break; 5259 if (atomicOp!"+="(spinlock, 1) == 1) 5260 { 5261 with (StdFileHandle) 5262 assert(_iob == stdin || _iob == stdout || _iob == stderr); 5263 impl.handle = cast() mixin(_iob); 5264 result._p = &impl; 5265 atomicOp!"+="(spinlock, uint.max / 2); 5266 break; 5267 } 5268 atomicOp!"-="(spinlock, 1); 5269 } 5270 } 5271 return result; 5272 } 5273 5274 /** The standard input stream. 5275 5276 Returns: 5277 stdin as a $(LREF File). 5278 5279 Note: 5280 The returned $(LREF File) wraps $(REF stdin,core,stdc,stdio), and 5281 is therefore thread global. Reassigning `stdin` to a different 5282 `File` must be done in a single-threaded or locked context in 5283 order to avoid race conditions. 5284 5285 All reading from `stdin` automatically locks the file globally, 5286 and will cause all other threads calling `read` to wait until 5287 the lock is released. 5288 */ 5289 alias stdin = makeGlobal!(StdFileHandle.stdin); 5290 5291 /// 5292 @safe unittest 5293 { 5294 // Read stdin, sort lines, write to stdout 5295 import std.algorithm.mutation : copy; 5296 import std.algorithm.sorting : sort; 5297 import std.array : array; 5298 import std.typecons : Yes; 5299 5300 void main() 5301 { 5302 stdin // read from stdin 5303 .byLineCopy(Yes.keepTerminator) // copying each line 5304 .array() // convert to array of lines 5305 .sort() // sort the lines 5306 .copy( // copy output of .sort to an OutputRange 5307 stdout.lockingTextWriter()); // the OutputRange 5308 } 5309 } 5310 5311 /** 5312 The standard output stream. 5313 5314 Returns: 5315 stdout as a $(LREF File). 5316 5317 Note: 5318 The returned $(LREF File) wraps $(REF stdout,core,stdc,stdio), and 5319 is therefore thread global. Reassigning `stdout` to a different 5320 `File` must be done in a single-threaded or locked context in 5321 order to avoid race conditions. 5322 5323 All writing to `stdout` automatically locks the file globally, 5324 and will cause all other threads calling `write` to wait until 5325 the lock is released. 5326 */ 5327 alias stdout = makeGlobal!(StdFileHandle.stdout); 5328 5329 /// 5330 @safe unittest 5331 { 5332 void main() 5333 { 5334 stdout.writeln("Write a message to stdout."); 5335 } 5336 } 5337 5338 /// 5339 @safe unittest 5340 { 5341 void main() 5342 { 5343 import std.algorithm.iteration : filter, map, sum; 5344 import std.format : format; 5345 import std.range : iota, tee; 5346 5347 int len; 5348 const r = 6.iota 5349 .filter!(a => a % 2) // 1 3 5 5350 .map!(a => a * 2) // 2 6 10 5351 .tee!(_ => stdout.writefln("len: %d", len++)) 5352 .sum; 5353 5354 assert(r == 18); 5355 } 5356 } 5357 5358 /// 5359 @safe unittest 5360 { 5361 void main() 5362 { 5363 import std.algorithm.mutation : copy; 5364 import std.algorithm.iteration : map; 5365 import std.format : format; 5366 import std.range : iota; 5367 5368 10.iota 5369 .map!(e => "N: %d".format(e)) 5370 .copy(stdout.lockingTextWriter()); // the OutputRange 5371 } 5372 } 5373 5374 /** 5375 The standard error stream. 5376 5377 Returns: 5378 stderr as a $(LREF File). 5379 5380 Note: 5381 The returned $(LREF File) wraps $(REF stderr,core,stdc,stdio), and 5382 is therefore thread global. Reassigning `stderr` to a different 5383 `File` must be done in a single-threaded or locked context in 5384 order to avoid race conditions. 5385 5386 All writing to `stderr` automatically locks the file globally, 5387 and will cause all other threads calling `write` to wait until 5388 the lock is released. 5389 */ 5390 alias stderr = makeGlobal!(StdFileHandle.stderr); 5391 5392 /// 5393 @safe unittest 5394 { 5395 void main() 5396 { 5397 stderr.writeln("Write a message to stderr."); 5398 } 5399 } 5400 5401 @system unittest 5402 { 5403 static import std.file; 5404 import std.typecons : tuple; 5405 5406 scope(failure) printf("Failed test at line %d\n", __LINE__); 5407 auto deleteme = testFilename(); 5408 5409 std.file.write(deleteme, "1 2\n4 1\n5 100"); 5410 scope(exit) std.file.remove(deleteme); 5411 { 5412 File f = File(deleteme); 5413 scope(exit) f.close(); 5414 auto t = [ tuple(1, 2), tuple(4, 1), tuple(5, 100) ]; 5415 uint i; 5416 foreach (e; f.byRecord!(int, int)("%s %s")) 5417 { 5418 //writeln(e); 5419 assert(e == t[i++]); 5420 } 5421 assert(i == 3); 5422 } 5423 } 5424 5425 @safe unittest 5426 { 5427 // Retain backwards compatibility 5428 // https://issues.dlang.org/show_bug.cgi?id=17472 5429 static assert(is(typeof(stdin) == File)); 5430 static assert(is(typeof(stdout) == File)); 5431 static assert(is(typeof(stderr) == File)); 5432 } 5433 5434 // roll our own appender, but with "safe" arrays 5435 private struct ReadlnAppender 5436 { 5437 char[] buf; 5438 size_t pos; 5439 bool safeAppend = false; 5440 5441 void initialize(char[] b) @safe 5442 { 5443 buf = b; 5444 pos = 0; 5445 } 5446 @property char[] data() @trusted 5447 { 5448 if (safeAppend) 5449 assumeSafeAppend(buf.ptr[0 .. pos]); 5450 return buf.ptr[0 .. pos]; 5451 } 5452 5453 bool reserveWithoutAllocating(size_t n) 5454 { 5455 if (buf.length >= pos + n) // buf is already large enough 5456 return true; 5457 5458 immutable curCap = buf.capacity; 5459 if (curCap >= pos + n) 5460 { 5461 buf.length = curCap; 5462 /* Any extra capacity we end up not using can safely be claimed 5463 by someone else. */ 5464 safeAppend = true; 5465 return true; 5466 } 5467 5468 return false; 5469 } 5470 void reserve(size_t n) @trusted 5471 { 5472 import core.stdc.string : memcpy; 5473 if (!reserveWithoutAllocating(n)) 5474 { 5475 size_t ncap = buf.length * 2 + 128 + n; 5476 char[] nbuf = new char[ncap]; 5477 memcpy(nbuf.ptr, buf.ptr, pos); 5478 buf = nbuf; 5479 // Allocated a new buffer. No one else knows about it. 5480 safeAppend = true; 5481 } 5482 } 5483 void putchar(char c) @trusted 5484 { 5485 reserve(1); 5486 buf.ptr[pos++] = c; 5487 } 5488 void putdchar(dchar dc) @trusted 5489 { 5490 import std.utf : encode, UseReplacementDchar; 5491 5492 char[4] ubuf; 5493 immutable size = encode!(UseReplacementDchar.yes)(ubuf, dc); 5494 reserve(size); 5495 foreach (c; ubuf) 5496 buf.ptr[pos++] = c; 5497 } 5498 void putonly(const char[] b) @trusted 5499 { 5500 import core.stdc.string : memcpy; 5501 assert(pos == 0); // assume this is the only put call 5502 if (reserveWithoutAllocating(b.length)) 5503 memcpy(buf.ptr + pos, b.ptr, b.length); 5504 else 5505 buf = b.dup; 5506 pos = b.length; 5507 } 5508 } 5509 5510 private struct LockedFile 5511 { 5512 private @system _iobuf* fp; 5513 5514 this(FILE* fps) @trusted 5515 { 5516 _FLOCK(fps); 5517 // Since fps is now locked, we can cast away shared 5518 fp = cast(_iobuf*) fps; 5519 } 5520 5521 @disable this(); 5522 @disable this(this); 5523 @disable void opAssign(LockedFile); 5524 5525 // these use unlocked fgetc calls 5526 @trusted fgetc() { return _FGETC(fp); } 5527 @trusted fgetwc() { return _FGETWC(fp); } 5528 5529 ~this() @trusted 5530 { 5531 _FUNLOCK(cast(FILE*) fp); 5532 } 5533 } 5534 5535 @safe unittest 5536 { 5537 void f() @safe 5538 { 5539 FILE* fps; 5540 auto lf = LockedFile(fps); 5541 static assert(!__traits(compiles, lf = LockedFile(fps))); 5542 version (ShouldFail) 5543 { 5544 lf.fps = null; // error with -preview=systemVariables 5545 } 5546 } 5547 } 5548 5549 // Private implementation of readln 5550 private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation) @safe 5551 { 5552 version (CRuntime_Microsoft) 5553 { 5554 auto lf = LockedFile(fps); 5555 5556 ReadlnAppender app; 5557 app.initialize(buf); 5558 5559 int c; 5560 while ((c = lf.fgetc()) != -1) 5561 { 5562 app.putchar(cast(char) c); 5563 if (c == terminator) 5564 { 5565 buf = app.data; 5566 return buf.length; 5567 } 5568 5569 } 5570 5571 if (ferror(fps)) 5572 StdioException(); 5573 buf = app.data; 5574 return buf.length; 5575 } 5576 else static if (__traits(compiles, core.sys.posix.stdio.getdelim)) 5577 { 5578 if (orientation == File.Orientation.wide) 5579 { 5580 import core.stdc.wchar_ : fwide; 5581 5582 auto lf = LockedFile(fps); 5583 /* Stream is in wide characters. 5584 * Read them and convert to chars. 5585 */ 5586 version (Windows) 5587 { 5588 buf.length = 0; 5589 for (int c = void; (c = lf.fgetwc()) != -1; ) 5590 { 5591 if ((c & ~0x7F) == 0) 5592 { buf ~= c; 5593 if (c == terminator) 5594 break; 5595 } 5596 else 5597 { 5598 if (c >= 0xD800 && c <= 0xDBFF) 5599 { 5600 int c2 = void; 5601 if ((c2 = lf.fgetwc()) != -1 || 5602 c2 < 0xDC00 && c2 > 0xDFFF) 5603 { 5604 StdioException("unpaired UTF-16 surrogate"); 5605 } 5606 c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); 5607 } 5608 import std.utf : encode; 5609 encode(buf, c); 5610 } 5611 } 5612 if (ferror(fps)) 5613 StdioException(); 5614 return buf.length; 5615 } 5616 else version (Posix) 5617 { 5618 buf.length = 0; 5619 for (int c; (c = lf.fgetwc()) != -1; ) 5620 { 5621 import std.utf : encode; 5622 5623 if ((c & ~0x7F) == 0) 5624 buf ~= cast(char) c; 5625 else 5626 encode(buf, cast(dchar) c); 5627 if (c == terminator) 5628 break; 5629 } 5630 if (ferror(fps)) 5631 StdioException(); 5632 return buf.length; 5633 } 5634 else 5635 { 5636 static assert(0); 5637 } 5638 } 5639 return () @trusted { 5640 import core.stdc.stdlib : free; 5641 5642 static char *lineptr = null; 5643 static size_t n = 0; 5644 scope(exit) 5645 { 5646 if (n > 128 * 1024) 5647 { 5648 // Bound memory used by readln 5649 free(lineptr); 5650 lineptr = null; 5651 n = 0; 5652 } 5653 } 5654 5655 const s = core.sys.posix.stdio.getdelim(&lineptr, &n, terminator, fps); 5656 if (s < 0) 5657 { 5658 if (ferror(fps)) 5659 StdioException(); 5660 buf.length = 0; // end of file 5661 return 0; 5662 } 5663 5664 const line = lineptr[0 .. s]; 5665 if (s <= buf.length) 5666 { 5667 buf = buf[0 .. s]; 5668 buf[] = line; 5669 } 5670 else 5671 { 5672 buf = line.dup; 5673 } 5674 return s; 5675 }(); 5676 } 5677 else // version (NO_GETDELIM) 5678 { 5679 import core.stdc.wchar_ : fwide; 5680 5681 auto lf = LockedFile(fps); 5682 if (orientation == File.Orientation.wide) 5683 { 5684 /* Stream is in wide characters. 5685 * Read them and convert to chars. 5686 */ 5687 version (Windows) 5688 { 5689 buf.length = 0; 5690 for (int c; (c = lf.fgetwc()) != -1; ) 5691 { 5692 if ((c & ~0x7F) == 0) 5693 { buf ~= c; 5694 if (c == terminator) 5695 break; 5696 } 5697 else 5698 { 5699 if (c >= 0xD800 && c <= 0xDBFF) 5700 { 5701 int c2 = void; 5702 if ((c2 = lf.fgetwc()) != -1 || 5703 c2 < 0xDC00 && c2 > 0xDFFF) 5704 { 5705 StdioException("unpaired UTF-16 surrogate"); 5706 } 5707 c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); 5708 } 5709 import std.utf : encode; 5710 encode(buf, c); 5711 } 5712 } 5713 if (ferror(fps)) 5714 StdioException(); 5715 return buf.length; 5716 } 5717 else version (Posix) 5718 { 5719 import std.utf : encode; 5720 buf.length = 0; 5721 for (int c; (c = lf.fgetwc()) != -1; ) 5722 { 5723 if ((c & ~0x7F) == 0) 5724 buf ~= cast(char) c; 5725 else 5726 encode(buf, cast(dchar) c); 5727 if (c == terminator) 5728 break; 5729 } 5730 if (ferror(fps)) 5731 StdioException(); 5732 return buf.length; 5733 } 5734 else 5735 { 5736 static assert(0); 5737 } 5738 } 5739 5740 // Narrow stream 5741 // First, fill the existing buffer 5742 for (size_t bufPos = 0; bufPos < buf.length; ) 5743 { 5744 immutable c = lf.fgetc(); 5745 if (c == -1) 5746 { 5747 buf.length = bufPos; 5748 goto endGame; 5749 } 5750 buf[bufPos++] = cast(char) c; 5751 if (c == terminator) 5752 { 5753 // No need to test for errors in file 5754 buf.length = bufPos; 5755 return bufPos; 5756 } 5757 } 5758 // Then, append to it 5759 for (int c; (c = lf.fgetc()) != -1; ) 5760 { 5761 buf ~= cast(char) c; 5762 if (c == terminator) 5763 { 5764 // No need to test for errors in file 5765 return buf.length; 5766 } 5767 } 5768 5769 endGame: 5770 if (ferror(fps)) 5771 StdioException(); 5772 return buf.length; 5773 } 5774 } 5775 5776 @system unittest 5777 { 5778 static import std.file; 5779 auto deleteme = testFilename(); 5780 scope(exit) std.file.remove(deleteme); 5781 5782 std.file.write(deleteme, "abcd\n0123456789abcde\n1234\n"); 5783 File f = File(deleteme, "rb"); 5784 5785 char[] ln = new char[2]; 5786 f.readln(ln); 5787 5788 assert(ln == "abcd\n"); 5789 char[] t = ln[0 .. 2]; 5790 t ~= 't'; 5791 assert(t == "abt"); 5792 // https://issues.dlang.org/show_bug.cgi?id=13856: ln stomped to "abtd" 5793 assert(ln == "abcd\n"); 5794 5795 // it can also stomp the array length 5796 ln = new char[4]; 5797 f.readln(ln); 5798 assert(ln == "0123456789abcde\n"); 5799 5800 char[100] buf; 5801 ln = buf[]; 5802 f.readln(ln); 5803 assert(ln == "1234\n"); 5804 assert(ln.ptr == buf.ptr); // avoid allocation, buffer is good enough 5805 } 5806 5807 /** Experimental network access via the File interface 5808 5809 Opens a TCP connection to the given host and port, then returns 5810 a File struct with read and write access through the same interface 5811 as any other file (meaning writef and the byLine ranges work!). 5812 5813 Authors: 5814 Adam D. Ruppe 5815 5816 Bugs: 5817 Only works on Linux 5818 */ 5819 version (linux) 5820 { 5821 File openNetwork(string host, ushort port) 5822 { 5823 import core.stdc.string : memcpy; 5824 import core.sys.posix.arpa.inet : htons; 5825 import core.sys.posix.netdb : gethostbyname; 5826 import core.sys.posix.netinet.in_ : sockaddr_in; 5827 static import core.sys.posix.unistd; 5828 static import sock = core.sys.posix.sys.socket; 5829 import std.conv : to; 5830 import std.exception : enforce; 5831 import std.internal.cstring : tempCString; 5832 5833 auto h = enforce( gethostbyname(host.tempCString()), 5834 new StdioException("gethostbyname")); 5835 5836 int s = sock.socket(sock.AF_INET, sock.SOCK_STREAM, 0); 5837 enforce(s != -1, new StdioException("socket")); 5838 5839 scope(failure) 5840 { 5841 // want to make sure it doesn't dangle if something throws. Upon 5842 // normal exit, the File struct's reference counting takes care of 5843 // closing, so we don't need to worry about success 5844 core.sys.posix.unistd.close(s); 5845 } 5846 5847 sockaddr_in addr; 5848 5849 addr.sin_family = sock.AF_INET; 5850 addr.sin_port = htons(port); 5851 memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length); 5852 5853 enforce(sock.connect(s, cast(sock.sockaddr*) &addr, addr.sizeof) != -1, 5854 new StdioException("Connect failed")); 5855 5856 File f; 5857 f.fdopen(s, "w+", host ~ ":" ~ to!string(port)); 5858 return f; 5859 } 5860 } 5861 5862 version (StdUnittest) private string testFilename(string file = __FILE__, size_t line = __LINE__) @safe 5863 { 5864 import std.conv : text; 5865 import std.file : deleteme; 5866 import std.path : baseName; 5867 5868 // filename intentionally contains non-ASCII (Russian) characters for 5869 // https://issues.dlang.org/show_bug.cgi?id=7648 5870 return text(deleteme, "-детка.", baseName(file), ".", line); 5871 }