aboutsummaryrefslogtreecommitdiff
path: root/butl
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2016-07-12 17:24:00 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2016-07-23 19:42:48 +0300
commit6c8e3f09c185d7fa4664ccd9e5c4f623a17b84cc (patch)
tree513f523dba31f275994d8152c02db82f3380c56e /butl
parent09bedede7116961fbfb298a6a6cfa933af7af682 (diff)
Extend fdstream
Diffstat (limited to 'butl')
-rw-r--r--butl/fdstream310
-rw-r--r--butl/fdstream.cxx331
-rw-r--r--butl/fdstream.ixx196
-rw-r--r--butl/filesystem.cxx37
-rw-r--r--butl/pager4
-rw-r--r--butl/pager.cxx22
-rw-r--r--butl/process15
-rw-r--r--butl/process.cxx30
8 files changed, 793 insertions, 152 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)
diff --git a/butl/pager b/butl/pager
index 4b39c2e..0414e0a 100644
--- a/butl/pager
+++ b/butl/pager
@@ -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;