// file : butl/fdstream -*- C++ -*- // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef BUTL_FDSTREAM #define BUTL_FDSTREAM #include <string> #include <istream> #include <ostream> #include <cstdint> // uint16_t #include <butl/export> #include <butl/path> #include <butl/filesystem> // permissions namespace butl { // 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 // - no support for put back // - throws ios::failure in case of open()/read()/write()/close() errors // - exception mask has at least badbit // - after catching an exception caused by badbit the stream is no longer // used // - not movable, though can be easily supported // class LIBBUTL_EXPORT fdbuf: public std::basic_streambuf<char> { public: virtual ~fdbuf (); fdbuf () = default; fdbuf (int fd) {open (fd);} fdbuf (const fdbuf&) = delete; fdbuf& operator= (const fdbuf&) = delete; void close (); void open (int fd); bool is_open () const {return fd_ != -1;} public: using int_type = std::basic_streambuf<char>::int_type; using traits_type = std::basic_streambuf<char>::traits_type; // basic_streambuf input interface. // public: virtual std::streamsize showmanyc (); virtual int_type underflow (); private: bool load (); // basic_streambuf output interface. // public: virtual int_type overflow (int_type); virtual int sync (); private: bool save (); private: int fd_ = -1; char buf_[4096]; }; // 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. // // 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. // enum class fdstream_mode: std::uint16_t { text = 0x01, binary = 0x02, skip = 0x04 }; 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 build 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_EXPORT fdstream_base { protected: fdstream_base () = default; fdstream_base (int fd): buf_ (fd) {} fdstream_base (int, fdstream_mode); 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 -1 as a 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. // // @@ Need to make sure performance is on par with fstream on both // Linux and Windows. // // @@ Do we need to increase default buffer size? Make it customizable? // Wonder what it is in libstdc++ and MSVC? // Note that ifdstream destructor will close an open file descriptor but // will ignore any errors. To detect such errors, call close() explicitly. // class LIBBUTL_EXPORT ifdstream: fdstream_base, public std::istream { public: // Create an unopened object with iostate = badbit | failbit (we cannot // have the iostate as an argument since it clashes with int fd) To create // an unopened object with non-default exception mask one can do: // // ifdstream (-1, ...); // ifdstream (); explicit ifdstream (int fd, iostate e = badbit | failbit); ifdstream (int fd, fdstream_mode m, iostate e = badbit | failbit); explicit ifdstream (const char*, openmode = in, iostate e = badbit | failbit); explicit ifdstream (const std::string&, openmode = in, iostate e = badbit | failbit); explicit ifdstream (const path&, openmode = in, iostate e = badbit | failbit); ifdstream (const char*, fdopen_mode, iostate e = badbit | failbit); ifdstream (const std::string&, fdopen_mode, iostate e = badbit | failbit); ifdstream (const path&, fdopen_mode, iostate e = 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 (int fd) {buf_.open (fd); clear ();} void close (); bool is_open () const {return buf_.is_open ();} 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_EXPORT ofdstream: fdstream_base, public std::ostream { public: // Create an unopened object with iostate = badbit | failbit (we cannot // have the iostate as an argument since it clashes with int fd). To create // an unopened object with non-default exception mask one can do: // // ofdstream (-1, ...); // ofdstream (); explicit ofdstream (int fd, iostate e = badbit | failbit); ofdstream (int fd, fdstream_mode m, iostate e = badbit | failbit); explicit ofdstream (const char*, openmode = out, iostate e = badbit | failbit); explicit ofdstream (const std::string&, openmode = out, iostate e = badbit | failbit); explicit ofdstream (const path&, openmode = out, iostate e = badbit | failbit); ofdstream (const char*, fdopen_mode, iostate e = badbit | failbit); ofdstream (const std::string&, fdopen_mode, iostate e = badbit | failbit); ofdstream (const path&, fdopen_mode, iostate e = 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 (int fd) {buf_.open (fd); clear ();} void close () {if (is_open ()) flush (); buf_.close ();} bool is_open () const {return buf_.is_open ();} }; // 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_EXPORT ifdstream& getline (ifdstream&, std::string&, char delim = '\n'); // Open a file returning the 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. // LIBBUTL_EXPORT int fdopen (const char*, fdopen_mode, permissions = permissions::ru | permissions::wu | permissions::rg | permissions::wg | permissions::ro | permissions::wo); LIBBUTL_EXPORT int fdopen (const std::string&, fdopen_mode, permissions = permissions::ru | permissions::wu | permissions::rg | permissions::wg | permissions::ro | permissions::wo); LIBBUTL_EXPORT int fdopen (const path&, fdopen_mode, permissions = permissions::ru | permissions::wu | permissions::rg | permissions::wg | permissions::ro | permissions::wo); // Set the translation mode for the file descriptor. Return the previous // mode on success, throw ios::failure otherwise. // LIBBUTL_EXPORT fdstream_mode fdmode (int, fdstream_mode); // Convenience functions for setting the translation mode for standard // streams. // LIBBUTL_EXPORT fdstream_mode stdin_fdmode (fdstream_mode); LIBBUTL_EXPORT fdstream_mode stdout_fdmode (fdstream_mode); LIBBUTL_EXPORT 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_EXPORT 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 file descriptor on success, set errno and return -1 otherwise. // Note that it's the caller's responsibility to close the returned file // descriptor. // // 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. // #ifndef _WIN32 LIBBUTL_EXPORT int fdnull () noexcept; #else LIBBUTL_EXPORT int fdnull (bool temp = false) noexcept; #endif } #include <butl/fdstream.ixx> #endif // BUTL_FDSTREAM