]> git.ipfire.org Git - thirdparty/gcc.git/blame - libphobos/src/std/stdio.d
libphobos: Merge upstream phobos 66ae77ac3
[thirdparty/gcc.git] / libphobos / src / std / stdio.d
CommitLineData
03385ed3 1// Written in the D programming language.
2
3/**
4Standard I/O functions that extend $(B core.stdc.stdio). $(B core.stdc.stdio)
5is $(D_PARAM public)ally imported when importing $(B std.stdio).
6
7Source: $(PHOBOSSRC std/_stdio.d)
8Copyright: Copyright Digital Mars 2007-.
9License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
10Authors: $(HTTP digitalmars.com, Walter Bright),
11 $(HTTP erdani.org, Andrei Alexandrescu),
12 Alex Rønne Petersen
13 */
14module std.stdio;
15
16import core.stdc.stddef; // wchar_t
17public import core.stdc.stdio;
18import std.algorithm.mutation; // copy
19import std.meta; // allSatisfy
20import std.range.primitives; // ElementEncodingType, empty, front,
21 // isBidirectionalRange, isInputRange, put
22import std.traits; // isSomeChar, isSomeString, Unqual, isPointer
23import std.typecons; // Flag
24
25/++
26If flag $(D KeepTerminator) is set to $(D KeepTerminator.yes), then the delimiter
27is included in the strings returned.
28+/
29alias KeepTerminator = Flag!"keepTerminator";
30
31version (CRuntime_Microsoft)
32{
33 version = MICROSOFT_STDIO;
34}
35else version (CRuntime_DigitalMars)
36{
37 // Specific to the way Digital Mars C does stdio
38 version = DIGITAL_MARS_STDIO;
39}
40
41version (CRuntime_Glibc)
42{
43 // Specific to the way Gnu C does stdio
44 version = GCC_IO;
45 version = HAS_GETDELIM;
46}
99550ead 47else version (CRuntime_Bionic)
03385ed3 48{
49 version = GENERIC_IO;
50 version = HAS_GETDELIM;
51}
99550ead 52else version (CRuntime_Musl)
03385ed3 53{
54 version = GENERIC_IO;
55 version = HAS_GETDELIM;
56}
57
99550ead 58version (OSX)
03385ed3 59{
60 version = GENERIC_IO;
61 version = HAS_GETDELIM;
62}
99550ead 63else version (FreeBSD)
aae12232 64{
65 version = GENERIC_IO;
66 version = HAS_GETDELIM;
67}
99550ead 68else version (NetBSD)
03385ed3 69{
70 version = GENERIC_IO;
99550ead 71 version = HAS_GETDELIM;
03385ed3 72}
99550ead 73else version (DragonFlyBSD)
74{
75 version = GENERIC_IO;
76 version = HAS_GETDELIM;
77}
78else version (Solaris)
03385ed3 79{
80 version = GENERIC_IO;
81 version = NO_GETDELIM;
82}
83
84// Character type used for operating system filesystem APIs
85version (Windows)
86{
87 private alias FSChar = wchar;
88}
89else version (Posix)
90{
91 private alias FSChar = char;
92}
93else
94 static assert(0);
95
96version (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
108version (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}
141else 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}
181else 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}
210else 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}
242else
243{
244 static assert(0, "unsupported C I/O system");
245}
246
247version (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//------------------------------------------------------------------------------
255struct ByRecord(Fields...)
256{
257private:
258 import std.typecons : Tuple;
259
260 File file;
261 char[] line;
262 Tuple!(Fields) current;
263 string format;
264
265public:
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
310template byRecord(Fields...)
311{
312 ByRecord!(Fields) byRecord(File f, string format)
313 {
314 return typeof(return)(f, format);
315 }
316}
317
318/**
319Encapsulates a $(D FILE*). Generally D does not attempt to provide
320thin wrappers over equivalent functions in the C standard library, but
321manipulating $(D FILE*) values directly is unsafe and error-prone in
322many ways. The $(D File) type ensures safe manipulation, automatic
323file closing, and a lot of convenience.
324
325The underlying $(D FILE*) handle is maintained in a reference-counted
326manner, such that as soon as the last $(D File) variable bound to a
327given $(D FILE*) goes out of scope, the underlying $(D FILE*) is
328automatically closed.
329
330Example:
331----
332// test.d
333void 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
352Hello, Jimmy!
353% __
354)
355 */
356struct 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/**
387Constructor taking the name of the file to open and the open mode.
388
389Copying one $(D File) object to another results in the two $(D File)
390objects referring to the same underlying file.
391
392The destructor automatically closes the file as soon as no $(D File)
393object refers to it anymore.
394
395Params:
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
402Throws: $(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/**
469Assigns a file to another. The target of the assignment gets detached
470from whatever file it was attached to, and attaches itself to the new
471file.
472 */
473 void opAssign(File rhs) @safe
474 {
475 import std.algorithm.mutation : swap;
476
477 swap(this, rhs);
478 }
479
480/**
481First calls $(D detach) (throwing on failure), and then attempts to
482_open file $(D name) with mode $(D stdioOpenmode). The mode has the
483same semantics as in the C standard library $(HTTP
484cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) function.
485
486Throws: $(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/**
495Reuses the `File` object to either open a different file, or change
496the file mode. If `name` is `null`, the mode of the currently open
497file is changed; otherwise, a new file is opened, reusing the C
498`FILE*`. The function has the same semantics as in the C standard
499library $(HTTP cplusplus.com/reference/cstdio/freopen/, freopen)
500function.
501
502Note: Calling `reopen` with a `null` `name` is not implemented
503in all C runtimes.
504
505Throws: $(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/**
575First calls $(D detach) (throwing on failure), and then runs a command
576by calling the C standard library function $(HTTP
577opengroup.org/onlinepubs/007908799/xsh/_popen.html, _popen).
578
579Throws: $(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/**
592First calls $(D detach) (throwing on failure), and then attempts to
593associate the given file descriptor with the $(D File). The mode must
594be compatible with the mode of the file descriptor.
595
596Throws: $(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/**
646First calls $(D detach) (throwing on failure), and then attempts to
647associate the given Windows $(D HANDLE) with the $(D File). The mode must
648be compatible with the access attributes of the handle. Windows only.
649
650Throws: $(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/**
696Returns $(D true) if the file is at end (see $(HTTP
697cplusplus.com/reference/clibrary/cstdio/feof.html, feof)).
698
699Throws: $(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.
710If a $(D File) was created with $(LREF tmpfile) and $(LREF wrapFile)
711it has no name.*/
712 @property string name() const @safe pure nothrow
713 {
714 return _name;
715 }
716
717/**
718If the file is not opened, returns $(D true). Otherwise, returns
719$(HTTP cplusplus.com/reference/clibrary/cstdio/ferror.html, ferror) for
720the 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/**
740Detaches from the underlying file. If the sole owner, calls $(D close).
741
742Throws: $(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/**
773If the file was unopened, succeeds vacuously. Otherwise closes the
774file (by calling $(HTTP
775cplusplus.com/reference/clibrary/cstdio/fclose.html, fclose)),
776throwing on error. Even if an exception is thrown, afterwards the $(D
777File) object is empty. This is different from $(D detach) in that it
778always closes the file; consequently, all other $(D File) objects
779referring to the same handle will see a closed file henceforth.
780
781Throws: $(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/**
818If 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/**
829Flushes the C $(D FILE) buffers.
830
831Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_fflush.html, _fflush)
832for the file handle.
833
834Throws: $(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/**
859Forces any data buffered by the OS to be written to disk.
860Call $(LREF flush) before calling this function to flush the C $(D FILE) buffers first.
861
862This 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
868Throws: $(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/**
890Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fread.html, fread) for the
891file handle. The number of items to read and the size of
892each item is inferred from the size and type of the input array, respectively.
893
894Returns: The slice of $(D buffer) containing the data that was actually read.
895This will be shorter than $(D buffer) if EOF was reached before the buffer
896could be filled.
897
898Throws: $(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/**
950Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fwrite.html, fwrite) for the file
951handle. The number of items to write and the size of each
952item is inferred from the size and type of the input array, respectively. An
953error is thrown if the buffer could not be written in its entirety.
954
955$(D rawWrite) always writes in binary mode on Windows.
956
957Throws: $(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/**
1004Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fseek.html, fseek)
1005for the file handle.
1006
1007Throws: $(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/**
1067Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/ftell.html, ftell) for the
1068managed file handle.
1069
1070Throws: $(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/**
1112Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_rewind.html, _rewind)
1113for the file handle.
1114
1115Throws: $(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/**
1126Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, _setvbuf) for
1127the file handle.
1128
1129Throws: $(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/**
1142Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html,
1143_setvbuf) for the file handle.
1144
1145Throws: $(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/**
1208Locks the specified file segment. If the file segment is already locked
1209by another process, waits until the existing lock is released.
1210If both $(D start) and $(D length) are zero, the entire file is locked.
1211
1212Locks 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/**
1250Attempts to lock the specified file segment.
1251If both $(D start) and $(D length) are zero, the entire file is locked.
1252Returns: $(D true) if the lock was successful, and $(D false) if the
1253specified 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/**
1294Removes 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/**
1397Writes its arguments in text format to the file.
1398
1399Throws: $(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/**
1444Writes its arguments in text format to the file, followed by a newline.
1445
1446Throws: $(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/**
1455Writes its arguments in text format to the file, according to the
1456format string fmt.
1457
1458Params:
1459fmt = The $(LINK2 std_format.html#format-string, format string).
1460When passed as a compile-time argument, the string will be statically checked
1461against the argument types passed.
1462args = Items to write.
1463
1464Throws: $(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/**
1507Read line from the file handle and return it as a specified type.
1508
1509This version manages its own read buffer, which means one memory allocation per call. If you are not
1510retaining a reference to the read data, consider the $(D File.readln(buf)) version, which may offer
1511better performance as it can reuse its read buffer.
1512
1513Params:
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
1517Note:
1518 String terminators are not supported due to ambiguity with readln(buf) below.
1519
1520Returns:
1521 The line that was read, including the line terminator character.
1522
1523Throws:
1524 $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error.
1525
1526Example:
1527---
1528// Reads `stdin` and writes it to `stdout`.
1529import std.stdio;
1530
1531void 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/**
1590Read line from the file handle and write it to $(D buf[]), including
1591terminating character.
1592
1593This can be faster than $(D line = File.readln()) because you can reuse
1594the buffer for each call. Note that reusing the buffer means that you
1595must copy the previous contents if you wish to retain them.
1596
1597Params:
1598buf = Buffer used to store the resulting line data. buf is
1599resized as necessary.
1600terminator = Line terminator (by default, $(D '\n')). Use
1601$(REF newline, std,ascii) for portability (unless the file was opened in
1602text mode).
1603
1604Returns:
16050 for end of file, otherwise number of characters read
1606
1607Throws: $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode
1608conversion error.
1609
1610Example:
1611---
1612// Read lines from `stdin` into a string
1613// Ignore lines starting with '#'
1614// Write the string to `stdout`
1615
1616void 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
1633This method can be more efficient than the one in the previous example
1634because $(D stdin.readln(buf)) reuses (if possible) memory allocated
1635for $(D buf), whereas $(D line = stdin.readln()) makes a new memory allocation
1636for every line.
1637
1638For even better performance you can help $(D readln) by passing in a
1639large buffer to avoid memory reallocations. This can be done by reusing the
1640largest buffer returned by $(D readln):
1641
1642Example:
1643---
1644// Read lines from `stdin` and count words
1645
1646void 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---
1664This is actually what $(LREF byLine) does internally, so its usage
1665is 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
1803void 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
18182
18193
18204
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/**
1938Unsafe function that wraps an existing $(D FILE*). The resulting $(D
1939File) never takes the initiative in closing the file.
1940Note 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/**
1950Returns 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/**
1968Returns 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/**
1979Returns 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/*
1996Range that reads one line at a time. Returned by $(LREF byLine).
1997
1998Allows 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/**
2104Returns an input range set up to read from the file handle one line
2105at a time.
2106
2107The element type for the range will be $(D Char[]). Range primitives
2108may throw $(D StdioException) on I/O error.
2109
2110Note:
2111Each $(D front) will not persist after $(D
2112popFront) is called, so the caller must copy its contents (e.g. by
2113calling $(D to!string)) when retention is needed. If the caller needs
2114to retain a copy of every line, use the $(LREF byLineCopy) function
2115instead.
2116
2117Params:
2118Char = Character type for each line, defaulting to $(D char).
2119keepTerminator = Use $(D Yes.keepTerminator) to include the
2120terminator at the end of each line.
2121terminator = Line separator ($(D '\n') by default). Use
2122$(REF newline, std,ascii) for portability (unless the file was opened in
2123text mode).
2124
2125Example:
2126----
2127import std.algorithm, std.stdio, std.string;
2128// Count words in a file using ranges.
2129void 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
2140Example:
2141----
2142import std.range, std.stdio;
2143// Read lines using foreach.
2144void 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----
2159Notice that neither example accesses the line data returned by
2160$(D front) after the corresponding $(D popFront) call is made (because
2161the 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/**
2266Returns an input range set up to read from the file handle one line
2267at a time. Each line will be newly allocated. $(D front) will cache
2268its value to allow repeated calls without unnecessary allocations.
2269
2270Note: Due to caching byLineCopy can be more memory-efficient than
2271$(D File.byLine.map!idup).
2272
2273The element type for the range will be $(D Char[]). Range
2274primitives may throw $(D StdioException) on I/O error.
2275
2276Params:
2277Char = Character type for each line, defaulting to $(D immutable char).
2278keepTerminator = Use $(D Yes.keepTerminator) to include the
2279terminator at the end of each line.
2280terminator = Line separator ($(D '\n') by default). Use
2281$(REF newline, std,ascii) for portability (unless the file was opened in
2282text mode).
2283
2284Example:
2285----
2286import std.algorithm, std.array, std.stdio;
2287// Print sorted lines of a file.
2288void 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----
2298See_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/**
2582Returns an input range set up to read from the file handle a chunk at a
2583time.
2584
2585The element type for the range will be $(D ubyte[]). Range primitives
2586may throw $(D StdioException) on I/O error.
2587
2588Example:
2589---------
2590void 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
2600The parameter may be a number (as shown in the example above) dictating the
2601size of each chunk. Alternatively, $(D byChunk) accepts a
2602user-provided buffer that it uses directly.
2603
2604Example:
2605---------
2606void 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
2616In 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
2618needed, the caller must copy its contents (e.g. by calling $(D buffer.dup)).
2619
2620In the example above, $(D buffer.length) is 4096 for all iterations, except
2621for the last one, in which case $(D buffer.length) may be less than 4096 (but
2622always greater than zero).
2623
2624With the mentioned limitations, $(D byChunk) works with any algorithm
2625compatible with input ranges.
2626
2627Example:
2628---
2629// Efficient file copy, 1MB at a time.
2630import std.algorithm, std.stdio;
2631void 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
2638a single range lazily.
2639Example:
2640---
2641import std.algorithm, std.stdio;
2642void 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
2651Returns: A call to $(D byChunk) returns a range initialized with the $(D File)
2652object and the appropriate buffer.
2653
2654Throws: If the user-provided size is zero or the user-provided buffer
2655is 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
2891See $(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
3008Example:
3009Produce a grayscale image of the $(LINK2 https://en.wikipedia.org/wiki/Mandelbrot_set, Mandelbrot set)
3010in binary $(LINK2 https://en.wikipedia.org/wiki/Netpbm_format, Netpbm format) to standard output.
3011---
3012import std.algorithm, std.range, std.stdio;
3013
3014void 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).
3302enum 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
3322struct 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 */
3479template 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 */
3495private @property File trustedStdout() @trusted
3496{
3497 return stdout;
3498}
3499
3500/***********************************
3501For each argument $(D arg) in $(D args), format the argument (using
3502$(REF to, std,conv)) and write the resulting
3503string to $(D args[0]). A call without any arguments will fail to
3504compile.
3505
3506Params:
3507 args = the items to write to `stdout`
3508
3509Throws: In case of an I/O error, throws an $(D StdioException).
3510
3511Example:
3512 Reads `stdin` and writes it to `stdout` with an argument
3513 counter.
3514---
3515import std.stdio;
3516
3517void 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 */
3528void write(T...)(T args)
3529if (!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---
3564import std.stdio;
3565
3566void 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 */
3577void 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/***********************************
3727Writes formatted data to standard output (without a trailing newline).
3728
3729Params:
3730fmt = The $(LINK2 std_format.html#format-string, format string).
3731When passed as a compile-time argument, the string will be statically checked
3732against the argument types passed.
3733args = Items to write.
3734
3735Note: In older versions of Phobos, it used to be possible to write:
3736
3737------
3738writef(stderr, "%s", "message");
3739------
3740
3741to print a message to $(D stderr). This syntax is no longer supported, and has
3742been superceded by:
3743
3744------
3745stderr.writef("%s", "message");
3746------
3747
3748*/
3749void writef(alias fmt, A...)(A args)
3750if (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
3760void 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 */
3790void writefln(alias fmt, A...)(A args)
3791if (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
3801void 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
3850void 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
38632
38643
38654
3866)
3867 */
3868uint readf(alias format, A...)(auto ref A args)
3869if (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
3879uint 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---
3917import std.stdio;
3918
3919void main()
3920{
3921 string line;
3922 while ((line = readln()) !is null)
3923 write(line);
3924}
3925---
3926*/
3927S readln(S = string)(dchar terminator = '\n')
3928if (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---
3951import std.stdio;
3952
3953void main()
3954{
3955 char[] buf;
3956 while (readln(buf))
3957 write(buf);
3958}
3959---
3960*/
3961size_t readln(C)(ref C[] buf, dchar terminator = '\n')
3962if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum))
3963{
3964 return stdin.readln(buf, terminator);
3965}
3966
3967/** ditto */
3968size_t readln(C, R)(ref C[] buf, R terminator)
3969if (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 */
4005private FILE* fopen(R1, R2)(R1 name, R2 mode = "r")
4006if ((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
4041version (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 */
4068private 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 */
4076private 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---------
4087void main()
4088{
4089 foreach (string line; lines(stdin))
4090 {
4091 ... use line ...
4092 }
4093}
4094---------
4095The line terminator ($(D '\n') by default) is part of the string read (it
4096could be missing in the last line of the file). Several types are
4097supported for $(D line), and the behavior of $(D lines)
4098changes accordingly:
4099
4100$(OL $(LI If $(D line) has type $(D string), $(D
4101wstring), or $(D dstring), a new string of the respective type
4102is allocated every read.) $(LI If $(D line) has type $(D
4103char[]), $(D wchar[]), $(D dchar[]), the line's content
4104will be reused (overwritten) across reads.) $(LI If $(D line)
4105has type $(D immutable(ubyte)[]), the behavior is similar to
4106case (1), except that no UTF checking is attempted upon input.) $(LI
4107If $(D line) has type $(D ubyte[]), the behavior is
4108similar to case (2), except that no UTF checking is attempted upon
4109input.))
4110
4111In all cases, a two-symbols versions is also accepted, in which case
4112the first symbol (of integral type, e.g. $(D ulong) or $(D
4113uint)) tracks the zero-based number of the current line.
4114
4115Example:
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
4125See_Also:
4126$(LREF byLine)
4127 */
4128
4129struct 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/**
4354Iterates through a file a chunk at a time by using $(D foreach).
4355
4356Example:
4357
4358---------
4359void main()
4360{
4361 foreach (ubyte[] buffer; chunks(stdin, 4096))
4362 {
4363 ... use buffer ...
4364 }
4365}
4366---------
4367
4368The 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*/
4375auto chunks(File f, size_t size)
4376{
4377 return ChunksImpl(f, size);
4378}
4379private 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/**
4466Writes an array or range to a file.
4467Shorthand for $(D data.copy(File(fileName, "wb").lockingBinaryWriter)).
4468Similar to $(REF write, std,file), strings are written as-is,
4469rather than encoded according to the $(D File)'s $(HTTP
4470en.cppreference.com/w/c/io#Narrow_and_wide_orientation,
4471orientation).
4472*/
4473void toFile(T)(T data, string fileName)
4474if (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 */
4493class StdioException : Exception
4494{
4495 static import core.stdc.errno;
4496 /// Operating system error code.
4497 uint errno;
4498
4499/**
4500Initialize 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
22163f0d 4526enum StdFileHandle: string
4527{
4528 stdin = "core.stdc.stdio.stdin",
4529 stdout = "core.stdc.stdio.stdout",
4530 stderr = "core.stdc.stdio.stderr",
4531}
4532
03385ed3 4533// Undocumented but public because the std* handles are aliasing it.
22163f0d 4534@property ref File makeGlobal(StdFileHandle _iob)()
03385ed3 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 {
22163f0d 4553 with (StdFileHandle)
4554 assert(_iob == stdin || _iob == stdout || _iob == stderr);
4555 impl.handle = mixin(_iob);
03385ed3 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*/
22163f0d 4572alias stdin = makeGlobal!(StdFileHandle.stdin);
03385ed3 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*/
22163f0d 4600alias stdout = makeGlobal!(StdFileHandle.stdout);
03385ed3 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*/
22163f0d 4609alias stderr = makeGlobal!(StdFileHandle.stderr);
03385ed3 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
4645private 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
4721version (DIGITAL_MARS_STDIO)
4722private 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
4844version (MICROSOFT_STDIO)
4845private 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
4876version (HAS_GETDELIM)
4877private 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
4978version (NO_GETDELIM)
4979private 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*/
5123version (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
5166version (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}