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