diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2016-07-12 17:24:00 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2016-07-23 19:42:48 +0300 |
commit | 6c8e3f09c185d7fa4664ccd9e5c4f623a17b84cc (patch) | |
tree | 513f523dba31f275994d8152c02db82f3380c56e /butl/fdstream.cxx | |
parent | 09bedede7116961fbfb298a6a6cfa933af7af682 (diff) |
Extend fdstream
Diffstat (limited to 'butl/fdstream.cxx')
-rw-r--r-- | butl/fdstream.cxx | 331 |
1 files changed, 286 insertions, 45 deletions
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); } |