diff options
-rw-r--r-- | butl/fdstream | 310 | ||||
-rw-r--r-- | butl/fdstream.cxx | 331 | ||||
-rw-r--r-- | butl/fdstream.ixx | 196 | ||||
-rw-r--r-- | butl/filesystem.cxx | 37 | ||||
-rw-r--r-- | butl/pager | 4 | ||||
-rw-r--r-- | butl/pager.cxx | 22 | ||||
-rw-r--r-- | butl/process | 15 | ||||
-rw-r--r-- | butl/process.cxx | 30 | ||||
-rw-r--r-- | tests/cpfile/driver.cxx | 21 | ||||
-rw-r--r-- | tests/fdstream/driver.cxx | 192 | ||||
-rw-r--r-- | tests/link/driver.cxx | 12 | ||||
-rw-r--r-- | tests/pager/driver.cxx | 5 | ||||
-rw-r--r-- | tests/process/driver.cxx | 26 |
13 files changed, 1012 insertions, 189 deletions
diff --git a/butl/fdstream b/butl/fdstream index 23cc58c..bb14f25 100644 --- a/butl/fdstream +++ b/butl/fdstream @@ -5,24 +5,37 @@ #ifndef BUTL_FDSTREAM #define BUTL_FDSTREAM +#include <string> #include <istream> #include <ostream> #include <cstdint> // uint16_t #include <butl/path> -#include <butl/filesystem> // permissions +#include <butl/filesystem> // permissions namespace butl { - // An iostream that is initialized with a file descriptor rather than - // a file name. + // 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 std::system_error in case of a read()/write() error + // - 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 fdbuf: public std::basic_streambuf<char> @@ -80,81 +93,262 @@ namespace butl char buf_[4096]; }; - // File descriptor translation mode. It has the same semantics as the - // binary/text opening modes in std::fstream. Specifically, this is a - // noop for POSIX systems where the two modes are the same. + // 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. // - enum class fdtranslate + // 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, - binary + text = 0x01, + binary = 0x02, + skip = 0x04 }; + fdstream_mode operator& (fdstream_mode, fdstream_mode); + fdstream_mode operator| (fdstream_mode, fdstream_mode); + fdstream_mode operator&= (fdstream_mode&, fdstream_mode); + 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. + }; + + fdopen_mode operator& (fdopen_mode, fdopen_mode); + fdopen_mode operator| (fdopen_mode, fdopen_mode); + fdopen_mode operator&= (fdopen_mode&, fdopen_mode); + fdopen_mode operator|= (fdopen_mode&, fdopen_mode); + class fdstream_base { protected: fdstream_base () = default; fdstream_base (int fd): buf_ (fd) {} - fdstream_base (int, fdtranslate); + fdstream_base (int, fdstream_mode); protected: fdbuf buf_; }; - // Note that the destructor may throw. + // 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 ifdstream: fdstream_base, public std::istream { public: - ifdstream (): std::istream (&buf_) {} - ifdstream (int fd): fdstream_base (fd), std::istream (&buf_) {} - ifdstream (int fd, fdtranslate m) - : fdstream_base (fd, m), std::istream (&buf_) {} + // 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 close () {buf_.close ();} - void open (int fd) {buf_.open (fd);} + 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 the destructor flushes the stream and may throw. + // 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 ofdstream: fdstream_base, public std::ostream { public: - ofdstream (): std::ostream (&buf_) {} - ofdstream (int fd): fdstream_base (fd), std::ostream (&buf_) {} - ofdstream (int fd, fdtranslate m) - : fdstream_base (fd, m), std::ostream (&buf_) {} + // 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 (); - ~ofdstream () override {if (is_open () && good ()) buf_.sync ();} + explicit + ofdstream (int fd, iostate e = badbit | failbit); + ofdstream (int fd, fdstream_mode m, iostate e = badbit | failbit); - void close () {flush (); buf_.close ();} - void open (int fd) {buf_.open (fd);} - bool is_open () const {return buf_.is_open ();} - }; + explicit + ofdstream (const char*, + openmode = out, + iostate e = badbit | failbit); - // 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. + explicit + ofdstream (const std::string&, + openmode = out, + iostate e = badbit | failbit); - none = 0 // Usefull when build the mode incrementally. + 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 ();} }; - fdopen_mode operator& (fdopen_mode, fdopen_mode); - fdopen_mode operator| (fdopen_mode, fdopen_mode); - fdopen_mode operator&= (fdopen_mode&, fdopen_mode); - fdopen_mode operator|= (fdopen_mode&, fdopen_mode); + // 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. + // + ifdstream& + getline (ifdstream&, std::string&, char delim = '\n'); // Open a file returning the file descriptor on success and throwing - // std::system_error otherwise. + // 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 @@ -170,6 +364,20 @@ namespace butl // Windows permissions other than ru and wu are unlikelly to have effect. // int + fdopen (const char*, + fdopen_mode, + permissions = permissions::ru | permissions::wu | + permissions::rg | permissions::wg | + permissions::ro | permissions::wo); + + int + fdopen (const std::string&, + fdopen_mode, + permissions = permissions::ru | permissions::wu | + permissions::rg | permissions::wg | + permissions::ro | permissions::wo); + + int fdopen (const path&, fdopen_mode, permissions = permissions::ru | permissions::wu | @@ -177,17 +385,17 @@ namespace butl permissions::ro | permissions::wo); // Set the translation mode for the file descriptor. Return the previous - // mode on success, throw std::system_error otherwise. + // mode on success, throw ios::failure otherwise. // - fdtranslate - fdmode (int, fdtranslate); + fdstream_mode + fdmode (int, fdstream_mode); // Convenience functions for setting the translation mode for standard // streams. // - fdtranslate stdin_fdmode (fdtranslate); - fdtranslate stdout_fdmode (fdtranslate); - fdtranslate stderr_fdmode (fdtranslate); + fdstream_mode stdin_fdmode (fdstream_mode); + fdstream_mode stdout_fdmode (fdstream_mode); + fdstream_mode stderr_fdmode (fdstream_mode); // Low-level, nothrow file descriptor API. // diff --git a/butl/fdstream.cxx b/butl/fdstream.cxx index e4d11ba..e2095ba 100644 --- a/butl/fdstream.cxx +++ b/butl/fdstream.cxx @@ -5,27 +5,69 @@ #include <butl/fdstream> #ifndef _WIN32 -# include <fcntl.h> // open(), O_* -# include <unistd.h> // close(), read(), write() -# include <sys/stat.h> // S_I* +# include <fcntl.h> // open(), O_* +# include <unistd.h> // close(), read(), write(), lseek() +# include <sys/stat.h> // S_I* +# include <sys/types.h> // off_t #else -# include <io.h> // _close(), _read(), _write(), _setmode(), _sopen() +# include <io.h> // _close(), _read(), _write(), _setmode(), _sopen(), + // _lseek() # include <share.h> // _SH_DENYNO # include <stdio.h> // _fileno(), stdin, stdout, stderr # include <fcntl.h> // _O_* # include <sys/stat.h> // S_I* #endif +#include <errno.h> // errno, E* + +#include <ios> // ios_base::openmode, ios_base::failure +#include <limits> // numeric_limits +#include <cassert> +#include <exception> // uncaught_exception() +#include <stdexcept> // invalid_argument +#include <type_traits> #include <system_error> using namespace std; namespace butl { + // throw_ios_failure + // + template <bool = is_base_of<system_error, ios_base::failure>::value> + struct throw_ios + { + static void impl (error_code e, const char* m) { + throw ios_base::failure (m, e);} + }; + + template <> + struct throw_ios<false> + { + static void impl (error_code, const char* m) {throw ios_base::failure (m);} + }; + + inline void + throw_ios_failure (int ev) + { + error_code ec (ev, system_category ()); + throw_ios<>::impl (ec, ec.message ().c_str ()); + } + + inline void + throw_ios_failure (int ev, const char* m) + { + throw_ios<>::impl (error_code (ev, system_category ()), m); + } + // fdbuf // fdbuf:: - ~fdbuf () {close ();} + ~fdbuf () + { + if (is_open ()) + fdclose (fd_); // Don't check for an error as not much we can do here. + } void fdbuf:: open (int fd) @@ -42,7 +84,7 @@ namespace butl if (is_open ()) { if (!fdclose (fd_)) - throw system_error (errno, system_category ()); + throw_ios_failure (errno); fd_ = -1; } @@ -78,7 +120,7 @@ namespace butl #endif if (n == -1) - throw system_error (errno, system_category ()); + throw_ios_failure (errno); setg (buf_, buf_, buf_ + n); return n != 0; @@ -117,6 +159,10 @@ namespace butl if (n != 0) { + // Note that for MinGW GCC (5.2.0) _write() returns 0 for a file + // descriptor opened for read-only access (while -1 with errno EBADF is + // expected). This is in contrast with VC's _write() and POSIX's write(). + // #ifndef _WIN32 ssize_t m (write (fd_, buf_, n)); #else @@ -124,7 +170,7 @@ namespace butl #endif if (m == -1) - throw system_error (errno, system_category ()); + throw_ios_failure (errno); if (n != static_cast<size_t> (m)) return false; @@ -138,19 +184,188 @@ namespace butl // fdstream_base // fdstream_base:: - fdstream_base (int fd, fdtranslate m) + fdstream_base (int fd, fdstream_mode m) : fdstream_base (fd) // Delegate. { // Note that here we rely on fdstream_base() (and fdbuf() which it calls) - // to note read from the file. + // to not read from the file. + // + if (fd != -1 && + ((m & fdstream_mode::text) == fdstream_mode::text || + (m & fdstream_mode::binary) == fdstream_mode::binary)) + fdmode (fd, m); + } + + static fdopen_mode + translate_mode (ios_base::openmode m) + { + enum + { + in = ios_base::in, + out = ios_base::out, + app = ios_base::app, + bin = ios_base::binary, + trunc = ios_base::trunc, + ate = ios_base::ate + }; + + const fdopen_mode fd_in (fdopen_mode::in); + const fdopen_mode fd_out (fdopen_mode::out); + const fdopen_mode fd_inout (fdopen_mode::in | fdopen_mode::out); + const fdopen_mode fd_app (fdopen_mode::append); + const fdopen_mode fd_trunc (fdopen_mode::truncate); + const fdopen_mode fd_create (fdopen_mode::create); + const fdopen_mode fd_bin (fdopen_mode::binary); + const fdopen_mode fd_ate (fdopen_mode::at_end); + + fdopen_mode r; + switch (m & ~(ate | bin)) + { + case in : r = fd_in ; break; + case out : + case out | trunc : r = fd_out | fd_trunc | fd_create ; break; + case app : + case out | app : r = fd_out | fd_app | fd_create ; break; + case out | in : r = fd_inout ; break; + case out | in | trunc : r = fd_inout | fd_trunc | fd_create ; break; + case out | in | app : + case in | app : r = fd_inout | fd_app | fd_create ; break; + + default: throw invalid_argument ("invalid open mode"); + } + + if (m & ate) + r |= fd_ate; + + if (m & bin) + r |= fd_bin; + + return r; + } + + // ifdstream + // + ifdstream:: + ifdstream (const char* f, openmode m, iostate e) + : ifdstream (f, translate_mode (m | in), e) // Delegate. + { + } + + ifdstream:: + ifdstream (const char* f, fdopen_mode m, iostate e) + : ifdstream (fdopen (f, m | fdopen_mode::in), e) // Delegate. + { + } + + ifdstream:: + ~ifdstream () + { + if (skip_ && is_open () && good ()) + { + // Clear the exception mask to prevent ignore() from throwing. + // + exceptions (goodbit); + ignore (numeric_limits<streamsize>::max ()); + } + + // Underlying file descriptor is closed by fdbuf dtor with errors (if any) + // being ignored. + // + } + + void ifdstream:: + open (const char* f, openmode m) + { + open (f, translate_mode (m | in)); + } + + void ifdstream:: + open (const char* f, fdopen_mode m) + { + open (fdopen (f, m | fdopen_mode::in)); + } + + void ifdstream:: + close () + { + if (skip_ && is_open () && good ()) + ignore (numeric_limits<streamsize>::max ()); + + buf_.close (); + } + + ifdstream& + getline (ifdstream& is, string& s, char delim) + { + ifdstream::iostate eb (is.exceptions ()); + assert (eb & ifdstream::badbit); + + // Amend the exception mask to prevent exceptions being thrown by the C++ + // IO runtime to avoid incompatibility issues due to ios_base::failure ABI + // fiasco (#66145). We will not restore the mask when ios_base::failure is + // thrown by fdbuf since there is no way to "silently" restore it if the + // corresponding bits are in the error state without the exceptions() call + // throwing ios_base::failure. Not restoring exception mask on throwing + // because of badbit should probably be ok since the stream is no longer + // usable. // - fdmode (fd, m); + if (eb != ifdstream::badbit) + is.exceptions (ifdstream::badbit); + + std::getline (is, s, delim); + + // Throw if any of the newly set bits are present in the exception mask. + // + if ((is.rdstate () & eb) != ifdstream::goodbit) + throw_ios_failure (EIO, "getline failure"); + + if (eb != ifdstream::badbit) + is.exceptions (eb); // Restore exception mask. + + return is; + } + + // ofdstream + // + ofdstream:: + ofdstream (const char* f, openmode m, iostate e) + : ofdstream (f, translate_mode (m | out), e) // Delegate. + { + } + + ofdstream:: + ofdstream (const char* f, fdopen_mode m, iostate e) + : ofdstream (fdopen (f, m | fdopen_mode::out), e) // Delegate. + { + } + + ofdstream:: + ~ofdstream () + { + // Enforce explicit close(). Note that we may have false negatives but not + // false positives. Specifically, we will fail to enforce if someone is + // using ofdstream in a dtor being called while unwinding the stack due to + // an exception. + // + assert (!is_open () || !good () || uncaught_exception ()); + } + + void ofdstream:: + open (const char* f, openmode m) + { + open (f, translate_mode (m | out)); + } + + void ofdstream:: + open (const char* f, fdopen_mode m) + { + open (fdopen (f, m | fdopen_mode::out)); } // Utility functions // int - fdopen (const path& f, fdopen_mode m, permissions p) + fdopen (const char* f, fdopen_mode m, permissions p) { mode_t pf (S_IREAD | S_IWRITE | S_IEXEC); @@ -202,7 +417,7 @@ namespace butl of |= O_LARGEFILE; #endif - int fd (open (f.string ().c_str (), of, pf)); + int fd (open (f, of, pf)); #else @@ -240,27 +455,46 @@ namespace butl // bool pass_perm (of & _O_CREAT); - if (pass_perm && file_exists (f)) + if (pass_perm && file_exists (path (f))) { // If the _O_CREAT flag is set then we need to clear it so that we can // omit the permissions. But if the _O_EXCL flag is set as well we can't // do that as fdopen() wouldn't fail as expected. // if (of & _O_EXCL) - throw system_error (EEXIST, system_category ()); + throw_ios_failure (EEXIST); of &= ~_O_CREAT; pass_perm = false; } int fd (pass_perm - ? _sopen (f.string ().c_str (), of, _SH_DENYNO, pf) - : _sopen (f.string ().c_str (), of, _SH_DENYNO)); + ? _sopen (f, of, _SH_DENYNO, pf) + : _sopen (f, of, _SH_DENYNO)); #endif if (fd == -1) - throw system_error (errno, system_category ()); + throw_ios_failure (errno); + + if (mode (fdopen_mode::at_end)) + { +#ifndef _WIN32 + bool r (lseek(fd, 0, SEEK_END) != static_cast<off_t>(-1)); +#else + bool r (_lseek(fd, 0, SEEK_END) != -1); +#endif + + // Note that in the case of an error we don't delete the newly created + // file as we have no indication if it is a new one. + // + if (!r) + { + int e (errno); + fdclose (fd); // Doesn't throw, but can change errno. + throw_ios_failure (e); + } + } return fd; } @@ -279,28 +513,28 @@ namespace butl return open ("/dev/null", O_RDWR); } - fdtranslate - fdmode (int, fdtranslate) + fdstream_mode + fdmode (int, fdstream_mode) { - return fdtranslate::binary; + return fdstream_mode::binary; } - fdtranslate - stdin_fdmode (fdtranslate) + fdstream_mode + stdin_fdmode (fdstream_mode) { - return fdtranslate::binary; + return fdstream_mode::binary; } - fdtranslate - stdout_fdmode (fdtranslate) + fdstream_mode + stdout_fdmode (fdstream_mode) { - return fdtranslate::binary; + return fdstream_mode::binary; } - fdtranslate - stderr_fdmode (fdtranslate) + fdstream_mode + stderr_fdmode (fdstream_mode) { - return fdtranslate::binary; + return fdstream_mode::binary; } #else @@ -317,44 +551,51 @@ namespace butl return _sopen ("nul", _O_RDWR, _SH_DENYNO); } - fdtranslate - fdmode (int fd, fdtranslate m) + fdstream_mode + fdmode (int fd, fdstream_mode m) { - int r (_setmode (fd, m == fdtranslate::binary ? _O_BINARY : _O_TEXT)); + m &= fdstream_mode::text | fdstream_mode::binary; + + // Should be exactly one translation flag specified. + // + if (m != fdstream_mode::binary && m != fdstream_mode::text) + throw invalid_argument ("invalid translation mode"); + + int r (_setmode (fd, m == fdstream_mode::binary ? _O_BINARY : _O_TEXT)); if (r == -1) - throw system_error (errno, system_category ()); + throw_ios_failure (errno); return (r & _O_BINARY) == _O_BINARY - ? fdtranslate::binary - : fdtranslate::text; + ? fdstream_mode::binary + : fdstream_mode::text; } - fdtranslate - stdin_fdmode (fdtranslate m) + fdstream_mode + stdin_fdmode (fdstream_mode m) { int fd (_fileno (stdin)); if (fd == -1) - throw system_error (errno, system_category ()); + throw_ios_failure (errno); return fdmode (fd, m); } - fdtranslate - stdout_fdmode (fdtranslate m) + fdstream_mode + stdout_fdmode (fdstream_mode m) { int fd (_fileno (stdout)); if (fd == -1) - throw system_error (errno, system_category ()); + throw_ios_failure (errno); return fdmode (fd, m); } - fdtranslate - stderr_fdmode (fdtranslate m) + fdstream_mode + stderr_fdmode (fdstream_mode m) { int fd (_fileno (stderr)); if (fd == -1) - throw system_error (errno, system_category ()); + throw_ios_failure (errno); return fdmode (fd, m); } diff --git a/butl/fdstream.ixx b/butl/fdstream.ixx index a3ea83c..c7c329b 100644 --- a/butl/fdstream.ixx +++ b/butl/fdstream.ixx @@ -2,8 +2,204 @@ // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include <cassert> + namespace butl { + // ifdstream + // + inline ifdstream:: + ifdstream () + : std::istream (&buf_) + { + exceptions (badbit | failbit); + } + + inline ifdstream:: + ifdstream (int fd, iostate e) + : fdstream_base (fd), std::istream (&buf_) + { + assert (e & badbit); + exceptions (e); + } + + inline ifdstream:: + ifdstream (int fd, fdstream_mode m, iostate e) + : fdstream_base (fd, m), + std::istream (&buf_), + skip_ ((m & fdstream_mode::skip) == fdstream_mode::skip) + { + assert (e & badbit); + exceptions (e); + } + + inline ifdstream:: + ifdstream (const std::string& f, openmode m, iostate e) + : ifdstream (f.c_str (), m, e) // Delegate. + { + } + + inline ifdstream:: + ifdstream (const path& f, openmode m, iostate e) + : ifdstream (f.string (), m, e) // Delegate. + { + } + + inline ifdstream:: + ifdstream (const std::string& f, fdopen_mode m, iostate e) + : ifdstream (f.c_str (), m, e) // Delegate. + { + } + + inline ifdstream:: + ifdstream (const path& f, fdopen_mode m, iostate e) + : ifdstream (f.string (), m, e) // Delegate. + { + } + + inline void ifdstream:: + open (const std::string& f, openmode m) + { + open (f.c_str (), m); + } + + inline void ifdstream:: + open (const path& f, openmode m) + { + open (f.string (), m); + } + + inline void ifdstream:: + open (const std::string& f, fdopen_mode m) + { + open (f.c_str (), m); + } + + inline void ifdstream:: + open (const path& f, fdopen_mode m) + { + open (f.string (), m); + } + + // ofdstream + // + inline ofdstream:: + ofdstream () + : std::ostream (&buf_) + { + exceptions (badbit | failbit); + } + + inline ofdstream:: + ofdstream (int fd, iostate e) + : fdstream_base (fd), std::ostream (&buf_) + { + assert (e & badbit); + exceptions (e); + } + + inline ofdstream:: + ofdstream (int fd, fdstream_mode m, iostate e) + : fdstream_base (fd, m), std::ostream (&buf_) + { + assert (e & badbit); + exceptions (e); + } + + inline ofdstream:: + ofdstream (const std::string& f, openmode m, iostate e) + : ofdstream (f.c_str (), m, e) // Delegate. + { + } + + inline ofdstream:: + ofdstream (const path& f, openmode m, iostate e) + : ofdstream (f.string (), m, e) // Delegate. + { + } + + inline ofdstream:: + ofdstream (const std::string& f, fdopen_mode m, iostate e) + : ofdstream (f.c_str (), m, e) // Delegate. + { + } + + inline ofdstream:: + ofdstream (const path& f, fdopen_mode m, iostate e) + : ofdstream (f.string (), m, e) // Delegate. + { + } + + inline void ofdstream:: + open (const std::string& f, openmode m) + { + open (f.c_str (), m); + } + + inline void ofdstream:: + open (const path& f, openmode m) + { + open (f.string (), m); + } + + inline void ofdstream:: + open (const std::string& f, fdopen_mode m) + { + open (f.c_str (), m); + } + + inline void ofdstream:: + open (const path& f, fdopen_mode m) + { + open (f.string (), m); + } + + // fdopen() + // + inline int + fdopen (const std::string& f, fdopen_mode m, permissions p) + { + return fdopen (f.c_str (), m, p); + } + + inline int + fdopen (const path& f, fdopen_mode m, permissions p) + { + return fdopen (f.string (), m, p); + } + + // fdstream_mode + // + inline fdstream_mode + operator& (fdstream_mode x, fdstream_mode y) + { + return x &= y; + } + + inline fdstream_mode + operator| (fdstream_mode x, fdstream_mode y) + { + return x |= y; + } + + inline fdstream_mode + operator&= (fdstream_mode& x, fdstream_mode y) + { + return x = static_cast<fdstream_mode> ( + static_cast<std::uint16_t> (x) & + static_cast<std::uint16_t> (y)); + } + + inline fdstream_mode + operator|= (fdstream_mode& x, fdstream_mode y) + { + return x = static_cast<fdstream_mode> ( + static_cast<std::uint16_t> (x) | + static_cast<std::uint16_t> (y)); + } + + // fdopen_mode + // inline fdopen_mode operator& (fdopen_mode x, fdopen_mode y) {return x &= y;} inline fdopen_mode operator| (fdopen_mode x, fdopen_mode y) {return x |= y;} diff --git a/butl/filesystem.cxx b/butl/filesystem.cxx index eab62dc..cabe306 100644 --- a/butl/filesystem.cxx +++ b/butl/filesystem.cxx @@ -227,13 +227,7 @@ namespace butl cpfile (const path& from, const path& to, cpflags fl) { permissions perm (path_permissions (from)); - - // We do not enable exceptions to be thrown when badbit/failbit are set for - // the ifs stream nor check the bits manually down the road as there is no - // input functions being called for the stream. Input functions are called - // directly for the input stream buffer, which is our fdbuf that throws. - // - ifdstream ifs (fdopen (from, fdopen_mode::in | fdopen_mode::binary)); + ifdstream ifs (from, fdopen_mode::binary); fdopen_mode om (fdopen_mode::out | fdopen_mode::truncate | @@ -249,34 +243,15 @@ namespace butl auto_rmfile rm; ofdstream ofs (fdopen (to, om, perm)); - rm = auto_rmfile (to); - // Setting badbit for the ofs stream is the only way to make sure the - // original std::system_error, that is thrown on the output operation - // failure, is retrown by the output stream's operator<<() and flush() - // calls (the latter is called from close()). Note that both of the - // functions behave as UnformattedOutputFunction. - // - ofs.exceptions (ofdstream::badbit); + rm = auto_rmfile (to); - // If the output operation ends up with the badbit set for a reason other - // than std::system_error being thrown (by fdbuf), then ofdstream::failure - // will be thrown instead. We need to convert it to std::system_error to - // comply with the cpfile() interface specification. + // Throws ios::failure on fdbuf read/write failures. // - try - { - // Throws std::system_error on fdbuf read/write failures. - // - ofs << ifs.rdbuf (); + ofs << ifs.rdbuf (); - ifs.close (); - ofs.close (); // Throws std::system_error on flush failure. - } - catch (const ofdstream::failure& e) - { - throw system_error (EIO, system_category (), e.what ()); - } + ifs.close (); // Throws ios::failure on failure. + ofs.close (); // Throws ios::failure on flush/close failure. if ((fl & cpflags::overwrite_permissions) == cpflags::overwrite_permissions) @@ -45,7 +45,7 @@ namespace butl class pager: protected std::streambuf { public: - ~pager () {wait ();} + ~pager () {wait (true);} // If verbose is true, then print (to STDERR) the pager command line. // @@ -58,7 +58,7 @@ namespace butl stream () {return os_.is_open () ? os_ : std::cout;} bool - wait (); + wait (bool ignore_errors = false); // The streambuf output interface that implements indentation. You can // override it to implement custom output pre-processing. diff --git a/butl/pager.cxx b/butl/pager.cxx index 0f29bb4..3d429f5 100644 --- a/butl/pager.cxx +++ b/butl/pager.cxx @@ -5,20 +5,20 @@ #include <butl/pager> #ifndef _WIN32 -# include <unistd.h> // close(), STDOUT_FILENO +# include <unistd.h> // STDOUT_FILENO # include <sys/ioctl.h> // ioctl() # include <chrono> # include <thread> // this_thread::sleep_for() #else # include <butl/win32-utility> - -# include <io.h> // _close() #endif #include <cstring> // strchr() #include <system_error> +#include <butl/fdstream> // fdclose() + using namespace std; namespace butl @@ -130,11 +130,8 @@ namespace butl bool r; if (p_.try_wait (r)) { -#ifndef _WIN32 - close (p_.out_fd); -#else - _close (p_.out_fd); -#endif + fdclose (p_.out_fd); + if (pager != nullptr) throw system_error (ECHILD, system_category ()); } @@ -162,7 +159,7 @@ namespace butl } bool pager:: - wait () + wait (bool ie) { // Teardown the indentation machinery. // @@ -172,8 +169,13 @@ namespace butl buf_ = nullptr; } + // Prevent ofdstream::close() from throwing in the ignore errors mode. + // + if (ie) + os_.exceptions (ofdstream::goodbit); + os_.close (); - return p_.wait (); + return p_.wait (ie); } pager::int_type pager:: diff --git a/butl/process b/butl/process index 54a8dae..e864b53 100644 --- a/butl/process +++ b/butl/process @@ -88,13 +88,13 @@ namespace butl process (const char* cwd, char const* const[], process&, int = 1, int = 2); // Wait for the process to terminate. Return true if the process - // terminated normally and with the zero exit status. Throw - // process_error if anything goes wrong. This function can be - // called multiple times with subsequent calls simply returning - // the status. + // terminated normally and with the zero exit status. Unless ignore_error + // is true, throw process_error if anything goes wrong. This function can + // be called multiple times with subsequent calls simply returning the + // status. // bool - wait (); + wait (bool ignore_errors = false); // Return true if the process has already terminated in which case // the argument is set to the result of wait(). @@ -102,7 +102,10 @@ namespace butl bool try_wait (bool&); - ~process () {if (handle != 0) wait ();} + // Note that the destructor will wait for the process but will ignore + // any errors and the exit status. + // + ~process () {if (handle != 0) wait (true);} // Moveable-only type. // diff --git a/butl/process.cxx b/butl/process.cxx index e67e12e..9bb0ea2 100644 --- a/butl/process.cxx +++ b/butl/process.cxx @@ -196,7 +196,7 @@ namespace butl } bool process:: - wait () + wait (bool ie) { if (handle != 0) { @@ -204,7 +204,18 @@ namespace butl handle = 0; // We have tried. if (r == -1) - throw process_error (errno, false); + { + if (!ie) + throw process_error (errno, false); + else + // Fold into status, so this and subsequent wait() calls return + // false. There is no portable way to update the status bits + // representing a process exit code specifically. So we set all bits + // to 1 and recon on getting non-zero exit status wherever the exact + // bits are. + // + status = ~0; + } } return WIFEXITED (status) && WEXITSTATUS (status) == 0; @@ -571,7 +582,7 @@ namespace butl } bool process:: - wait () + wait (bool ie) { if (handle != 0) { @@ -584,10 +595,15 @@ namespace butl auto_handle h (handle); // Auto-deleter. handle = 0; // We have tried. - if (e != NO_ERROR) - throw process_error (error_msg (e)); - - status = s; + if (e == NO_ERROR) + status = s; + else + { + if (!ie) + throw process_error (error_msg (e)); + else + status = 1; // Fold into status. + } } return status == 0; diff --git a/tests/cpfile/driver.cxx b/tests/cpfile/driver.cxx index 669ac26..23b6fc3 100644 --- a/tests/cpfile/driver.cxx +++ b/tests/cpfile/driver.cxx @@ -2,12 +2,13 @@ // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include <ios> #include <string> #include <cassert> -#include <fstream> #include <system_error> #include <butl/path> +#include <butl/fdstream> #include <butl/filesystem> using namespace std; @@ -20,22 +21,20 @@ static const char text3[] = "XAB\r\n9"; static string from_file (const path& f) { - ifstream ifs; - ifs.exceptions (fstream::badbit | fstream::failbit); - ifs.open (f.string (), ios::binary); + ifdstream ifs (f, ios::binary); string s; getline (ifs, s, '\0'); + ifs.close (); // Not to miss failed close of the underlying file descriptor. return s; } static void to_file (const path& f, const char* s) { - ofstream ofs; - ofs.exceptions (fstream::badbit | fstream::failbit); - ofs.open (f.string (), ios::binary); + ofdstream ofs (f, ios::binary); ofs << s; + ofs.close (); } int @@ -89,7 +88,7 @@ main () cpfile (from, to, cpflags::none); assert (false); } - catch (const system_error&) + catch (const ios::failure&) { } @@ -162,11 +161,13 @@ main () cpfile (from, tslink, cpflags::none); assert (false); } - catch (const system_error&) + catch (const ios::failure&) { } - // Check that copy fail if 'from' symlink points to non-existent file. + // Check that copy fail if 'from' symlink points to non-existent file. The + // std::system_error is thrown as cpfile() fails to obtain permissions for + // the 'from' symlink target. // try { diff --git a/tests/fdstream/driver.cxx b/tests/fdstream/driver.cxx index 3972473..0c6c480 100644 --- a/tests/fdstream/driver.cxx +++ b/tests/fdstream/driver.cxx @@ -2,9 +2,11 @@ // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include <ios> #include <string> #include <cassert> -#include <system_error> +#include <sstream> +#include <exception> #include <butl/path> #include <butl/fdstream> @@ -20,20 +22,20 @@ static const string text3 ("ABCDEF\r\nXYZ"); static string from_file (const path& f, fdopen_mode m = fdopen_mode::none) { - ifdstream ifs (fdopen (f, m | fdopen_mode::in)); - ifs.exceptions (ifdstream::badbit); + ifdstream ifs (f, m, ifdstream::badbit); string s; getline (ifs, s, '\0'); + ifs.close (); // Not to miss failed close of the underlying file descriptor. return s; } static void to_file (const path& f, const string& s, fdopen_mode m = fdopen_mode::none) { - ofdstream ofs (fdopen (f, m | fdopen_mode::out)); - ofs.exceptions (ofdstream::badbit | ofdstream::failbit); + ofdstream ofs (f, m); ofs << s; + ofs.close (); } int @@ -55,7 +57,7 @@ main () fdopen (f, fdopen_mode::out); // fdopen_mode::create is missed. assert (false); } - catch (const system_error&) + catch (const ios::failure&) { } @@ -64,13 +66,45 @@ main () assert (from_file (f, fdopen_mode::create) == ""); assert (try_rmfile (f) == rmfile_status::success); + // Read from the newly created non-empty file. + // to_file (f, text1, fdopen_mode::create); assert (from_file (f) == text1); + // Check that skip on close as requested. + // + { + ifdstream ifs (fdopen (f, fdopen_mode::in), fdstream_mode::skip); + + string s; + getline (ifs, s); + assert (!ifs.eof ()); + + ifs.close (); + assert (ifs.eof ()); + } + + // Check that don't skip on close by default. + // + { + ifdstream ifs (fdopen (f, fdopen_mode::in)); + + string s; + getline (ifs, s); + assert (!ifs.eof ()); + + ifs.close (); + assert (!ifs.eof ()); + } + // Read from the file opened in R/W mode. // assert (from_file (f, fdopen_mode::out) == text1); + // Read starting from the file's end. + // + assert (from_file (f, fdopen_mode::at_end) == ""); + try { // Fail to create if the file already exists. @@ -80,7 +114,7 @@ main () assert (false); } - catch (const system_error&) + catch (const ios::failure&) { } @@ -101,6 +135,148 @@ main () to_file (f, text2, fdopen_mode::append); assert (from_file (f) == text1 + text2); + // Append to the file with the yet another way. + // + to_file (f, text1, fdopen_mode::truncate); + to_file (f, text2, fdopen_mode::at_end); + assert (from_file (f) == text1 + text2); + + // Check creating unopened ifdstream with a non-default exception mask. + // + to_file (f, "", fdopen_mode::truncate); + + { + ifdstream ifs (-1, ifdstream::badbit); + ifs.open (f); + + string s; + assert (!getline (ifs, s)); + } + + { + ifdstream ifs (-1, fdstream_mode::text, ifdstream::badbit); + ifs.open (f); + + string s; + assert (!getline (ifs, s)); + } + + // Check creating unopened ofdstream with a non-default exception mask. + // + { + ofdstream ofs (-1, ifdstream::badbit); + ofs.open (f); + + istringstream is; + ofs << is.rdbuf (); // Sets failbit if no characters is inserted. + ofs.close (); + } + + { + ofdstream ofs (-1, fdstream_mode::binary, ifdstream::badbit); + ofs.open (f); + + istringstream is; + ofs << is.rdbuf (); // Sets failbit if no characters is inserted. + ofs.close (); + } + + // Fail to write to a read-only file. + // + // Don't work well for MinGW GCC (5.2.0) that throws ios::failure, which in + // combination with libstdc++'s ios::failure ABI fiasco (#66145) make it + // impossible to properly catch in this situation. + // + try + { + { + ofdstream ofs (fdopen (f, fdopen_mode::in)); + ofs << text1; + ofs.flush (); + } + + assert (false); + } +#if !defined(_WIN32) || !defined(__GLIBCXX__) + catch (const ios::failure&) + { + } +#else + catch (const std::exception&) + { + } +#endif + + try + { + ofdstream ofs; + ofs.open (fdopen (f, fdopen_mode::in)); + ofs << text1; + ofs.close (); + + assert (false); + } +#if !defined(_WIN32) || !defined(__GLIBCXX__) + catch (const ios::failure&) + { + } +#else + catch (const std::exception&) + { + } +#endif + + // Fail to read from a write-only file. + // + try + { + ifdstream ifs (fdopen (f, fdopen_mode::out)); + ifs.peek (); + + assert (false); + } + catch (const ios::failure&) + { + } + + try + { + ifdstream ifs; + ifs.open (fdopen (f, fdopen_mode::out)); + ifs.peek (); + + assert (false); + } + catch (const ios::failure&) + { + } + + // Dtor of a not opened ofdstream doesn't terminate a program. + // + { + ofdstream ofs; + } + + // Dtor of an opened ofdstream doesn't terminate a program during the stack + // unwinding. + // + try + { + ofdstream ofs (f); + throw true; + } + catch (bool) + { + } + + // Dtor of an opened but being in a bad state ofdstream doesn't terminate a + // program. + // + { + ofdstream ofs (f, fdopen_mode::out, ofdstream::badbit); + ofs.clear (ofdstream::failbit); + } + #ifndef _WIN32 // Fail for an existing symlink to unexistent file. @@ -115,7 +291,7 @@ main () assert (false); } - catch (const system_error&) + catch (const ios::failure&) { } diff --git a/tests/link/driver.cxx b/tests/link/driver.cxx index 7ec4ac2..a32a8dc 100644 --- a/tests/link/driver.cxx +++ b/tests/link/driver.cxx @@ -4,11 +4,11 @@ #include <set> #include <cassert> -#include <fstream> #include <utility> // pair #include <system_error> #include <butl/path> +#include <butl/fdstream> #include <butl/filesystem> using namespace std; @@ -35,10 +35,9 @@ link_file (const path& target, const path& link, bool hard, bool check_content) return true; string s; - ifstream ifs; - ifs.exceptions (fstream::badbit | fstream::failbit); - ifs.open (link.string ()); + ifdstream ifs (link); ifs >> s; + ifs.close (); // Not to miss failed close of the underlying file descriptor. return s == text; } @@ -93,10 +92,9 @@ main () path fp (td / fn); { - ofstream ofs; - ofs.exceptions (fstream::badbit | fstream::failbit); - ofs.open (fp.string ()); + ofdstream ofs (fp); ofs << text; + ofs.close (); } // Create the file hard link. diff --git a/tests/pager/driver.cxx b/tests/pager/driver.cxx index cab3078..e921533 100644 --- a/tests/pager/driver.cxx +++ b/tests/pager/driver.cxx @@ -2,6 +2,7 @@ // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include <ios> // ios_base::failure #include <vector> #include <string> #include <utility> // move() @@ -104,6 +105,10 @@ public: assert (p.wait ()); } + catch (const ios_base::failure&) + { + assert (false); + } catch (const system_error&) { assert (false); diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx index 741340c..5c43fb5 100644 --- a/tests/process/driver.cxx +++ b/tests/process/driver.cxx @@ -68,13 +68,12 @@ exec (const path& p, auto bin_mode = [bin](int fd) -> int { if (bin) - fdmode (fd, fdtranslate::binary); + fdmode (fd, fdstream_mode::binary); return fd; }; ofdstream os (bin_mode (pr.out_fd)); - os.exceptions (ofdstream::badbit); copy (in.begin (), in.end (), ostream_iterator<char> (os)); os.close (); @@ -166,7 +165,6 @@ exec (const path& p, int main (int argc, const char* argv[]) -try { bool child (false); bool bin (false); @@ -201,8 +199,7 @@ try if (i != argc) { if (!child) - cerr << "usage: " << argv[0] << " [-c] [-i] [-o] [-e] [-b] [<dir>]" - << endl; + cerr << "usage: " << argv[0] << " [-c] [-b] [<dir>]" << endl; return 1; } @@ -235,9 +232,9 @@ try { if (bin) { - stdin_fdmode (fdtranslate::binary); - stdout_fdmode (fdtranslate::binary); - stderr_fdmode (fdtranslate::binary); + stdin_fdmode (fdstream_mode::binary); + stdout_fdmode (fdstream_mode::binary); + stderr_fdmode (fdstream_mode::binary); } vector<char> data @@ -317,8 +314,13 @@ try // assert (exec ( fp.leaf (), vector<char> (), false, false, false, true, fp.directory ())); -} -catch (const system_error&) -{ - assert (false); + +#ifndef _WIN32 + // Check that wait() works properly if the underlying low-level wait + // operation fails. + // + process pr; + pr.handle = reinterpret_cast<process::handle_type>(-1); + assert (!pr.wait (true) && !pr.wait (false)); +#endif } |