From 61377c582e0f2675baa5f5e6e30a35d1a4164b33 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Mon, 1 May 2017 16:08:43 +0300 Subject: Add hxx extension for headers and lib prefix for library dir --- libbutl/fdstream.cxx | 1085 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1085 insertions(+) create mode 100644 libbutl/fdstream.cxx (limited to 'libbutl/fdstream.cxx') diff --git a/libbutl/fdstream.cxx b/libbutl/fdstream.cxx new file mode 100644 index 0000000..77e48e0 --- /dev/null +++ b/libbutl/fdstream.cxx @@ -0,0 +1,1085 @@ +// file : libbutl/fdstream.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#ifndef _WIN32 +# include // open(), O_*, fcntl() +# include // close(), read(), write(), lseek(), dup(), pipe(), + // ssize_t, STD*_FILENO +# include // writev(), iovec +# include // S_I* +# include // off_t +#else +# include + +# include // _close(), _read(), _write(), _setmode(), _sopen(), + // _lseek(), _dup(), _pipe(), _get_osfhandle() +# include // _SH_DENYNO +# include // _fileno(), stdin, stdout, stderr +# include // _O_* +# include // S_I* +#endif + +#include // errno, E* + +#include // ios_base::openmode, ios_base::failure +#include // bad_alloc +#include // numeric_limits +#include +#include // memcpy(), memmove() +#include // uncaught_exception() +#include // invalid_argument +#include +#include + +#include + +using namespace std; + +#ifdef _WIN32 +using namespace butl::win32; +#endif + +namespace butl +{ + // throw_ios_failure + // + template + [[noreturn]] static inline typename enable_if::type + throw_ios_failure (error_code e, const char* m) + { + // The idea here is to make an error code to be saved into failure + // exception and to make a string returned by what() to contain the error + // description plus an optional custom message if provided. Unfortunatelly + // there is no way to say that the custom message is absent. Passing an + // empty string results for libstdc++ (as of version 5.3.1) with a + // description like this (note the ': ' prefix): + // + // : No such file or directory + // + // Note that our custom operator<<(ostream, exception) strips this prefix. + // + throw ios_base::failure (m != nullptr ? m : "", e); + } + + template + [[noreturn]] static inline typename enable_if::type + throw_ios_failure (error_code e, const char* m) + { + throw ios_base::failure (m != nullptr ? m : e.message ().c_str ()); + } + + // Throw system_error with generic_category. + // + [[noreturn]] static inline void + throw_ios_failure (int errno_code, const char* m = nullptr) + { + error_code ec (errno_code, generic_category ()); + throw_ios_failure::value> ( + ec, m); + } + +#ifdef _WIN32 + // Throw system_error with system_category. + // + static inline void + throw_ios_system_failure (int system_code) + { + // Here we work around MinGW libstdc++ that interprets Windows system error + // codes (for example those returned by GetLastError()) as errno codes. + // + // Note that the resulting system_error description will have ': Success.' + // suffix that is stripped by our custom operator<<(ostream, exception). + // + error_code ec (0, system_category ()); + string m (win32::error_msg (system_code)); + + throw_ios_failure::value> ( + ec, m.c_str ()); + } +#endif + + // auto_fd + // + void auto_fd:: + close () + { + if (fd_ >= 0) + { + bool r (fdclose (fd_)); + + // If fdclose() failed then no reason to expect it to succeed the next + // time. + // + fd_ = -1; + + if (!r) + throw_ios_failure (errno); + } + } + + // fdbuf + // + fdbuf:: + fdbuf (auto_fd&& fd) + { + if (fd.get () >= 0) + open (move (fd)); + } + + void fdbuf:: + open (auto_fd&& fd) + { + close (); + +#ifndef _WIN32 + int flags (fcntl (fd.get (), F_GETFL)); + + if (flags == -1) + throw_ios_failure (errno); + + non_blocking_ = (flags & O_NONBLOCK) == O_NONBLOCK; +#endif + + setg (buf_, buf_, buf_); + setp (buf_, buf_ + sizeof (buf_) - 1); // Keep space for overflow's char. + + fd_ = move (fd); + } + + streamsize fdbuf:: + showmanyc () + { + if (!is_open ()) + return -1; + + streamsize n (egptr () - gptr ()); + + if (n > 0) + return n; + +#ifndef _WIN32 + if (non_blocking_) + { + ssize_t n (read (fd_.get (), buf_, sizeof (buf_))); + + if (n == -1) + { + if (errno == EAGAIN || errno == EINTR) + return 0; + + throw_ios_failure (errno); + } + + if (n == 0) // EOF. + return -1; + + setg (buf_, buf_, buf_ + n); + return n; + } +#endif + + return 0; + } + + fdbuf::int_type fdbuf:: + underflow () + { + int_type r (traits_type::eof ()); + + if (is_open ()) + { + // The underflow() function interface doesn't support the non-blocking + // semantics as it must return either the next character or EOF. In the + // future we may implement the blocking behavior for a non-blocking file + // descriptor. + // + if (non_blocking_) + throw_ios_failure (ENOTSUP); + + if (gptr () < egptr () || load ()) + r = traits_type::to_int_type (*gptr ()); + } + + return r; + } + + bool fdbuf:: + load () + { + // Doesn't handle blocking mode and so should not be called. + // + assert (!non_blocking_); + +#ifndef _WIN32 + ssize_t n (read (fd_.get (), buf_, sizeof (buf_))); +#else + int n (_read (fd_.get (), buf_, sizeof (buf_))); +#endif + + if (n == -1) + throw_ios_failure (errno); + + setg (buf_, buf_, buf_ + n); + return n != 0; + } + + fdbuf::int_type fdbuf:: + overflow (int_type c) + { + int_type r (traits_type::eof ()); + + if (is_open () && c != traits_type::eof ()) + { + // The overflow() function interface doesn't support the non-blocking + // semantics since being unable to serialize the character is supposed + // to be an error. In the future we may implement the blocking behavior + // for a non-blocking file descriptor. + // + if (non_blocking_) + throw_ios_failure (ENOTSUP); + + // Store last character in the space we reserved in open(). Note + // that pbump() doesn't do any checks. + // + *pptr () = traits_type::to_char_type (c); + pbump (1); + + if (save ()) + r = c; + } + + return r; + } + + int fdbuf:: + sync () + { + if (!is_open ()) + return -1; + + // The sync() function interface doesn't support the non-blocking + // semantics since it should either completely sync the data or fail. In + // the future we may implement the blocking behavior for a non-blocking + // file descriptor. + // + if (non_blocking_) + throw_ios_failure (ENOTSUP); + + return save () ? 0 : -1; + } + + bool fdbuf:: + save () + { + size_t n (pptr () - pbase ()); + + 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_.get (), buf_, n)); +#else + int m (_write (fd_.get (), buf_, n)); +#endif + + if (m == -1) + throw_ios_failure (errno); + + if (n != static_cast (m)) + return false; + + setp (buf_, buf_ + sizeof (buf_) - 1); + } + + return true; + } + + streamsize fdbuf:: + xsputn (const char_type* s, streamsize sn) + { + // The xsputn() function interface doesn't support the non-blocking + // semantics since the only excuse not to fully serialize the data is + // encountering EOF (the default behaviour is defined as a sequence of + // sputc() calls which stops when either sn characters are written or a + // call would have returned EOF). In the future we may implement the + // blocking behavior for a non-blocking file descriptor. + // + if (non_blocking_) + throw_ios_failure (ENOTSUP); + + // To avoid futher 'signed/unsigned comparison' compiler warnings. + // + size_t n (static_cast (sn)); + + // Buffer the data if there is enough space. + // + size_t an (epptr () - pptr ()); // Amount of free space in the buffer. + if (n <= an) + { + memcpy (pptr (), s, n); + pbump (n); + return n; + } + + size_t bn (pptr () - pbase ()); // Buffered data size. + +#ifndef _WIN32 + + ssize_t r; + if (bn > 0) + { + // Write both buffered and new data with a single system call. + // + iovec iov[2] = {{pbase (), bn}, {const_cast (s), n}}; + r = writev (fd_.get (), iov, 2); + } + else + r = write (fd_.get (), s, n); + + if (r == -1) + throw_ios_failure (errno); + + size_t m (static_cast (r)); + + // If the buffered data wasn't fully written then move the unwritten part + // to the beginning of the buffer. + // + if (m < bn) + { + memmove (pbase (), pbase () + m, bn - m); + pbump (-m); // Note that pbump() accepts negatives. + return 0; + } + + setp (buf_, buf_ + sizeof (buf_) - 1); + return m - bn; + +#else + + // On Windows there is no writev() available so sometimes we will make two + // system calls. Fill and flush the buffer, then try to fit the data tail + // into the empty buffer. If the data tail is too long then just write it + // to the file and keep the buffer empty. + // + // We will end up with two _write() calls if the total data size to be + // written exceeds double the buffer size. In this case the buffer filling + // is redundant so let's pretend there is no free space in the buffer, and + // so buffered and new data will be written separatelly. + // + if (bn + n > 2 * (bn + an)) + an = 0; + else + { + memcpy (pptr (), s, an); + pbump (an); + } + + // Flush the buffer. + // + size_t wn (bn + an); + int r (wn > 0 ? _write (fd_.get (), buf_, wn) : 0); + + if (r == -1) + throw_ios_failure (errno); + + size_t m (static_cast (r)); + + // If the buffered data wasn't fully written then move the unwritten part + // to the beginning of the buffer. + // + if (m < wn) + { + memmove (pbase (), pbase () + m, wn - m); + pbump (-m); // Note that pbump() accepts negatives. + return m < bn ? 0 : m - bn; + } + + setp (buf_, buf_ + sizeof (buf_) - 1); + + // Now 'an' holds the size of the data portion written as a part of the + // buffer flush. + // + s += an; + n -= an; + + // Buffer the data tail if it fits the buffer. + // + if (n <= static_cast (epptr () - pbase ())) + { + memcpy (pbase (), s, n); + pbump (n); + return sn; + } + + // The data tail doesn't fit the buffer so write it to the file. + // + r = _write (fd_.get (), s, n); + + if (r == -1) + throw_ios_failure (errno); + + return an + r; +#endif + } + + inline static bool + flag (fdstream_mode m, fdstream_mode flag) + { + return (m & flag) == flag; + } + + inline static auto_fd + mode (auto_fd fd, fdstream_mode m) + { + if (fd.get () >= 0 && + (flag (m, fdstream_mode::text) || + flag (m, fdstream_mode::binary) || + flag (m, fdstream_mode::blocking) || + flag (m, fdstream_mode::non_blocking))) + fdmode (fd.get (), m); + + return fd; + } + + // fdstream_base + // + fdstream_base:: + fdstream_base (auto_fd&& fd, fdstream_mode m) + : fdstream_base (mode (move (fd), m)) // Delegate. + { + } + + 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::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::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. + // + 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 + // + auto_fd + fdopen (const char* f, fdopen_mode m, permissions p) + { + mode_t pf (S_IREAD | S_IWRITE | S_IEXEC); + +#ifdef S_IRWXG + pf |= S_IRWXG; +#endif + +#ifdef S_IRWXO + pf |= S_IRWXO; +#endif + + pf &= static_cast (p); + + // Return true if the open mode contains a specific flag. + // + auto mode = [m](fdopen_mode flag) -> bool {return (m & flag) == flag;}; + + int of (0); + bool in (mode (fdopen_mode::in)); + bool out (mode (fdopen_mode::out)); + +#ifndef _WIN32 + + if (in && out) + of |= O_RDWR; + else if (in) + of |= O_RDONLY; + else if (out) + of |= O_WRONLY; + + if (out) + { + if (mode (fdopen_mode::append)) + of |= O_APPEND; + + if (mode (fdopen_mode::truncate)) + of |= O_TRUNC; + } + + if (mode (fdopen_mode::create)) + { + of |= O_CREAT; + + if (mode (fdopen_mode::exclusive)) + of |= O_EXCL; + } + +#ifdef O_LARGEFILE + of |= O_LARGEFILE; +#endif + + int fd (open (f, of | O_CLOEXEC, pf)); + +#else + + if (in && out) + of |= _O_RDWR; + else if (in) + of |= _O_RDONLY; + else if (out) + of |= _O_WRONLY; + + if (out) + { + if (mode (fdopen_mode::append)) + of |= _O_APPEND; + + if (mode (fdopen_mode::truncate)) + of |= _O_TRUNC; + } + + if (mode (fdopen_mode::create)) + { + of |= _O_CREAT; + + if (mode (fdopen_mode::exclusive)) + of |= _O_EXCL; + } + + of |= mode (fdopen_mode::binary) ? _O_BINARY : _O_TEXT; + + // According to Microsoft _sopen() should not change the permissions of an + // existing file. However it does if we pass them (reproduced on Windows + // XP, 7, and 8). And we must pass them if we have _O_CREATE. So we need + // to take care of preserving the permissions ourselves. Note that Wine's + // implementation of _sopen() works properly. + // + bool pass_perm (of & _O_CREAT); + + 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_ios_failure (EEXIST); + + of &= ~_O_CREAT; + pass_perm = false; + } + + // Make sure the file descriptor is not inheritable by default. + // + of |= _O_NOINHERIT; + + int fd (pass_perm + ? _sopen (f, of, _SH_DENYNO, pf) + : _sopen (f, of, _SH_DENYNO)); + +#endif + + if (fd == -1) + throw_ios_failure (errno); + + if (mode (fdopen_mode::at_end)) + { +#ifndef _WIN32 + bool r (lseek (fd, 0, SEEK_END) != static_cast (-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 auto_fd (fd); + } + +#ifndef _WIN32 + + auto_fd + fddup (int fd) + { + // dup() doesn't copy FD_CLOEXEC flag, so we need to do it ourselves. Note + // that the new descriptor can leak into child processes before we copy the + // flag. To prevent this we will acquire the process_spawn_mutex (see + // process-details header) prior to duplicating the descriptor. Also note + // there is dup3() (available on Linux and FreeBSD but not on Max OS) that + // takes flags, but it's usage tends to be hairy (need to preopen a dummy + // file descriptor to pass as a second argument). + // + auto dup = [fd] () -> auto_fd + { + auto_fd nfd (::dup (fd)); + if (nfd.get () == -1) + throw_ios_failure (errno); + + return nfd; + }; + + int f (fcntl (fd, F_GETFD)); + if (f == -1) + throw_ios_failure (errno); + + // If the source descriptor has no FD_CLOEXEC flag set then no flag copy is + // required (as the duplicate will have no flag by default). + // + if ((f & FD_CLOEXEC) == 0) + return dup (); + + slock l (process_spawn_mutex); + auto_fd nfd (dup ()); + + f = fcntl (nfd.get (), F_GETFD); + if (f == -1 || fcntl (nfd.get (), F_SETFD, f | FD_CLOEXEC) == -1) + throw_ios_failure (errno); + + return nfd; + } + + bool + fdclose (int fd) noexcept + { + return close (fd) == 0; + } + + auto_fd + fdnull () noexcept + { + int fd (open ("/dev/null", O_RDWR | O_CLOEXEC)); + + if (fd == -1) + throw_ios_failure (errno); + + return auto_fd (fd); + } + + fdstream_mode + fdmode (int fd, fdstream_mode m) + { + int flags (fcntl (fd, F_GETFL)); + + if (flags == -1) + throw_ios_failure (errno); + + if (flag (m, fdstream_mode::blocking) || + flag (m, fdstream_mode::non_blocking)) + { + m &= fdstream_mode::blocking | fdstream_mode::non_blocking; + + // Should be exactly one blocking mode flag specified. + // + if (m != fdstream_mode::blocking && m != fdstream_mode::non_blocking) + throw invalid_argument ("invalid blocking mode"); + + int new_flags ( + m == fdstream_mode::non_blocking + ? flags | O_NONBLOCK + : flags & ~O_NONBLOCK); + + if (fcntl (fd, F_SETFL, new_flags) == -1) + throw_ios_failure (errno); + } + + return fdstream_mode::binary | + ((flags & O_NONBLOCK) == O_NONBLOCK + ? fdstream_mode::non_blocking + : fdstream_mode::blocking); + } + + fdstream_mode + stdin_fdmode (fdstream_mode m) + { + return fdmode (STDIN_FILENO, m); + } + + fdstream_mode + stdout_fdmode (fdstream_mode m) + { + return fdmode (STDOUT_FILENO, m); + } + + fdstream_mode + stderr_fdmode (fdstream_mode m) + { + return fdmode (STDERR_FILENO, m); + } + + fdpipe + fdopen_pipe (fdopen_mode m) + { + assert (m == fdopen_mode::none || m == fdopen_mode::binary); + + // Note that the pipe file descriptors can leak into child processes before + // we set FD_CLOEXEC flag for them. To prevent this we will acquire the + // process_spawn_mutex (see process-details header) prior to creating the + // pipe. Also note there is pipe2() (available on Linux and FreeBSD but not + // on Max OS) that takes flags. + // + slock l (process_spawn_mutex); + + int pd[2]; + if (pipe (pd) == -1) + throw_ios_failure (errno); + + fdpipe r {auto_fd (pd[0]), auto_fd (pd[1])}; + + for (size_t i (0); i < 2; ++i) + { + int f (fcntl (pd[i], F_GETFD)); + if (f == -1 || fcntl (pd[i], F_SETFD, f | FD_CLOEXEC) == -1) + throw_ios_failure (errno); + } + + return r; + } + +#else + + auto_fd + fddup (int fd) + { + // _dup() doesn't copy _O_NOINHERIT flag, so we need to do it ourselves. + // Note that the new descriptor can leak into child processes before we + // copy the flag. To prevent this we will acquire the process_spawn_mutex + // (see process-details header) prior to duplicating the descriptor. + // + // We can not ammend file descriptors directly (nor obtain the flag value), + // so need to resolve them to Windows HANDLE first. Such handles are closed + // either when CloseHandle() is called for them or when _close() is called + // for the associated file descriptors. Make sure that either the original + // file descriptor or the resulting HANDLE is closed but not both of them. + // + auto handle = [] (int fd) -> HANDLE + { + HANDLE h (reinterpret_cast (_get_osfhandle (fd))); + if (h == INVALID_HANDLE_VALUE) + throw_ios_failure (errno); // EBADF (POSIX value). + + return h; + }; + + auto dup = [fd] () -> auto_fd + { + auto_fd nfd (_dup (fd)); + if (nfd.get () == -1) + throw_ios_failure (errno); + + return nfd; + }; + + DWORD f; + if (!GetHandleInformation (handle (fd), &f)) + throw_ios_system_failure (GetLastError ()); + + // If the source handle is inheritable then no flag copy is required (as + // the duplicate handle will be inheritable by default). + // + if (f & HANDLE_FLAG_INHERIT) + return dup (); + + slock l (process_spawn_mutex); + + auto_fd nfd (dup ()); + if (!SetHandleInformation (handle (nfd.get ()), HANDLE_FLAG_INHERIT, 0)) + throw_ios_system_failure (GetLastError ()); + + return nfd; + } + + bool + fdclose (int fd) noexcept + { + return _close (fd) == 0; + } + + auto_fd + fdnull (bool temp) noexcept + { + // No need to translate \r\n before sending it to void. + // + if (!temp) + { + int fd (_sopen ("nul", _O_RDWR | _O_BINARY | _O_NOINHERIT, _SH_DENYNO)); + + if (fd == -1) + throw_ios_failure (errno); + + return auto_fd (fd); + } + + try + { + // We could probably implement a Windows-specific version of getting + // the temporary file that avoid any allocations and exceptions. + // + path p (path::temp_path ("null")); // Can throw. + int fd (_sopen (p.string ().c_str (), + (_O_CREAT | + _O_RDWR | + _O_BINARY | // Don't translate. + _O_TEMPORARY | // Remove on close. + _O_SHORT_LIVED | // Don't flush to disk. + _O_NOINHERIT), // Don't inherit by child process. + _SH_DENYNO, + _S_IREAD | _S_IWRITE)); + + if (fd == -1) + throw_ios_failure (errno); + + return auto_fd (fd); + } + catch (const bad_alloc&) + { + throw_ios_failure (ENOMEM); + } + catch (const system_error& e) + { + // Make sure that the error denotes errno portable code. + // + assert (e.code ().category () == generic_category ()); + + throw_ios_failure (e.code ().value ()); + } + } + + fdstream_mode + fdmode (int fd, fdstream_mode m) + { + m &= fdstream_mode::text | fdstream_mode::binary; + + // Should be exactly one translation flag specified. + // + // It would have been natural not to change translation mode if none of + // text or binary flags are passed. Unfortunatelly there is no (easy) way + // to obtain the current mode for the file descriptor without setting a + // new one. This is why not specifying one of the modes is an error. + // + if (m != fdstream_mode::binary && m != fdstream_mode::text) + throw invalid_argument ("invalid translation mode"); + + // Note that _setmode() preserves the _O_NOINHERIT flag value. + // + int r (_setmode (fd, m == fdstream_mode::binary ? _O_BINARY : _O_TEXT)); + if (r == -1) + throw_ios_failure (errno); // EBADF or EINVAL (POSIX values). + + return fdstream_mode::blocking | + ((r & _O_BINARY) == _O_BINARY + ? fdstream_mode::binary + : fdstream_mode::text); + } + + fdstream_mode + stdin_fdmode (fdstream_mode m) + { + int fd (_fileno (stdin)); + if (fd == -1) + throw_ios_failure (errno); + + return fdmode (fd, m); + } + + fdstream_mode + stdout_fdmode (fdstream_mode m) + { + int fd (_fileno (stdout)); + if (fd == -1) + throw_ios_failure (errno); + + return fdmode (fd, m); + } + + fdstream_mode + stderr_fdmode (fdstream_mode m) + { + int fd (_fileno (stderr)); + if (fd == -1) + throw_ios_failure (errno); + + return fdmode (fd, m); + } + + fdpipe + fdopen_pipe (fdopen_mode m) + { + assert (m == fdopen_mode::none || m == fdopen_mode::binary); + + int pd[2]; + if (_pipe ( + pd, + 64 * 1024, // Set buffer size to 64K. + _O_NOINHERIT | (m == fdopen_mode::none ? _O_TEXT : _O_BINARY)) == -1) + throw_ios_failure (errno); + + return {auto_fd (pd[0]), auto_fd (pd[1])}; + } +#endif +} -- cgit v1.1