aboutsummaryrefslogtreecommitdiff
path: root/libbutl/fdstream.mxx
blob: 9818732ef3f1aea95103d2ea3843d31eefb25467 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
// file      : libbutl/fdstream.mxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef __cpp_modules_ts
#pragma once
#endif

#include <cassert>

#ifndef __cpp_lib_modules_ts
#include <ios>     // streamsize
#include <vector>
#include <string>
#include <chrono>
#include <istream>
#include <ostream>
#include <utility> // move(), pair
#include <cstdint> // uint16_t, uint64_t
#include <cstddef> // size_t

#include <iterator>
#endif

// Other includes.

#ifdef __cpp_modules_ts
export module butl.fdstream;
#ifdef __cpp_lib_modules_ts
import std.core;
import std.io;
#endif
import butl.path;
import butl.filesystem; // permissions, entry_stat
import butl.small_vector;
#else
#include <libbutl/path.mxx>
#include <libbutl/filesystem.mxx>
#include <libbutl/small-vector.mxx>
#endif

#include <libbutl/export.hxx>

LIBBUTL_MODEXPORT namespace butl
{
  // RAII type for file descriptors. Note that failure to close the descriptor
  // is silently ignored by both the destructor and reset().
  //
  // The descriptor can be negative. Such a descriptor is treated as unopened
  // and is not closed.
  //
  struct nullfd_t
  {
    constexpr explicit nullfd_t (int) {}
    constexpr operator int () const {return -1;}
  };

#if defined(__cpp_modules_ts) && defined(__clang__) //@@ MOD Clang duplicate sym.
  inline
#endif
  constexpr nullfd_t nullfd (-1);

  class LIBBUTL_SYMEXPORT auto_fd
  {
  public:
    auto_fd (nullfd_t = nullfd) noexcept: fd_ (-1) {}

    explicit
    auto_fd (int fd) noexcept: fd_ (fd) {}

    auto_fd (auto_fd&& fd) noexcept: fd_ (fd.release ()) {}
    auto_fd& operator= (auto_fd&&) noexcept;

    auto_fd (const auto_fd&) = delete;
    auto_fd& operator= (const auto_fd&) = delete;

    ~auto_fd () noexcept;

    int
    get () const noexcept {return fd_;}

    void
    reset (int fd = -1) noexcept;

    int
    release () noexcept
    {
      int r (fd_);
      fd_ = -1;
      return r;
    }

    // Close an open file descriptor. Throw ios::failure on the underlying OS
    // error. Reset the descriptor to -1 whether the exception is thrown or
    // not.
    //
    void
    close ();

  private:
    int fd_;
  };

  inline bool
  operator== (const auto_fd& x, const auto_fd& y)
  {
    return x.get () == y.get ();
  }

  inline bool
  operator!= (const auto_fd& x, const auto_fd& y)
  {
    return !(x == y);
  }

  inline bool
  operator== (const auto_fd& x, nullfd_t)
  {
    return x.get () == -1;
  }

  inline bool
  operator!= (const auto_fd& x, nullfd_t y)
  {
    return !(x == y);
  }

  // An [io]fstream that can be initialized with a file descriptor in addition
  // to a file name and that also by default enables exceptions on badbit and
  // failbit. So instead of a dance like this:
  //
  // ifstream ifs;
  // ifs.exceptions (ifstream::badbit | ifstream::failbit);
  // ifs.open (path.string ());
  //
  // You can simply do:
  //
  // ifdstream ifs (path);
  //
  // Notes and limitations:
  //
  // - char only
  // - input or output but not both (can use a union of two streams for that)
  // - no support for put back
  // - use of tell[gp]() and seek[gp]() is discouraged on Windows for
  //   fdstreams opened in the text mode (see fdbuf::seekoff() implementation
  //   for reasoning and consider using non-standard tellg() and seekg() in
  //   fdbuf, instead)
  // - non-blocking file descriptor is supported only by showmanyc() function
  //   and only for pipes on Windows, in contrast to POSIX systems
  // - throws ios::failure in case of open(), read(), write(), close(),
  //   seek[gp](), or tell[gp]() errors
  // - exception mask has at least badbit
  // - after catching an exception caused by badbit the stream is no longer
  //   usable
  // - not movable, though can be easily supported (or not: there is no move
  //   constructor for istream/ostream in GCC 4.9)
  // - passing to constructor auto_fd with a negative file descriptor is valid
  //   and results in the creation of an unopened object
  //
  class LIBBUTL_SYMEXPORT fdbuf: public std::basic_streambuf<char>
  {
  public:
    fdbuf () = default;

    // Unless specified, the current read/write position is assumed to
    // be 0 (note: not queried).
    //
    fdbuf (auto_fd&&, std::uint64_t pos = 0);

    // Before we invented auto_fd into fdstreams we keept fdbuf opened on
    // faulty close attempt. Now fdbuf is always closed by close() function.
    // This semantics change seems to be the right one as there is no reason to
    // expect fdclose() to succeed after it has already failed once.
    //
    void
    close () {fd_.close ();}

    auto_fd
    release ();

    void
    open (auto_fd&&, std::uint64_t pos = 0);

    bool
    is_open () const {return fd_.get () >= 0;}

    int
    fd () const {return fd_.get ();}

    // Set the file descriptor blocking mode returning the previous mode on
    // success and throwing ios::failure otherwise (see fdmode() for details).
    //
    // Note that besides calling fdmode(fd()), this function also updating its
    // internal state according to the new mode.
    //
    bool
    blocking (bool);

  public:
    using base = std::basic_streambuf<char>;

    using int_type = base::int_type;
    using traits_type = base::traits_type;

    using pos_type = base::pos_type; // std::streampos
    using off_type = base::off_type; // std::streamoff

    // basic_streambuf input interface.
    //
  public:
    virtual std::streamsize
    showmanyc ();

    virtual int_type
    underflow ();

    // Direct access to the get area. Use with caution.
    //
    using base::gptr;
    using base::egptr;
    using base::gbump;

    // Return the (logical) position of the next byte to be read.
    //
    // Note that on Windows when reading in the text mode the logical position
    // may differ from the physical file descriptor position due to the CRLF
    // character sequence translation. See the seekoff() implementation for
    // more background on this issue.
    //
    std::uint64_t
    tellg () const {return off_ - (egptr () - gptr ());}

    // Seek to the (logical) position as if by reading the specified number of
    // bytes from the beginning of the stream. Throw ios::failure on the
    // underlying OS errors.
    //
    void
    seekg (std::uint64_t);

  private:
    bool
    load ();

    // basic_streambuf output interface.
    //
  public:
    virtual int_type
    overflow (int_type);

    virtual int
    sync ();

    virtual std::streamsize
    xsputn (const char_type*, std::streamsize);

    // Return the (logical) position of the next byte to be written.
    //
    std::uint64_t
    tellp () const {return off_ + (pptr () - buf_);}

    // basic_streambuf positioning interface (both input/output).
    //
  public:
    virtual pos_type
    seekpos (pos_type, std::ios_base::openmode);

    virtual pos_type
    seekoff (off_type, std::ios_base::seekdir, std::ios_base::openmode);

  private:
    bool
    save ();

  private:
    auto_fd fd_;
    std::uint64_t off_;
    char buf_[8192];
    bool non_blocking_ = false;
  };

  // File stream mode.
  //
  // The text/binary flags have the same semantics as those in std::fstream.
  // Specifically, this is a noop for POSIX systems where the two modes are
  // the same. On Windows, when reading in the text mode the sequence of 0xD,
  // 0xA characters is translated into the single OxA character and 0x1A is
  // interpreted as EOF. When writing in the text mode the OxA character is
  // translated into the 0xD, 0xA sequence.
  //
  // The skip flag instructs the stream to skip to the end before closing the
  // file descriptor. This is primarily useful when working with pipes where
  // you may want not to "offend" the other end by closing your end before
  // reading all the data.
  //
  // The blocking/non_blocking flags determine whether the IO operation should
  // block or return control if currently there is no data to read or no room
  // to write. Only the istream::readsome() function supports the semantics of
  // non-blocking operations. In contrast to POSIX systems, we only support
  // this for pipes on Windows, always assuming the blocking mode for other
  // file descriptors. IO stream operations other than readsome() are illegal
  // in the non-blocking mode and result in the badbit being set (note that
  // it is not the more appropriate failbit for implementation reasons).
  //
  enum class fdstream_mode: std::uint16_t
  {
    text         = 0x01,
    binary       = 0x02,
    skip         = 0x04,
    blocking     = 0x08,
    non_blocking = 0x10
  };

  inline fdstream_mode operator& (fdstream_mode, fdstream_mode);
  inline fdstream_mode operator| (fdstream_mode, fdstream_mode);
  inline fdstream_mode operator&= (fdstream_mode&, fdstream_mode);
  inline fdstream_mode operator|= (fdstream_mode&, fdstream_mode);

  // Extended (compared to ios::openmode) file open flags.
  //
  enum class fdopen_mode: std::uint16_t
  {
    in         = 0x01, // Open for reading.
    out        = 0x02, // Open for writing.
    append     = 0x04, // Seek to the end of file before each write.
    truncate   = 0x08, // Discard the file contents on open.
    create     = 0x10, // Create a file if not exists.
    exclusive  = 0x20, // Fail if the file exists and the create flag is set.
    binary     = 0x40, // Set binary translation mode.
    at_end     = 0x80, // Seek to the end of stream immediately after open.

    none = 0           // Usefull when building the mode incrementally.
  };

  inline fdopen_mode operator& (fdopen_mode, fdopen_mode);
  inline fdopen_mode operator| (fdopen_mode, fdopen_mode);
  inline fdopen_mode operator&= (fdopen_mode&, fdopen_mode);
  inline fdopen_mode operator|= (fdopen_mode&, fdopen_mode);

  class LIBBUTL_SYMEXPORT fdstream_base
  {
  protected:
    fdstream_base () = default;
    fdstream_base (auto_fd&&, std::uint64_t pos);
    fdstream_base (auto_fd&&, fdstream_mode, std::uint64_t pos);

  public:
    int
    fd () const {return buf_.fd ();}

  protected:
    fdbuf buf_;
  };

  // iofdstream constructors and open() functions that take openmode as an
  // argument mimic the corresponding iofstream functions in terms of the
  // openmode mask interpretation. They throw std::invalid_argument for an
  // invalid combination of flags (as per the standard). Note that the in and
  // out flags are always added implicitly for ifdstream and ofdstream,
  // respectively.
  //
  // iofdstream constructors and open() functions that take fdopen_mode as an
  // argument interpret the mask literally just ignoring some flags which are
  // meaningless in the absense of others (read more on that in the comment
  // for fdopen()). Note that the in and out flags are always added implicitly
  // for ifdstream and ofdstream, respectively.
  //
  // iofdstream constructors and open() functions that take file path as a
  // const std::string& or const char* may throw the invalid_path exception.
  //
  // Passing auto_fd with a negative file descriptor is valid and results in
  // the creation of an unopened object.
  //
  // Also note that open() and close() functions can be successfully called
  // for an opened and unopened objects respectively. That is in contrast with
  // iofstream that sets failbit in such cases.
  //

  // Note that ifdstream destructor will close an open file descriptor but
  // will ignore any errors. To detect such errors, call close() explicitly.
  //
  // This is a sample usage of iofdstreams with process. Note that here it is
  // expected that the child process reads from STDIN first and writes to
  // STDOUT afterwards.
  //
  // try
  // {
  //   process pr (args, -1, -1);
  //
  //   try
  //   {
  //     // In case of exception, skip and close input after output.
  //     //
  //     ifdstream is (move (pr.in_ofd), fdstream_mode::skip);
  //     ofdstream os (move (pr.out_fd));
  //
  //     // Write.
  //
  //     os.close (); // Don't block the other end.
  //
  //     // Read.
  //
  //     is.close (); // Skip till end and close.
  //
  //     if (pr.wait ())
  //     {
  //       return ...; // Good.
  //     }
  //
  //     // Non-zero exit, diagnostics presumably issued, fall through.
  //   }
  //   catch (const failure&)
  //   {
  //     // IO failure, child exit status doesn't matter. Just wait for the
  //     // process completion and fall through.
  //     //
  //     // Note that this is optional if the process_error handler simply
  //     // falls through since process destructor will wait (but will ignore
  //     // any errors).
  //     //
  //     pr.wait ();
  //   }
  //
  //   error <<  .... ;
  //
  //   // Fall through.
  // }
  // catch (const process_error& e)
  // {
  //   error << ... << e;
  //
  //   if (e.child ())
  //     exit (1);
  //
  //   // Fall through.
  // }
  //
  // throw failed ();
  //
  class LIBBUTL_SYMEXPORT ifdstream: public fdstream_base, public std::istream
  {
  public:
    // Create an unopened object.
    //
    explicit
    ifdstream (iostate = badbit | failbit);

    explicit
    ifdstream (auto_fd&&,
               iostate = badbit | failbit,
               std::uint64_t pos = 0);

    ifdstream (auto_fd&&,
               fdstream_mode m,
               iostate = badbit | failbit,
               std::uint64_t pos = 0);

    explicit
    ifdstream (const char*,
               iostate = badbit | failbit);

    explicit
    ifdstream (const std::string&,
               iostate = badbit | failbit);

    explicit
    ifdstream (const path&,
               iostate = badbit | failbit);

    // @@ In some implementations (for example, MSVC), iostate and openmode
    //    (and/or their respective constants) are not distinct enough which
    //    causes overload resolution errors.
    //
    ifdstream (const char*,
               openmode,
               iostate /*= badbit | failbit*/);

    ifdstream (const std::string&,
               openmode,
               iostate /*= badbit | failbit*/);

    ifdstream (const path&,
               openmode,
               iostate /*= badbit | failbit*/);

    ifdstream (const char*,
               fdopen_mode,
               iostate = badbit | failbit);

    ifdstream (const std::string&,
               fdopen_mode,
               iostate = badbit | failbit);

    ifdstream (const path&,
               fdopen_mode,
               iostate = badbit | failbit);

    ~ifdstream () override;

    void
    open (const char*, openmode = in);

    void
    open (const std::string&, openmode = in);

    void
    open (const path&, openmode = in);

    void
    open (const char*, fdopen_mode);

    void
    open (const std::string&, fdopen_mode);

    void
    open (const path&, fdopen_mode);

    void
    open (auto_fd&& fd, std::uint64_t pos = 0)
    {
      buf_.open (std::move (fd), pos);
      clear ();
    }

    void
    open (auto_fd&& fd, fdstream_mode m, std::uint64_t pos = 0);

    void close ();
    auto_fd release (); // Note: no skipping.
    bool is_open () const {return buf_.is_open ();}

    // Read the textual stream. The stream is supposed not to contain the null
    // character.
    //
    std::string
    read_text ();

    // Read the binary stream.
    //
    std::vector<char>
    read_binary ();

  private:
    bool skip_ = false;
  };

  // Note that ofdstream requires that you explicitly call close() before
  // destroying it. Or, more specifically, the ofdstream object should not be
  // in the opened state by the time its destructor is called, unless it is in
  // the "not good" state (good() == false) or the destructor is being called
  // during the stack unwinding due to an exception being thrown
  // (std::uncaught_exception() == true). This is enforced with assert() in
  // the ofdstream destructor.
  //
  class LIBBUTL_SYMEXPORT ofdstream: public fdstream_base, public std::ostream
  {
  public:
    // Create an unopened object.
    //
    explicit
    ofdstream (iostate = badbit | failbit);

    explicit
    ofdstream (auto_fd&&,
               iostate = badbit | failbit,
               std::uint64_t pos = 0);

    ofdstream (auto_fd&&,
               fdstream_mode m,
               iostate = badbit | failbit,
               std::uint64_t pos = 0);

    explicit
    ofdstream (const char*,
               iostate = badbit | failbit);

    explicit
    ofdstream (const std::string&,
               iostate = badbit | failbit);

    explicit
    ofdstream (const path&,
               iostate = badbit | failbit);

    // @@ In some implementations (for example, MSVC), iostate and openmode
    //    (and/or their respective constants) are not distinct enough which
    //    causes overload resolution errors.
    //
    ofdstream (const char*,
               openmode,
               iostate /*= badbit | failbit*/);

    ofdstream (const std::string&,
               openmode,
               iostate /*= badbit | failbit*/);

    ofdstream (const path&,
               openmode,
               iostate /*= badbit | failbit*/);

    ofdstream (const char*,
               fdopen_mode,
               iostate = badbit | failbit);

    ofdstream (const std::string&,
               fdopen_mode,
               iostate = badbit | failbit);

    ofdstream (const path&,
               fdopen_mode,
               iostate = badbit | failbit);

    ~ofdstream () override;

    void
    open (const char*, openmode = out);

    void
    open (const std::string&, openmode = out);

    void
    open (const path&, openmode = out);

    void
    open (const char*, fdopen_mode);

    void
    open (const std::string&, fdopen_mode);

    void
    open (const path&, fdopen_mode);

    void
    open (auto_fd&& fd, std::uint64_t pos = 0)
    {
      buf_.open (std::move (fd), pos);
      clear ();
    }

    void close () {if (is_open ()) flush (); buf_.close ();}
    auto_fd release ();
    bool is_open () const {return buf_.is_open ();}
  };

  // Open a file or, if the file name is `-`, stdin/stdout.
  //
  // In case of the stdin/stdout, these functions simply adjust the exception
  // mask on std::cin/cout to match the i/ofdstreams argument.
  //
  // Return a reference to the opened i/ofdstream or cin/cout and, in the
  // latter case, set the translated name in path_name to <stdin>/<stdout>
  // (unless it is already present).
  //
  // Note that ofdstream::close() should be called explicitly unless stdout
  // was opened (but harmless to call even if it was).
  //
  LIBBUTL_SYMEXPORT std::istream&
  open_file_or_stdin (path_name&, ifdstream&);

  LIBBUTL_SYMEXPORT std::ostream&
  open_file_or_stdout (path_name&, ofdstream&);

  // The std::getline() replacement that provides a workaround for libstdc++'s
  // ios::failure ABI fiasco (#66145) by throwing ios::failure, as it is
  // defined at libbutl build time (new ABI on recent distributions) rather
  // than libstdc++ build time (still old ABI on most distributions).
  //
  // Notes:
  //
  // - This relies of ADL so if the stream is used via the std::istream
  //   interface, then std::getline() will still be used. To put it another
  //   way, this is "the best we can do" until GCC folks get their act
  //   together.
  //
  // - The fail and eof bits may be left cleared in the stream exception mask
  //   when the function throws because of badbit.
  //
  LIBBUTL_SYMEXPORT ifdstream&
  getline (ifdstream&, std::string&, char delim = '\n');

  // Open a file returning an auto_fd that holds its file descriptor on
  // success and throwing ios::failure otherwise.
  //
  // The mode argument should have at least one of the in or out flags set.
  // The append and truncate flags are meaningless in the absense of the out
  // flag and are ignored without it. The exclusive flag is meaningless in the
  // absense of the create flag and is ignored without it. Note also that if
  // the exclusive flag is specified then a dangling symbolic link is treated
  // as an existing file.
  //
  // The permissions argument is taken into account only if the file is
  // created. Note also that permissions can be adjusted while being set in a
  // way specific for the OS. On POSIX systems they are modified with the
  // process' umask, so effective permissions are permissions & ~umask. On
  // Windows permissions other than ru and wu are unlikelly to have effect.
  //
  // Also note that on POSIX the FD_CLOEXEC flag is set for the file descriptor
  // to prevent its leakage into child processes. On Windows, for the same
  // purpose, the _O_NOINHERIT flag is set. Note that the process class, that
  // passes such a descriptor to the child, makes it inheritable for a while.
  //
  LIBBUTL_SYMEXPORT auto_fd
  fdopen (const char*,
          fdopen_mode,
          permissions = permissions::ru | permissions::wu |
                        permissions::rg | permissions::wg |
                        permissions::ro | permissions::wo);

  LIBBUTL_SYMEXPORT auto_fd
  fdopen (const std::string&,
          fdopen_mode,
          permissions = permissions::ru | permissions::wu |
                        permissions::rg | permissions::wg |
                        permissions::ro | permissions::wo);

  LIBBUTL_SYMEXPORT auto_fd
  fdopen (const path&,
          fdopen_mode,
          permissions = permissions::ru | permissions::wu |
                        permissions::rg | permissions::wg |
                        permissions::ro | permissions::wo);

  // Duplicate an open file descriptor. Throw ios::failure on the underlying
  // OS error.
  //
  // Note that on POSIX the FD_CLOEXEC flag is set for the new descriptor if it
  // is present for the source one. That's in contrast to POSIX dup() that
  // doesn't copy file descriptor flags. Also note that duplicating descriptor
  // and setting the flag is not an atomic operation generally, but it is in
  // regards to child process spawning (to prevent file descriptor leakage into
  // a child process).
  //
  // Note that on Windows the _O_NOINHERIT flag is set for the new descriptor
  // if it is present for the source one. That's in contrast to Windows _dup()
  // that doesn't copy the flag. Also note that duplicating descriptor and
  // setting the flag is not an atomic operation generally, but it is in
  // regards to child process spawning (to prevent file descriptor leakage into
  // a child process).
  //
  LIBBUTL_SYMEXPORT auto_fd
  fddup (int fd);

  // Set the translation and/or blocking modes for the file descriptor. Throw
  // invalid_argument for an invalid combination of flags. Return the previous
  // mode on success, throw ios::failure otherwise.
  //
  // The text and binary flags are mutually exclusive on Windows. On POSIX
  // system the two modes are the same and so no check is performed.
  //
  // The blocking and non-blocking flags are mutually exclusive. In contrast
  // to POSIX systems, on Windows the non-blocking mode is only supported for
  // pipes, with the blocking mode assumed for other file descriptors
  // regardless of the flags.
  //
  // Note that on Wine currently pipes always behave as blocking regardless of
  // the mode set.
  //
  LIBBUTL_SYMEXPORT fdstream_mode
  fdmode (int, fdstream_mode);

  // Portable functions for obtaining file descriptors of standard streams.
  // Throw ios::failure on the underlying OS error.
  //
  // Note that you normally wouldn't want to close them using fddup() to
  // convert them to auto_fd, for example:
  //
  // ifdstream is (fddup (stdin_fd ()));
  //
  LIBBUTL_SYMEXPORT int stdin_fd  ();
  LIBBUTL_SYMEXPORT int stdout_fd ();
  LIBBUTL_SYMEXPORT int stderr_fd ();

  // Convenience functions for setting the translation mode for standard
  // streams.
  //
  LIBBUTL_SYMEXPORT fdstream_mode stdin_fdmode  (fdstream_mode);
  LIBBUTL_SYMEXPORT fdstream_mode stdout_fdmode (fdstream_mode);
  LIBBUTL_SYMEXPORT fdstream_mode stderr_fdmode (fdstream_mode);

  // Low-level, nothrow file descriptor API.
  //

  // Close the file descriptor. Return true on success, set errno and return
  // false otherwise.
  //
  LIBBUTL_SYMEXPORT bool
  fdclose (int) noexcept;

  // Open the null device (e.g., /dev/null) that discards all data written to
  // it and provides no data for read operations (i.e., yelds EOF on read).
  // Return an auto_fd that holds its file descriptor on success and throwing
  // ios::failure otherwise.
  //
  // On Windows the null device is NUL and writing anything substantial to it
  // (like redirecting a process' output) is extremely slow, as in, an order
  // of magnitude slower than writing to disk. If you are using the descriptor
  // yourself this can be mitigated by setting the binary mode (already done
  // by fdopen()) and using a buffer of around 64K. However, sometimes you
  // have no control of how the descriptor will be used. For instance, it can
  // be used to redirect a child's stdout and the way the child sets up its
  // stdout is out of your control (on Windows). For such cases, there is an
  // emulation via a temporary file. Mostly it functions as a proper null
  // device with the file automatically removed once the descriptor is
  // closed. One difference, however, would be if you were to both write to
  // and read from the descriptor.
  //
  // Note that on POSIX the FD_CLOEXEC flag is set for the file descriptor to
  // prevent its leakage into child processes. On Windows, for the same
  // purpose, the _O_NOINHERIT flag is set.
  //
#ifndef _WIN32
  LIBBUTL_SYMEXPORT auto_fd
  fdopen_null ();
#else
  LIBBUTL_SYMEXPORT auto_fd
  fdopen_null (bool temp = false);
#endif

  struct fdpipe
  {
    auto_fd in;
    auto_fd out;

    void
    close ()
    {
      in.close ();
      out.close ();
    }
  };

  // Create a pipe. Throw ios::failure on the underlying OS error. By default
  // both ends of the pipe are opened in the text mode. Pass the binary flag
  // to instead open them in the binary mode. Passing a mode other than none
  // or binary is illegal.
  //
  // Note that on Windows both ends of the created pipe are not inheritable.
  // In particular, the process class that uses fdpipe underneath makes the
  // appropriate end (the one being passed to the child) inheritable.
  //
  // Note that on POSIX the FD_CLOEXEC flag is set for both ends, so they get
  // automatically closed by the child process to prevent undesired behaviors
  // (such as child deadlock on read from a pipe due to the write-end leakage
  // into the child process). Opening a pipe and setting the flag is not an
  // atomic operation generally, but it is in regards to child process spawning
  // (to prevent file descriptor leakage into child processes spawned from
  // other threads). Also note that you don't need to reset the flag for a pipe
  // end being passed to the process class ctor.
  //
  LIBBUTL_SYMEXPORT fdpipe
  fdopen_pipe (fdopen_mode = fdopen_mode::none);

  // Seeking.
  //
  enum class fdseek_mode {set, cur, end};

  LIBBUTL_SYMEXPORT std::uint64_t
  fdseek (int, std::int64_t, fdseek_mode);

  // Truncate or expand the file to the specified size. Throw ios::failure on
  // the underlying OS error.
  //
  LIBBUTL_SYMEXPORT void
  fdtruncate (int, std::uint64_t);

  // Return filesystem entry stat from file descriptor. Throw ios::failure on
  // the underlying OS error.
  //
  // See also path_entry() in filesystem.
  //
  LIBBUTL_SYMEXPORT entry_stat
  fdstat (int);

  // Test whether a file descriptor refers to a terminal. Throw ios::failure
  // on the underlying OS error.
  //
  LIBBUTL_SYMEXPORT bool
  fdterm (int);

  // Wait until one or more file descriptors becomes ready for input (reading)
  // or output (writing). Return the pair of numbers of descriptors that are
  // ready. Throw std::invalid_argument if anything is wrong with arguments
  // (both sets are empty, invalid fd, etc). Throw ios::failure on the
  // underlying OS error.
  //
  // Note that the function clears all the previously-ready entries on each
  // call. Entries with nullfd are ignored.
  //
  // On Windows only pipes and only their input (read) ends are supported.
  //
  struct fdselect_state
  {
    int  fd;
    bool ready;

    // Note: intentionally non-explicit to allow implicit initialization when
    // pushing to fdselect_set.
    //
    fdselect_state (int fd): fd (fd), ready (false) {}
  };

  using fdselect_set = small_vector<fdselect_state, 4>;

  LIBBUTL_SYMEXPORT std::pair<std::size_t, std::size_t>
  fdselect (fdselect_set& ifds, fdselect_set& ofds);

  inline std::size_t
  ifdselect (fdselect_set& ifds)
  {
    fdselect_set ofds;
    return fdselect (ifds, ofds).first;
  }

  inline std::size_t
  ofdselect (fdselect_set& ofds)
  {
    fdselect_set ifds;
    return fdselect (ifds, ofds).second;
  }

  // As above but wait up to the specified timeout returning a pair of zeroes
  // if none of the descriptors became ready.
  //
  template <typename R, typename P>
  std::pair<std::size_t, std::size_t>
  fdselect (fdselect_set&, fdselect_set&, const std::chrono::duration<R, P>&);

  template <typename R, typename P>
  inline std::size_t
  ifdselect (fdselect_set& ifds, const std::chrono::duration<R, P>& timeout)
  {
    fdselect_set ofds;
    return fdselect (ifds, ofds, timeout).first;
  }

  template <typename R, typename P>
  inline std::size_t
  ofdselect (fdselect_set& ofds, const std::chrono::duration<R, P>& timeout)
  {
    fdselect_set ifds;
    return fdselect (ifds, ofds, timeout).second;
  }

  // POSIX read() function wrapper. In particular, it supports the semantics
  // of non-blocking read for pipes on Windows.
  //
  // Note that on Wine currently pipes always behave as blocking regardless of
  // the mode.
  //
  LIBBUTL_SYMEXPORT std::streamsize
  fdread (int, void*, std::size_t);
}

#include <libbutl/fdstream.ixx>