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