mirror of
https://github.com/openstreetmap/mod_tile.git
synced 2025-07-28 23:23:55 +00:00
2457 lines
74 KiB
C++
2457 lines
74 KiB
C++
// PStreams - POSIX Process I/O for C++
|
|
|
|
// Copyright (C) 2001 - 2020 Jonathan Wakely
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// (See accompanying file LICENSE_1_0.txt or copy at
|
|
// http://www.boost.org/LICENSE_1_0.txt)
|
|
//
|
|
// SPDX-License-Identifier: BSL-1.0
|
|
|
|
/**
|
|
* @file pstream.h
|
|
* @brief Declares all PStreams classes.
|
|
* @author Jonathan Wakely
|
|
*
|
|
* Defines classes redi::ipstream, redi::opstream, redi::pstream
|
|
* and redi::rpstream.
|
|
*/
|
|
|
|
#ifndef REDI_PSTREAM_H_SEEN
|
|
#define REDI_PSTREAM_H_SEEN
|
|
|
|
#include <ios>
|
|
#include <streambuf>
|
|
#include <istream>
|
|
#include <ostream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm> // for min()
|
|
#include <cerrno> // for errno
|
|
#include <cstddef> // for size_t, NULL
|
|
#include <cstdlib> // for exit()
|
|
#include <sys/types.h> // for pid_t
|
|
#include <sys/wait.h> // for waitpid()
|
|
#include <sys/ioctl.h> // for ioctl() and FIONREAD
|
|
#if defined(__sun)
|
|
# include <sys/filio.h> // for FIONREAD on Solaris 2.5
|
|
#endif
|
|
#include <unistd.h> // for pipe() fork() exec() and filedes functions
|
|
#include <signal.h> // for kill()
|
|
#include <fcntl.h> // for fcntl()
|
|
#if REDI_EVISCERATE_PSTREAMS
|
|
# include <stdio.h> // for FILE, fdopen()
|
|
#endif
|
|
|
|
|
|
/// The library version.
|
|
#define PSTREAMS_VERSION 0x0103 // 1.0.3
|
|
|
|
/**
|
|
* @namespace redi
|
|
* @brief All PStreams classes are declared in namespace redi.
|
|
*
|
|
* Like the standard iostreams, PStreams is a set of class templates,
|
|
* taking a character type and traits type. As with the standard streams
|
|
* they are most likely to be used with @c char and the default
|
|
* traits type, so typedefs for this most common case are provided.
|
|
*
|
|
* The @c pstream_common class template is not intended to be used directly,
|
|
* it is used internally to provide the common functionality for the
|
|
* other stream classes.
|
|
*/
|
|
namespace redi
|
|
{
|
|
/// Common base class providing constants and typenames.
|
|
struct pstreams
|
|
{
|
|
/// Type used to specify how to connect to the process.
|
|
typedef std::ios_base::openmode pmode;
|
|
|
|
/// Type used to hold the arguments for a command.
|
|
typedef std::vector<std::string> argv_type;
|
|
|
|
/// Type used for file descriptors.
|
|
typedef int fd_type;
|
|
|
|
static const pmode pstdin = std::ios_base::out; ///< Write to stdin
|
|
static const pmode pstdout = std::ios_base::in; ///< Read from stdout
|
|
static const pmode pstderr = std::ios_base::app; ///< Read from stderr
|
|
|
|
/// Create a new process group for the child process.
|
|
static const pmode newpg = std::ios_base::trunc;
|
|
|
|
protected:
|
|
enum {
|
|
bufsz = 32, ///< Size of pstreambuf buffers.
|
|
pbsz = 2 ///< Number of putback characters kept.
|
|
};
|
|
|
|
#if __cplusplus >= 201103L
|
|
template<typename T>
|
|
using stringable = decltype((void)std::string(std::declval<const T&>()));
|
|
#endif
|
|
};
|
|
|
|
/// Class template for stream buffer.
|
|
template <typename CharT, typename Traits = std::char_traits<CharT> >
|
|
class basic_pstreambuf
|
|
: public std::basic_streambuf<CharT, Traits>
|
|
, public pstreams
|
|
{
|
|
public:
|
|
// Type definitions for dependent types
|
|
typedef CharT char_type;
|
|
typedef Traits traits_type;
|
|
typedef typename traits_type::int_type int_type;
|
|
typedef typename traits_type::off_type off_type;
|
|
typedef typename traits_type::pos_type pos_type;
|
|
/** @deprecated use pstreams::fd_type instead. */
|
|
typedef fd_type fd_t;
|
|
|
|
/// Default constructor.
|
|
basic_pstreambuf();
|
|
|
|
/// Constructor that initialises the buffer with @a cmd.
|
|
basic_pstreambuf(const std::string& cmd, pmode mode);
|
|
|
|
/// Constructor that initialises the buffer with @a file and @a argv.
|
|
basic_pstreambuf( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode );
|
|
|
|
#if __cplusplus >= 201103L
|
|
basic_pstreambuf(basic_pstreambuf&&) noexcept;
|
|
basic_pstreambuf& operator=(basic_pstreambuf&&) noexcept;
|
|
void swap(basic_pstreambuf&) noexcept;
|
|
#endif
|
|
|
|
/// Destructor.
|
|
~basic_pstreambuf();
|
|
|
|
/// Initialise the stream buffer with @a cmd.
|
|
basic_pstreambuf*
|
|
open(const std::string& cmd, pmode mode);
|
|
|
|
/// Initialise the stream buffer with @a file and @a argv.
|
|
basic_pstreambuf*
|
|
open(const std::string& file, const argv_type& argv, pmode mode);
|
|
|
|
/// Close the stream buffer and wait for the process to exit.
|
|
basic_pstreambuf*
|
|
close();
|
|
|
|
/// Send a signal to the process.
|
|
basic_pstreambuf*
|
|
kill(int signal = SIGTERM);
|
|
|
|
/// Send a signal to the process' process group.
|
|
basic_pstreambuf*
|
|
killpg(int signal = SIGTERM);
|
|
|
|
/// Close the pipe connected to the process' stdin.
|
|
void
|
|
peof();
|
|
|
|
/// Change active input source.
|
|
bool
|
|
read_err(bool readerr = true);
|
|
|
|
/// Report whether the stream buffer has been initialised.
|
|
bool
|
|
is_open() const;
|
|
|
|
/// Report whether the process has exited.
|
|
bool
|
|
exited();
|
|
|
|
#if REDI_EVISCERATE_PSTREAMS
|
|
/// Obtain FILE pointers for each of the process' standard streams.
|
|
std::size_t
|
|
fopen(FILE*& in, FILE*& out, FILE*& err);
|
|
#endif
|
|
|
|
/// Return the exit status of the process.
|
|
int
|
|
status() const;
|
|
|
|
/// Return the error number (errno) for the most recent failed operation.
|
|
int
|
|
error() const;
|
|
|
|
protected:
|
|
/// Transfer characters to the pipe when character buffer overflows.
|
|
int_type
|
|
overflow(int_type c);
|
|
|
|
/// Transfer characters from the pipe when the character buffer is empty.
|
|
int_type
|
|
underflow();
|
|
|
|
/// Make a character available to be returned by the next extraction.
|
|
int_type
|
|
pbackfail(int_type c = traits_type::eof());
|
|
|
|
/// Write any buffered characters to the stream.
|
|
int
|
|
sync();
|
|
|
|
/// Insert multiple characters into the pipe.
|
|
std::streamsize
|
|
xsputn(const char_type* s, std::streamsize n);
|
|
|
|
/// Insert a sequence of characters into the pipe.
|
|
std::streamsize
|
|
write(const char_type* s, std::streamsize n);
|
|
|
|
/// Extract a sequence of characters from the pipe.
|
|
std::streamsize
|
|
read(char_type* s, std::streamsize n);
|
|
|
|
/// Report how many characters can be read from active input without blocking.
|
|
std::streamsize
|
|
showmanyc();
|
|
|
|
protected:
|
|
/// Enumerated type to indicate whether stdout or stderr is to be read.
|
|
enum buf_read_src { rsrc_out = 0, rsrc_err = 1 };
|
|
|
|
/// Initialise pipes and fork process.
|
|
pid_t
|
|
fork(pmode mode);
|
|
|
|
/// Wait for the child process to exit.
|
|
int
|
|
wait(bool nohang = false);
|
|
|
|
/// Return the file descriptor for the output pipe.
|
|
fd_type&
|
|
wpipe();
|
|
|
|
/// Return the file descriptor for the active input pipe.
|
|
fd_type&
|
|
rpipe();
|
|
|
|
/// Return the file descriptor for the specified input pipe.
|
|
fd_type&
|
|
rpipe(buf_read_src which);
|
|
|
|
void
|
|
create_buffers(pmode mode);
|
|
|
|
void
|
|
destroy_buffers(pmode mode);
|
|
|
|
/// Writes buffered characters to the process' stdin pipe.
|
|
bool
|
|
empty_buffer();
|
|
|
|
bool
|
|
fill_buffer(bool non_blocking = false);
|
|
|
|
/// Return the active input buffer.
|
|
char_type*
|
|
rbuffer();
|
|
|
|
buf_read_src
|
|
switch_read_buffer(buf_read_src);
|
|
|
|
private:
|
|
#if __cplusplus >= 201103L
|
|
using basic_streambuf = std::basic_streambuf<char_type, traits_type>;
|
|
#else
|
|
basic_pstreambuf(const basic_pstreambuf&);
|
|
basic_pstreambuf& operator=(const basic_pstreambuf&);
|
|
#endif
|
|
|
|
void
|
|
init_rbuffers();
|
|
|
|
pid_t ppid_; // pid of process
|
|
fd_type wpipe_; // pipe used to write to process' stdin
|
|
fd_type rpipe_[2]; // two pipes to read from, stdout and stderr
|
|
char_type* wbuffer_;
|
|
char_type* rbuffer_[2];
|
|
char_type* rbufstate_[3];
|
|
/// Index into rpipe_[] to indicate active source for read operations.
|
|
buf_read_src rsrc_;
|
|
int status_; // hold exit status of child process
|
|
int error_; // hold errno if fork() or exec() fails
|
|
};
|
|
|
|
/// Class template for common base class.
|
|
template <typename CharT, typename Traits = std::char_traits<CharT> >
|
|
class pstream_common
|
|
: virtual public std::basic_ios<CharT, Traits>
|
|
, virtual public pstreams
|
|
{
|
|
protected:
|
|
typedef basic_pstreambuf<CharT, Traits> streambuf_type;
|
|
typedef std::basic_ios<CharT, Traits> ios_type;
|
|
|
|
typedef pstreams::pmode pmode;
|
|
typedef pstreams::argv_type argv_type;
|
|
|
|
/// Default constructor.
|
|
pstream_common();
|
|
|
|
/// Constructor that initialises the stream by starting a process.
|
|
pstream_common(const std::string& cmd, pmode mode);
|
|
|
|
/// Constructor that initialises the stream by starting a process.
|
|
pstream_common(const std::string& file, const argv_type& argv, pmode mode);
|
|
|
|
/// Pure virtual destructor.
|
|
virtual
|
|
~pstream_common() = 0;
|
|
|
|
#if __cplusplus >= 201103L
|
|
pstream_common(pstream_common&& rhs) noexcept
|
|
: command_(std::move(rhs.command_))
|
|
, buf_(std::move(rhs.buf_))
|
|
{
|
|
/* derived class is responsible for ios_type::move(rhs) happening */
|
|
}
|
|
|
|
pstream_common&
|
|
operator=(pstream_common&& rhs) noexcept
|
|
{
|
|
command_ = std::move(rhs.command_);
|
|
buf_ = std::move(rhs.buf_);
|
|
return *this;
|
|
}
|
|
|
|
void
|
|
swap(pstream_common& rhs) noexcept
|
|
{
|
|
/* derived class is responsible for ios_type::swap(rhs) happening */
|
|
command_.swap(rhs.command_);
|
|
buf_.swap(rhs.buf_);
|
|
}
|
|
#endif // C++11
|
|
|
|
/// Start a process.
|
|
void
|
|
do_open(const std::string& cmd, pmode mode);
|
|
|
|
/// Start a process.
|
|
void
|
|
do_open(const std::string& file, const argv_type& argv, pmode mode);
|
|
|
|
public:
|
|
/// Close the pipe, returning the program's exit status, as
|
|
/// pclose(3) does.
|
|
int
|
|
close();
|
|
|
|
/// Report whether the stream's buffer has been initialised.
|
|
bool
|
|
is_open() const;
|
|
|
|
/// Return the command used to initialise the stream.
|
|
const std::string&
|
|
command() const;
|
|
|
|
/// Return a pointer to the stream buffer.
|
|
streambuf_type*
|
|
rdbuf() const;
|
|
|
|
#if REDI_EVISCERATE_PSTREAMS
|
|
/// Obtain FILE pointers for each of the process' standard streams.
|
|
std::size_t
|
|
fopen(FILE*& in, FILE*& out, FILE*& err);
|
|
#endif
|
|
|
|
protected:
|
|
std::string command_; ///< The command used to start the process.
|
|
streambuf_type buf_; ///< The stream buffer.
|
|
};
|
|
|
|
|
|
/**
|
|
* @class basic_ipstream
|
|
* @brief Class template for Input PStreams.
|
|
*
|
|
* Reading from an ipstream reads the command's standard output and/or
|
|
* standard error (depending on how the ipstream is opened)
|
|
* and the command's standard input is the same as that of the process
|
|
* that created the object, unless altered by the command itself.
|
|
*/
|
|
|
|
template <typename CharT, typename Traits = std::char_traits<CharT> >
|
|
class basic_ipstream
|
|
: public std::basic_istream<CharT, Traits>
|
|
, public pstream_common<CharT, Traits>
|
|
, virtual public pstreams
|
|
{
|
|
typedef std::basic_istream<CharT, Traits> istream_type;
|
|
typedef pstream_common<CharT, Traits> pbase_type;
|
|
|
|
using pbase_type::buf_; // declare name in this scope
|
|
|
|
// Ensure a basic_ipstream will read from at least one pipe
|
|
pmode readable(pmode mode)
|
|
{
|
|
if (!(mode & (pstdout|pstderr)))
|
|
mode |= pstdout;
|
|
return mode;
|
|
}
|
|
|
|
public:
|
|
/// Type used to specify how to connect to the process.
|
|
typedef typename pbase_type::pmode pmode;
|
|
|
|
/// Type used to hold the arguments for a command.
|
|
typedef typename pbase_type::argv_type argv_type;
|
|
|
|
/// Default constructor, creates an uninitialised stream.
|
|
basic_ipstream()
|
|
: istream_type(NULL), pbase_type()
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling do_open() with the supplied
|
|
* arguments.
|
|
*
|
|
* @param cmd a string containing a shell command.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, pmode)
|
|
*/
|
|
explicit
|
|
basic_ipstream(const std::string& cmd, pmode mode = pstdout)
|
|
: istream_type(NULL), pbase_type(cmd, readable(mode))
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling do_open() with the supplied
|
|
* arguments.
|
|
*
|
|
* @param file a string containing the pathname of a program to execute.
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
basic_ipstream( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode = pstdout )
|
|
: istream_type(NULL), pbase_type(file, argv, readable(mode))
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling
|
|
* @c do_open(argv[0],argv,mode|pstdout)
|
|
*
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
explicit
|
|
basic_ipstream(const argv_type& argv, pmode mode = pstdout)
|
|
: istream_type(NULL), pbase_type(argv.at(0), argv, readable(mode))
|
|
{ }
|
|
|
|
#if __cplusplus >= 201103L
|
|
template<typename T, typename = stringable<T>>
|
|
explicit
|
|
basic_ipstream(std::initializer_list<T> args, pmode mode = pstdout)
|
|
: basic_ipstream(argv_type(args.begin(), args.end()), mode)
|
|
{ }
|
|
|
|
basic_ipstream(basic_ipstream&& rhs)
|
|
: istream_type(std::move(rhs))
|
|
, pbase_type(std::move(rhs))
|
|
{ istream_type::set_rdbuf(std::addressof(pbase_type::buf_)); }
|
|
|
|
basic_ipstream&
|
|
operator=(basic_ipstream&& rhs)
|
|
{
|
|
istream_type::operator=(std::move(rhs));
|
|
pbase_type::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
|
|
void
|
|
swap(basic_ipstream& rhs)
|
|
{
|
|
istream_type::swap(rhs);
|
|
pbase_type::swap(rhs);
|
|
}
|
|
#endif // C++11
|
|
|
|
/**
|
|
* @brief Destructor.
|
|
*
|
|
* Closes the stream and waits for the child to exit.
|
|
*/
|
|
~basic_ipstream()
|
|
{ }
|
|
|
|
/**
|
|
* @brief Start a process.
|
|
*
|
|
* Calls do_open( @a cmd , @a mode|pstdout ).
|
|
*
|
|
* @param cmd a string containing a shell command.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, pmode)
|
|
*/
|
|
void
|
|
open(const std::string& cmd, pmode mode = pstdout)
|
|
{
|
|
this->do_open(cmd, readable(mode));
|
|
}
|
|
|
|
/**
|
|
* @brief Start a process.
|
|
*
|
|
* Calls do_open( @a file , @a argv , @a mode|pstdout ).
|
|
*
|
|
* @param file a string containing the pathname of a program to execute.
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
void
|
|
open( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode = pstdout )
|
|
{
|
|
this->do_open(file, argv, readable(mode));
|
|
}
|
|
|
|
/**
|
|
* @brief Set streambuf to read from process' @c stdout.
|
|
* @return @c *this
|
|
*/
|
|
basic_ipstream&
|
|
out()
|
|
{
|
|
this->buf_.read_err(false);
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* @brief Set streambuf to read from process' @c stderr.
|
|
* @return @c *this
|
|
*/
|
|
basic_ipstream&
|
|
err()
|
|
{
|
|
this->buf_.read_err(true);
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @class basic_opstream
|
|
* @brief Class template for Output PStreams.
|
|
*
|
|
* Writing to an open opstream writes to the standard input of the command;
|
|
* the command's standard output is the same as that of the process that
|
|
* created the pstream object, unless altered by the command itself.
|
|
*/
|
|
|
|
template <typename CharT, typename Traits = std::char_traits<CharT> >
|
|
class basic_opstream
|
|
: public std::basic_ostream<CharT, Traits>
|
|
, public pstream_common<CharT, Traits>
|
|
, virtual public pstreams
|
|
{
|
|
typedef std::basic_ostream<CharT, Traits> ostream_type;
|
|
typedef pstream_common<CharT, Traits> pbase_type;
|
|
|
|
using pbase_type::buf_; // declare name in this scope
|
|
|
|
public:
|
|
/// Type used to specify how to connect to the process.
|
|
typedef typename pbase_type::pmode pmode;
|
|
|
|
/// Type used to hold the arguments for a command.
|
|
typedef typename pbase_type::argv_type argv_type;
|
|
|
|
/// Default constructor, creates an uninitialised stream.
|
|
basic_opstream()
|
|
: ostream_type(NULL), pbase_type()
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling do_open() with the supplied
|
|
* arguments.
|
|
*
|
|
* @param cmd a string containing a shell command.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, pmode)
|
|
*/
|
|
explicit
|
|
basic_opstream(const std::string& cmd, pmode mode = pstdin)
|
|
: ostream_type(NULL), pbase_type(cmd, mode|pstdin)
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling do_open() with the supplied
|
|
* arguments.
|
|
*
|
|
* @param file a string containing the pathname of a program to execute.
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
basic_opstream( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode = pstdin )
|
|
: ostream_type(NULL), pbase_type(file, argv, mode|pstdin)
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling
|
|
* @c do_open(argv[0],argv,mode|pstdin)
|
|
*
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
explicit
|
|
basic_opstream(const argv_type& argv, pmode mode = pstdin)
|
|
: ostream_type(NULL), pbase_type(argv.at(0), argv, mode|pstdin)
|
|
{ }
|
|
|
|
#if __cplusplus >= 201103L
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* @param args a list of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
template<typename T, typename = stringable<T>>
|
|
explicit
|
|
basic_opstream(std::initializer_list<T> args, pmode mode = pstdin)
|
|
: basic_opstream(argv_type(args.begin(), args.end()), mode)
|
|
{ }
|
|
|
|
basic_opstream(basic_opstream&& rhs)
|
|
: ostream_type(std::move(rhs))
|
|
, pbase_type(std::move(rhs))
|
|
{ ostream_type::set_rdbuf(std::addressof(pbase_type::buf_)); }
|
|
|
|
basic_opstream&
|
|
operator=(basic_opstream&& rhs)
|
|
{
|
|
ostream_type::operator=(std::move(rhs));
|
|
pbase_type::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
|
|
void
|
|
swap(basic_opstream& rhs)
|
|
{
|
|
ostream_type::swap(rhs);
|
|
pbase_type::swap(rhs);
|
|
}
|
|
#endif // C++11
|
|
|
|
/**
|
|
* @brief Destructor
|
|
*
|
|
* Closes the stream and waits for the child to exit.
|
|
*/
|
|
~basic_opstream() { }
|
|
|
|
/**
|
|
* @brief Start a process.
|
|
*
|
|
* Calls do_open( @a cmd , @a mode|pstdin ).
|
|
*
|
|
* @param cmd a string containing a shell command.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, pmode)
|
|
*/
|
|
void
|
|
open(const std::string& cmd, pmode mode = pstdin)
|
|
{
|
|
this->do_open(cmd, mode|pstdin);
|
|
}
|
|
|
|
/**
|
|
* @brief Start a process.
|
|
*
|
|
* Calls do_open( @a file , @a argv , @a mode|pstdin ).
|
|
*
|
|
* @param file a string containing the pathname of a program to execute.
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
void
|
|
open( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode = pstdin)
|
|
{
|
|
this->do_open(file, argv, mode|pstdin);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @class basic_pstream
|
|
* @brief Class template for Bidirectional PStreams.
|
|
*
|
|
* Writing to a pstream opened with @c pmode @c pstdin writes to the
|
|
* standard input of the command.
|
|
* Reading from a pstream opened with @c pmode @c pstdout and/or @c pstderr
|
|
* reads the command's standard output and/or standard error.
|
|
* Any of the process' @c stdin, @c stdout or @c stderr that is not
|
|
* connected to the pstream (as specified by the @c pmode)
|
|
* will be the same as the process that created the pstream object,
|
|
* unless altered by the command itself.
|
|
*/
|
|
template <typename CharT, typename Traits = std::char_traits<CharT> >
|
|
class basic_pstream
|
|
: public std::basic_iostream<CharT, Traits>
|
|
, public pstream_common<CharT, Traits>
|
|
, virtual public pstreams
|
|
{
|
|
typedef std::basic_iostream<CharT, Traits> iostream_type;
|
|
typedef pstream_common<CharT, Traits> pbase_type;
|
|
|
|
using pbase_type::buf_; // declare name in this scope
|
|
|
|
public:
|
|
/// Type used to specify how to connect to the process.
|
|
typedef typename pbase_type::pmode pmode;
|
|
|
|
/// Type used to hold the arguments for a command.
|
|
typedef typename pbase_type::argv_type argv_type;
|
|
|
|
/// Default constructor, creates an uninitialised stream.
|
|
basic_pstream()
|
|
: iostream_type(NULL), pbase_type()
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling do_open() with the supplied
|
|
* arguments.
|
|
*
|
|
* @param cmd a string containing a shell command.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, pmode)
|
|
*/
|
|
explicit
|
|
basic_pstream(const std::string& cmd, pmode mode = pstdout|pstdin)
|
|
: iostream_type(NULL), pbase_type(cmd, mode)
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling do_open() with the supplied
|
|
* arguments.
|
|
*
|
|
* @param file a string containing the pathname of a program to execute.
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
basic_pstream( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode = pstdout|pstdin )
|
|
: iostream_type(NULL), pbase_type(file, argv, mode)
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling
|
|
* @c do_open(argv[0],argv,mode)
|
|
*
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
explicit
|
|
basic_pstream(const argv_type& argv, pmode mode = pstdout|pstdin)
|
|
: iostream_type(NULL), pbase_type(argv.at(0), argv, mode)
|
|
{ }
|
|
|
|
#if __cplusplus >= 201103L
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* @param l a list of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
template<typename T, typename = stringable<T>>
|
|
explicit
|
|
basic_pstream(std::initializer_list<T> l, pmode mode = pstdout|pstdin)
|
|
: basic_pstream(argv_type(l.begin(), l.end()), mode)
|
|
{ }
|
|
|
|
basic_pstream(basic_pstream&& rhs)
|
|
: iostream_type(std::move(rhs))
|
|
, pbase_type(std::move(rhs))
|
|
{ iostream_type::set_rdbuf(std::addressof(pbase_type::buf_)); }
|
|
|
|
basic_pstream&
|
|
operator=(basic_pstream&& rhs)
|
|
{
|
|
iostream_type::operator=(std::move(rhs));
|
|
pbase_type::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
|
|
void
|
|
swap(basic_pstream& rhs)
|
|
{
|
|
iostream_type::swap(rhs);
|
|
pbase_type::swap(rhs);
|
|
}
|
|
#endif // C++11
|
|
|
|
/**
|
|
* @brief Destructor
|
|
*
|
|
* Closes the stream and waits for the child to exit.
|
|
*/
|
|
~basic_pstream() { }
|
|
|
|
/**
|
|
* @brief Start a process.
|
|
*
|
|
* Calls do_open( @a cnd , @a mode ).
|
|
*
|
|
* @param cmd a string containing a shell command.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, pmode)
|
|
*/
|
|
void
|
|
open(const std::string& cmd, pmode mode = pstdout|pstdin)
|
|
{
|
|
this->do_open(cmd, mode);
|
|
}
|
|
|
|
/**
|
|
* @brief Start a process.
|
|
*
|
|
* Calls do_open( @a file , @a argv , @a mode ).
|
|
*
|
|
* @param file a string containing the pathname of a program to execute.
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
void
|
|
open( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode = pstdout|pstdin )
|
|
{
|
|
this->do_open(file, argv, mode);
|
|
}
|
|
|
|
/**
|
|
* @brief Set streambuf to read from process' @c stdout.
|
|
* @return @c *this
|
|
*/
|
|
basic_pstream&
|
|
out()
|
|
{
|
|
this->buf_.read_err(false);
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* @brief Set streambuf to read from process' @c stderr.
|
|
* @return @c *this
|
|
*/
|
|
basic_pstream&
|
|
err()
|
|
{
|
|
this->buf_.read_err(true);
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @class basic_rpstream
|
|
* @brief Class template for Restricted PStreams.
|
|
*
|
|
* Writing to an rpstream opened with @c pmode @c pstdin writes to the
|
|
* standard input of the command.
|
|
* It is not possible to read directly from an rpstream object, to use
|
|
* an rpstream as in istream you must call either basic_rpstream::out()
|
|
* or basic_rpstream::err(). This is to prevent accidental reads from
|
|
* the wrong input source. If the rpstream was not opened with @c pmode
|
|
* @c pstderr then the class cannot read the process' @c stderr, and
|
|
* basic_rpstream::err() will return an istream that reads from the
|
|
* process' @c stdout, and vice versa.
|
|
* Reading from an rpstream opened with @c pmode @c pstdout and/or
|
|
* @c pstderr reads the command's standard output and/or standard error.
|
|
* Any of the process' @c stdin, @c stdout or @c stderr that is not
|
|
* connected to the pstream (as specified by the @c pmode)
|
|
* will be the same as the process that created the pstream object,
|
|
* unless altered by the command itself.
|
|
*/
|
|
|
|
template <typename CharT, typename Traits = std::char_traits<CharT> >
|
|
class basic_rpstream
|
|
: public std::basic_ostream<CharT, Traits>
|
|
, private std::basic_istream<CharT, Traits>
|
|
, private pstream_common<CharT, Traits>
|
|
, virtual public pstreams
|
|
{
|
|
typedef std::basic_ostream<CharT, Traits> ostream_type;
|
|
typedef std::basic_istream<CharT, Traits> istream_type;
|
|
typedef pstream_common<CharT, Traits> pbase_type;
|
|
|
|
using pbase_type::buf_; // declare name in this scope
|
|
|
|
public:
|
|
/// Type used to specify how to connect to the process.
|
|
typedef typename pbase_type::pmode pmode;
|
|
|
|
/// Type used to hold the arguments for a command.
|
|
typedef typename pbase_type::argv_type argv_type;
|
|
|
|
/// Default constructor, creates an uninitialised stream.
|
|
basic_rpstream()
|
|
: ostream_type(NULL), istream_type(NULL), pbase_type()
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling do_open() with the supplied
|
|
* arguments.
|
|
*
|
|
* @param cmd a string containing a shell command.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, pmode)
|
|
*/
|
|
explicit
|
|
basic_rpstream(const std::string& cmd, pmode mode = pstdout|pstdin)
|
|
: ostream_type(NULL) , istream_type(NULL) , pbase_type(cmd, mode)
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling do_open() with the supplied
|
|
* arguments.
|
|
*
|
|
* @param file a string containing the pathname of a program to execute.
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
basic_rpstream( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode = pstdout|pstdin )
|
|
: ostream_type(NULL), istream_type(NULL), pbase_type(file, argv, mode)
|
|
{ }
|
|
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* Initialises the stream buffer by calling
|
|
* @c do_open(argv[0],argv,mode)
|
|
*
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
explicit
|
|
basic_rpstream(const argv_type& argv, pmode mode = pstdout|pstdin)
|
|
: ostream_type(NULL), istream_type(NULL)
|
|
, pbase_type(argv.at(0), argv, mode)
|
|
{ }
|
|
|
|
#if __cplusplus >= 201103L
|
|
/**
|
|
* @brief Constructor that initialises the stream by starting a process.
|
|
*
|
|
* @param l a list of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
template<typename T, typename = stringable<T>>
|
|
explicit
|
|
basic_rpstream(std::initializer_list<T> l, pmode mode = pstdout|pstdin)
|
|
: basic_rpstream(argv_type(l.begin(), l.end()), mode)
|
|
{ }
|
|
|
|
// TODO: figure out how to move istream and ostream bases separately,
|
|
// but so the virtual basic_ios base is only modified once.
|
|
#if 0
|
|
basic_rpstream(basic_rpstream&& rhs)
|
|
: iostream_type(std::move(rhs))
|
|
, pbase_type(std::move(rhs))
|
|
{ iostream_type::set_rdbuf(std::addressof(pbase_type::buf_)); }
|
|
|
|
basic_rpstream&
|
|
operator=(basic_rpstream&& rhs)
|
|
{
|
|
iostream_type::operator=(std::move(rhs));
|
|
pbase_type::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
|
|
void
|
|
swap(basic_rpstream& rhs)
|
|
{
|
|
iostream_type::swap(rhs);
|
|
pbase_type::swap(rhs);
|
|
}
|
|
#endif
|
|
#endif // C++11
|
|
|
|
/// Destructor
|
|
~basic_rpstream() { }
|
|
|
|
/**
|
|
* @brief Start a process.
|
|
*
|
|
* Calls do_open( @a cmd , @a mode ).
|
|
*
|
|
* @param cmd a string containing a shell command.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, pmode)
|
|
*/
|
|
void
|
|
open(const std::string& cmd, pmode mode = pstdout|pstdin)
|
|
{
|
|
this->do_open(cmd, mode);
|
|
}
|
|
|
|
/**
|
|
* @brief Start a process.
|
|
*
|
|
* Calls do_open( @a file , @a argv , @a mode ).
|
|
*
|
|
* @param file a string containing the pathname of a program to execute.
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
void
|
|
open( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode = pstdout|pstdin )
|
|
{
|
|
this->do_open(file, argv, mode);
|
|
}
|
|
|
|
/**
|
|
* @brief Obtain a reference to the istream that reads
|
|
* the process' @c stdout.
|
|
* @return @c *this
|
|
*/
|
|
istream_type&
|
|
out()
|
|
{
|
|
this->buf_.read_err(false);
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* @brief Obtain a reference to the istream that reads
|
|
* the process' @c stderr.
|
|
* @return @c *this
|
|
*/
|
|
istream_type&
|
|
err()
|
|
{
|
|
this->buf_.read_err(true);
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
|
|
/// Type definition for common template specialisation.
|
|
typedef basic_pstreambuf<char> pstreambuf;
|
|
/// Type definition for common template specialisation.
|
|
typedef basic_ipstream<char> ipstream;
|
|
/// Type definition for common template specialisation.
|
|
typedef basic_opstream<char> opstream;
|
|
/// Type definition for common template specialisation.
|
|
typedef basic_pstream<char> pstream;
|
|
/// Type definition for common template specialisation.
|
|
typedef basic_rpstream<char> rpstream;
|
|
|
|
|
|
/**
|
|
* When inserted into an output pstream the manipulator calls
|
|
* basic_pstreambuf<C,T>::peof() to close the output pipe,
|
|
* causing the child process to receive the end-of-file indicator
|
|
* on subsequent reads from its @c stdin stream.
|
|
*
|
|
* @brief Manipulator to close the pipe connected to the process' stdin.
|
|
* @param s An output PStream class.
|
|
* @return The stream object the manipulator was invoked on.
|
|
* @see basic_pstreambuf<C,T>::peof()
|
|
* @relates basic_opstream basic_pstream basic_rpstream
|
|
*/
|
|
template <typename C, typename T>
|
|
inline std::basic_ostream<C,T>&
|
|
peof(std::basic_ostream<C,T>& s)
|
|
{
|
|
typedef basic_pstreambuf<C,T> pstreambuf_type;
|
|
if (pstreambuf_type* p = dynamic_cast<pstreambuf_type*>(s.rdbuf()))
|
|
p->peof();
|
|
return s;
|
|
}
|
|
|
|
|
|
/*
|
|
* member definitions for pstreambuf
|
|
*/
|
|
|
|
|
|
/**
|
|
* @class basic_pstreambuf
|
|
* Provides underlying streambuf functionality for the PStreams classes.
|
|
*/
|
|
|
|
/** Creates an uninitialised stream buffer. */
|
|
template <typename C, typename T>
|
|
inline
|
|
basic_pstreambuf<C,T>::basic_pstreambuf()
|
|
: ppid_(-1) // initialise to -1 to indicate no process run yet.
|
|
, wpipe_(-1)
|
|
, wbuffer_()
|
|
, rbuffer_()
|
|
, rbufstate_()
|
|
, rsrc_(rsrc_out)
|
|
, status_(-1)
|
|
, error_(0)
|
|
{
|
|
rpipe_[rsrc_out] = rpipe_[rsrc_err] = -1;
|
|
}
|
|
|
|
/**
|
|
* Initialises the stream buffer by calling open() with the supplied
|
|
* arguments.
|
|
*
|
|
* @param cmd a string containing a shell command.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see open()
|
|
*/
|
|
template <typename C, typename T>
|
|
inline
|
|
basic_pstreambuf<C,T>::basic_pstreambuf(const std::string& cmd, pmode mode)
|
|
: ppid_(-1) // initialise to -1 to indicate no process run yet.
|
|
, wpipe_(-1)
|
|
, wbuffer_()
|
|
, rbuffer_()
|
|
, rbufstate_()
|
|
, rsrc_(rsrc_out)
|
|
, status_(-1)
|
|
, error_(0)
|
|
{
|
|
rpipe_[rsrc_out] = rpipe_[rsrc_err] = -1;
|
|
open(cmd, mode);
|
|
}
|
|
|
|
/**
|
|
* Initialises the stream buffer by calling open() with the supplied
|
|
* arguments.
|
|
*
|
|
* @param file a string containing the name of a program to execute.
|
|
* @param argv a vector of argument strings passsed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see open()
|
|
*/
|
|
template <typename C, typename T>
|
|
inline
|
|
basic_pstreambuf<C,T>::basic_pstreambuf( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode )
|
|
: ppid_(-1) // initialise to -1 to indicate no process run yet.
|
|
, wpipe_(-1)
|
|
, wbuffer_()
|
|
, rbuffer_()
|
|
, rbufstate_()
|
|
, rsrc_(rsrc_out)
|
|
, status_(-1)
|
|
, error_(0)
|
|
{
|
|
rpipe_[rsrc_out] = rpipe_[rsrc_err] = -1;
|
|
open(file, argv, mode);
|
|
}
|
|
|
|
/**
|
|
* Closes the stream by calling close().
|
|
* @see close()
|
|
*/
|
|
template <typename C, typename T>
|
|
inline
|
|
basic_pstreambuf<C,T>::~basic_pstreambuf()
|
|
{
|
|
close();
|
|
}
|
|
|
|
#if __cplusplus >= 201103L
|
|
/**
|
|
* Move constructor.
|
|
*/
|
|
template <typename C, typename T>
|
|
inline
|
|
basic_pstreambuf<C,T>::basic_pstreambuf( basic_pstreambuf&& rhs ) noexcept
|
|
: basic_streambuf(static_cast<const basic_streambuf&>(rhs))
|
|
, ppid_(rhs.ppid_)
|
|
, wpipe_(rhs.wpipe_)
|
|
, rpipe_{rhs.rpipe_[0], rhs.rpipe_[1]}
|
|
, wbuffer_(rhs.wbuffer_)
|
|
, rbuffer_{rhs.rbuffer_[0], rhs.rbuffer_[1]}
|
|
, rbufstate_{rhs.rbufstate_[0], rhs.rbufstate_[1], rhs.rbufstate_[2]}
|
|
, rsrc_(rhs.rsrc_)
|
|
, status_(rhs.status_)
|
|
, error_(rhs.error_)
|
|
{
|
|
rhs.ppid_ = -1;
|
|
rhs.wpipe_ = -1;
|
|
rhs.rpipe_[0] = rhs.rpipe_[1] = -1;
|
|
rhs.wbuffer_ = nullptr;
|
|
rhs.rbuffer_[0] = rhs.rbuffer_[1] = nullptr;
|
|
rhs.rbufstate_[0] = rhs.rbufstate_[1] = rhs.rbufstate_[2] = nullptr;
|
|
rhs.rsrc_ = rsrc_out;
|
|
rhs.status_ = -1;
|
|
rhs.error_ = 0;
|
|
rhs.setg(nullptr, nullptr, nullptr);
|
|
rhs.setp(nullptr, nullptr);
|
|
}
|
|
|
|
template <typename C, typename T>
|
|
inline basic_pstreambuf<C,T>&
|
|
basic_pstreambuf<C,T>::operator=( basic_pstreambuf&& rhs ) noexcept
|
|
{
|
|
close();
|
|
basic_streambuf::operator=(static_cast<const basic_streambuf&>(rhs));
|
|
swap(rhs);
|
|
return *this;
|
|
}
|
|
|
|
template <typename C, typename T>
|
|
inline void
|
|
basic_pstreambuf<C,T>::swap( basic_pstreambuf& rhs ) noexcept
|
|
{
|
|
basic_streambuf::swap(static_cast<basic_streambuf&>(rhs));
|
|
std::swap(ppid_, rhs.ppid_);
|
|
std::swap(wpipe_, rhs.wpipe_);
|
|
std::swap(rpipe_, rhs.rpipe_);
|
|
std::swap(wbuffer_, rhs.wbuffer_);
|
|
std::swap(rbuffer_, rhs.rbuffer_);
|
|
std::swap(rbufstate_, rhs.rbufstate_);
|
|
std::swap(rsrc_, rhs.rsrc_);
|
|
std::swap(status_, rhs.status_);
|
|
std::swap(error_, rhs.error_);
|
|
}
|
|
#endif // C++11
|
|
|
|
/**
|
|
* Starts a new process by passing @a command to the shell (/bin/sh)
|
|
* and opens pipes to the process with the specified @a mode.
|
|
*
|
|
* If @a mode contains @c pstdout the initial read source will be
|
|
* the child process' stdout, otherwise if @a mode contains @c pstderr
|
|
* the initial read source will be the child's stderr.
|
|
*
|
|
* Will duplicate the actions of the shell in searching for an
|
|
* executable file if the specified file name does not contain a slash (/)
|
|
* character.
|
|
*
|
|
* @warning
|
|
* There is no way to tell whether the shell command succeeded, this
|
|
* function will always succeed unless resource limits (such as
|
|
* memory usage, or number of processes or open files) are exceeded.
|
|
* This means is_open() will return true even if @a command cannot
|
|
* be executed.
|
|
* Use pstreambuf::open(const std::string&, const argv_type&, pmode)
|
|
* if you need to know whether the command failed to execute.
|
|
*
|
|
* @param command a string containing a shell command.
|
|
* @param mode a bitwise OR of one or more of @c out, @c in, @c err.
|
|
* @return NULL if the shell could not be started or the
|
|
* pipes could not be opened, @c this otherwise.
|
|
* @see <b>execl</b>(3)
|
|
*/
|
|
template <typename C, typename T>
|
|
basic_pstreambuf<C,T>*
|
|
basic_pstreambuf<C,T>::open(const std::string& command, pmode mode)
|
|
{
|
|
const char * shell_path = "/bin/sh";
|
|
#if 0
|
|
const std::string argv[] = { "sh", "-c", command };
|
|
return this->open(shell_path, argv_type(argv, argv+3), mode);
|
|
#else
|
|
basic_pstreambuf<C,T>* ret = NULL;
|
|
|
|
if (!is_open())
|
|
{
|
|
switch(fork(mode))
|
|
{
|
|
case 0 :
|
|
// this is the new process, exec command
|
|
::execl(shell_path, "sh", "-c", command.c_str(), (char*)NULL);
|
|
|
|
// can only reach this point if exec() failed
|
|
|
|
// parent can get exit code from waitpid()
|
|
::_exit(errno);
|
|
// using std::exit() would make static dtors run twice
|
|
|
|
case -1 :
|
|
// couldn't fork, error already handled in pstreambuf::fork()
|
|
break;
|
|
|
|
default :
|
|
// this is the parent process
|
|
// activate buffers
|
|
create_buffers(mode);
|
|
ret = this;
|
|
}
|
|
}
|
|
return ret;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @brief Helper function to close a file descriptor.
|
|
*
|
|
* Inspects @a fd and calls <b>close</b>(3) if it has a non-negative value.
|
|
*
|
|
* @param fd a file descriptor.
|
|
* @relates basic_pstreambuf
|
|
*/
|
|
inline void
|
|
close_fd(pstreams::fd_type& fd)
|
|
{
|
|
if (fd >= 0 && ::close(fd) == 0)
|
|
fd = -1;
|
|
}
|
|
|
|
/**
|
|
* @brief Helper function to close an array of file descriptors.
|
|
*
|
|
* Calls @c close_fd() on each member of the array.
|
|
* The length of the array is determined automatically by
|
|
* template argument deduction to avoid errors.
|
|
*
|
|
* @param fds an array of file descriptors.
|
|
* @relates basic_pstreambuf
|
|
*/
|
|
template <int N>
|
|
inline void
|
|
close_fd_array(pstreams::fd_type (&fds)[N])
|
|
{
|
|
for (std::size_t i = 0; i < N; ++i)
|
|
close_fd(fds[i]);
|
|
}
|
|
|
|
/**
|
|
* Starts a new process by executing @a file with the arguments in
|
|
* @a argv and opens pipes to the process with the specified @a mode.
|
|
*
|
|
* By convention @c argv[0] should be the file name of the file being
|
|
* executed.
|
|
*
|
|
* If @a mode contains @c pstdout the initial read source will be
|
|
* the child process' stdout, otherwise if @a mode contains @c pstderr
|
|
* the initial read source will be the child's stderr.
|
|
*
|
|
* Will duplicate the actions of the shell in searching for an
|
|
* executable file if the specified file name does not contain a slash (/)
|
|
* character.
|
|
*
|
|
* Iff @a file is successfully executed then is_open() will return true.
|
|
* Otherwise, pstreambuf::error() can be used to obtain the value of
|
|
* @c errno that was set by <b>execvp</b>(3) in the child process.
|
|
*
|
|
* The exit status of the new process will be returned by
|
|
* pstreambuf::status() after pstreambuf::exited() returns true.
|
|
*
|
|
* @param file a string containing the pathname of a program to execute.
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode a bitwise OR of one or more of @c out, @c in and @c err.
|
|
* @return NULL if a pipe could not be opened or if the program could
|
|
* not be executed, @c this otherwise.
|
|
* @see <b>execvp</b>(3)
|
|
*/
|
|
template <typename C, typename T>
|
|
basic_pstreambuf<C,T>*
|
|
basic_pstreambuf<C,T>::open( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode )
|
|
{
|
|
basic_pstreambuf<C,T>* ret = NULL;
|
|
|
|
if (!is_open())
|
|
{
|
|
// constants for read/write ends of pipe
|
|
enum { RD, WR };
|
|
|
|
// open another pipe and set close-on-exec
|
|
fd_type ck_exec[] = { -1, -1 };
|
|
if (-1 == ::pipe(ck_exec)
|
|
|| -1 == ::fcntl(ck_exec[RD], F_SETFD, FD_CLOEXEC)
|
|
|| -1 == ::fcntl(ck_exec[WR], F_SETFD, FD_CLOEXEC))
|
|
{
|
|
error_ = errno;
|
|
close_fd_array(ck_exec);
|
|
}
|
|
else
|
|
{
|
|
switch(fork(mode))
|
|
{
|
|
case 0 :
|
|
// this is the new process, exec command
|
|
{
|
|
char** arg_v = new char*[argv.size()+1];
|
|
for (std::size_t i = 0; i < argv.size(); ++i)
|
|
{
|
|
const std::string& src = argv[i];
|
|
char*& dest = arg_v[i];
|
|
dest = new char[src.size()+1];
|
|
dest[ src.copy(dest, src.size()) ] = '\0';
|
|
}
|
|
arg_v[argv.size()] = NULL;
|
|
|
|
::execvp(file.c_str(), arg_v);
|
|
|
|
// can only reach this point if exec() failed
|
|
|
|
// parent can get error code from ck_exec pipe
|
|
error_ = errno;
|
|
|
|
while (::write(ck_exec[WR], &error_, sizeof(error_)) == -1
|
|
&& errno == EINTR)
|
|
{ }
|
|
|
|
::close(ck_exec[WR]);
|
|
::close(ck_exec[RD]);
|
|
|
|
::_exit(error_);
|
|
// using std::exit() would make static dtors run twice
|
|
}
|
|
|
|
case -1 :
|
|
// couldn't fork, error already handled in pstreambuf::fork()
|
|
close_fd_array(ck_exec);
|
|
break;
|
|
|
|
default :
|
|
// this is the parent process
|
|
|
|
// check child called exec() successfully
|
|
::close(ck_exec[WR]);
|
|
switch (::read(ck_exec[RD], &error_, sizeof(error_)))
|
|
{
|
|
case 0:
|
|
// activate buffers
|
|
create_buffers(mode);
|
|
ret = this;
|
|
break;
|
|
case -1:
|
|
error_ = errno;
|
|
break;
|
|
default:
|
|
// error_ contains error code from child
|
|
// call wait() to clean up and set ppid_ to 0
|
|
this->wait();
|
|
break;
|
|
}
|
|
::close(ck_exec[RD]);
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Creates pipes as specified by @a mode and calls @c fork() to create
|
|
* a new process. If the fork is successful the parent process stores
|
|
* the child's PID and the opened pipes and the child process replaces
|
|
* its standard streams with the opened pipes.
|
|
*
|
|
* If an error occurs the error code will be set to one of the possible
|
|
* errors for @c pipe() or @c fork().
|
|
* See your system's documentation for these error codes.
|
|
*
|
|
* @param mode an OR of pmodes specifying which of the child's
|
|
* standard streams to connect to.
|
|
* @return On success the PID of the child is returned in the parent's
|
|
* context and zero is returned in the child's context.
|
|
* On error -1 is returned and the error code is set appropriately.
|
|
*/
|
|
template <typename C, typename T>
|
|
pid_t
|
|
basic_pstreambuf<C,T>::fork(pmode mode)
|
|
{
|
|
pid_t pid = -1;
|
|
|
|
// Three pairs of file descriptors, for pipes connected to the
|
|
// process' stdin, stdout and stderr
|
|
// (stored in a single array so close_fd_array() can close all at once)
|
|
fd_type fd[] = { -1, -1, -1, -1, -1, -1 };
|
|
fd_type* const pin = fd;
|
|
fd_type* const pout = fd+2;
|
|
fd_type* const perr = fd+4;
|
|
|
|
// constants for read/write ends of pipe
|
|
enum { RD, WR };
|
|
|
|
// N.B.
|
|
// For the pstreambuf pin is an output stream and
|
|
// pout and perr are input streams.
|
|
|
|
if (!error_ && mode&pstdin && ::pipe(pin))
|
|
error_ = errno;
|
|
|
|
if (!error_ && mode&pstdout && ::pipe(pout))
|
|
error_ = errno;
|
|
|
|
if (!error_ && mode&pstderr && ::pipe(perr))
|
|
error_ = errno;
|
|
|
|
if (!error_)
|
|
{
|
|
pid = ::fork();
|
|
switch (pid)
|
|
{
|
|
case 0 :
|
|
{
|
|
// this is the new process
|
|
|
|
// for each open pipe close one end and redirect the
|
|
// respective standard stream to the other end
|
|
|
|
if (*pin >= 0)
|
|
{
|
|
::close(pin[WR]);
|
|
::dup2(pin[RD], STDIN_FILENO);
|
|
::close(pin[RD]);
|
|
}
|
|
if (*pout >= 0)
|
|
{
|
|
::close(pout[RD]);
|
|
::dup2(pout[WR], STDOUT_FILENO);
|
|
::close(pout[WR]);
|
|
}
|
|
if (*perr >= 0)
|
|
{
|
|
::close(perr[RD]);
|
|
::dup2(perr[WR], STDERR_FILENO);
|
|
::close(perr[WR]);
|
|
}
|
|
|
|
#ifdef _POSIX_JOB_CONTROL
|
|
if (mode&newpg)
|
|
::setpgid(0, 0); // Change to a new process group
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
case -1 :
|
|
{
|
|
// couldn't fork for some reason
|
|
error_ = errno;
|
|
// close any open pipes
|
|
close_fd_array(fd);
|
|
break;
|
|
}
|
|
default :
|
|
{
|
|
// this is the parent process, store process' pid
|
|
ppid_ = pid;
|
|
|
|
// store one end of open pipes and close other end
|
|
if (*pin >= 0)
|
|
{
|
|
wpipe_ = pin[WR];
|
|
::close(pin[RD]);
|
|
}
|
|
if (*pout >= 0)
|
|
{
|
|
rpipe_[rsrc_out] = pout[RD];
|
|
::close(pout[WR]);
|
|
}
|
|
if (*perr >= 0)
|
|
{
|
|
rpipe_[rsrc_err] = perr[RD];
|
|
::close(perr[WR]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// close any pipes we opened before failure
|
|
close_fd_array(fd);
|
|
}
|
|
return pid;
|
|
}
|
|
|
|
/**
|
|
* Closes all pipes and calls wait() to wait for the process to finish.
|
|
* If an error occurs the error code will be set to one of the possible
|
|
* errors for @c waitpid().
|
|
* See your system's documentation for these errors.
|
|
*
|
|
* @return @c this on successful close or @c NULL if there is no
|
|
* process to close or if an error occurs.
|
|
*/
|
|
template <typename C, typename T>
|
|
basic_pstreambuf<C,T>*
|
|
basic_pstreambuf<C,T>::close()
|
|
{
|
|
const bool running = is_open();
|
|
|
|
basic_pstreambuf::sync(); // might call wait() and reap the child process
|
|
|
|
// rather than trying to work out whether or not we need to clean up
|
|
// just do it anyway, all cleanup functions are safe to call twice.
|
|
|
|
destroy_buffers(pstdin|pstdout|pstderr);
|
|
|
|
// close pipes before wait() so child gets EOF/SIGPIPE
|
|
close_fd(wpipe_);
|
|
close_fd_array(rpipe_);
|
|
|
|
do
|
|
{
|
|
error_ = 0;
|
|
} while (wait() == -1 && error() == EINTR);
|
|
|
|
return running ? this : NULL;
|
|
}
|
|
|
|
/**
|
|
* Used to be called on construction to initialise the arrays for reading.
|
|
* No longer used.
|
|
*/
|
|
template <typename C, typename T>
|
|
#if __cplusplus >= 201402L && __has_cpp_attribute(deprecated)
|
|
[[deprecated]]
|
|
#elif __GNUC__
|
|
__attribute__((deprecated))
|
|
#endif
|
|
inline void
|
|
basic_pstreambuf<C,T>::init_rbuffers()
|
|
{
|
|
rpipe_[rsrc_out] = rpipe_[rsrc_err] = -1;
|
|
rbuffer_[rsrc_out] = rbuffer_[rsrc_err] = NULL;
|
|
rbufstate_[0] = rbufstate_[1] = rbufstate_[2] = NULL;
|
|
}
|
|
|
|
template <typename C, typename T>
|
|
void
|
|
basic_pstreambuf<C,T>::create_buffers(pmode mode)
|
|
{
|
|
if (mode & pstdin)
|
|
{
|
|
delete[] wbuffer_;
|
|
wbuffer_ = new char_type[bufsz];
|
|
this->setp(wbuffer_, wbuffer_ + bufsz);
|
|
}
|
|
if (mode & pstdout)
|
|
{
|
|
delete[] rbuffer_[rsrc_out];
|
|
rbuffer_[rsrc_out] = new char_type[bufsz];
|
|
rsrc_ = rsrc_out;
|
|
this->setg(rbuffer_[rsrc_out] + pbsz, rbuffer_[rsrc_out] + pbsz,
|
|
rbuffer_[rsrc_out] + pbsz);
|
|
}
|
|
if (mode & pstderr)
|
|
{
|
|
delete[] rbuffer_[rsrc_err];
|
|
rbuffer_[rsrc_err] = new char_type[bufsz];
|
|
if (!(mode & pstdout))
|
|
{
|
|
rsrc_ = rsrc_err;
|
|
this->setg(rbuffer_[rsrc_err] + pbsz, rbuffer_[rsrc_err] + pbsz,
|
|
rbuffer_[rsrc_err] + pbsz);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename C, typename T>
|
|
void
|
|
basic_pstreambuf<C,T>::destroy_buffers(pmode mode)
|
|
{
|
|
if (mode & pstdin)
|
|
{
|
|
this->setp(NULL, NULL);
|
|
delete[] wbuffer_;
|
|
wbuffer_ = NULL;
|
|
}
|
|
if (mode & pstdout)
|
|
{
|
|
if (rsrc_ == rsrc_out)
|
|
this->setg(NULL, NULL, NULL);
|
|
delete[] rbuffer_[rsrc_out];
|
|
rbuffer_[rsrc_out] = NULL;
|
|
}
|
|
if (mode & pstderr)
|
|
{
|
|
if (rsrc_ == rsrc_err)
|
|
this->setg(NULL, NULL, NULL);
|
|
delete[] rbuffer_[rsrc_err];
|
|
rbuffer_[rsrc_err] = NULL;
|
|
}
|
|
}
|
|
|
|
template <typename C, typename T>
|
|
typename basic_pstreambuf<C,T>::buf_read_src
|
|
basic_pstreambuf<C,T>::switch_read_buffer(buf_read_src src)
|
|
{
|
|
if (rsrc_ != src)
|
|
{
|
|
char_type* tmpbufstate[] = {this->eback(), this->gptr(), this->egptr()};
|
|
this->setg(rbufstate_[0], rbufstate_[1], rbufstate_[2]);
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
rbufstate_[i] = tmpbufstate[i];
|
|
rsrc_ = src;
|
|
}
|
|
return rsrc_;
|
|
}
|
|
|
|
/**
|
|
* Suspends execution and waits for the associated process to exit, or
|
|
* until a signal is delivered whose action is to terminate the current
|
|
* process or to call a signal handling function. If the process has
|
|
* already exited (i.e. it is a "zombie" process) then wait() returns
|
|
* immediately. Waiting for the child process causes all its system
|
|
* resources to be freed.
|
|
*
|
|
* error() will return EINTR if wait() is interrupted by a signal.
|
|
*
|
|
* @param nohang true to return immediately if the process has not exited.
|
|
* @return 1 if the process has exited and wait() has not yet been called.
|
|
* 0 if @a nohang is true and the process has not exited yet.
|
|
* -1 if no process has been started or if an error occurs,
|
|
* in which case the error can be found using error().
|
|
*/
|
|
template <typename C, typename T>
|
|
int
|
|
basic_pstreambuf<C,T>::wait(bool nohang)
|
|
{
|
|
int child_exited = -1;
|
|
if (is_open())
|
|
{
|
|
int exit_status;
|
|
switch(::waitpid(ppid_, &exit_status, nohang ? WNOHANG : 0))
|
|
{
|
|
case 0 :
|
|
// nohang was true and process has not exited
|
|
child_exited = 0;
|
|
break;
|
|
case -1 :
|
|
error_ = errno;
|
|
break;
|
|
default :
|
|
// process has exited
|
|
ppid_ = 0;
|
|
status_ = exit_status;
|
|
child_exited = 1;
|
|
// Close wpipe, would get SIGPIPE if we used it.
|
|
destroy_buffers(pstdin);
|
|
close_fd(wpipe_);
|
|
// Must free read buffers and pipes on destruction
|
|
// or next call to open()/close()
|
|
break;
|
|
}
|
|
}
|
|
return child_exited;
|
|
}
|
|
|
|
/**
|
|
* Sends the specified signal to the process. A signal can be used to
|
|
* terminate a child process that would not exit otherwise.
|
|
*
|
|
* If an error occurs the error code will be set to one of the possible
|
|
* errors for @c kill(). See your system's documentation for these errors.
|
|
*
|
|
* @param signal A signal to send to the child process.
|
|
* @return @c this or @c NULL if @c kill() fails.
|
|
*/
|
|
template <typename C, typename T>
|
|
inline basic_pstreambuf<C,T>*
|
|
basic_pstreambuf<C,T>::kill(int signal)
|
|
{
|
|
basic_pstreambuf<C,T>* ret = NULL;
|
|
if (is_open())
|
|
{
|
|
if (::kill(ppid_, signal))
|
|
error_ = errno;
|
|
else
|
|
{
|
|
#if 0
|
|
// TODO call exited() to check for exit and clean up? leave to user?
|
|
if (signal==SIGTERM || signal==SIGKILL)
|
|
this->exited();
|
|
#endif
|
|
ret = this;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Sends the specified signal to the process group of the child process.
|
|
* A signal can be used to terminate a child process that would not exit
|
|
* otherwise, or to kill the process and its own children.
|
|
*
|
|
* If an error occurs the error code will be set to one of the possible
|
|
* errors for @c getpgid() or @c kill(). See your system's documentation
|
|
* for these errors. If the child is in the current process group then
|
|
* NULL will be returned and the error code set to EPERM.
|
|
*
|
|
* @param signal A signal to send to the child process.
|
|
* @return @c this on success or @c NULL on failure.
|
|
*/
|
|
template <typename C, typename T>
|
|
inline basic_pstreambuf<C,T>*
|
|
basic_pstreambuf<C,T>::killpg(int signal)
|
|
{
|
|
basic_pstreambuf<C,T>* ret = NULL;
|
|
#ifdef _POSIX_JOB_CONTROL
|
|
if (is_open())
|
|
{
|
|
pid_t pgid = ::getpgid(ppid_);
|
|
if (pgid == -1)
|
|
error_ = errno;
|
|
else if (pgid == ::getpgrp())
|
|
error_ = EPERM; // Don't commit suicide
|
|
else if (::killpg(pgid, signal))
|
|
error_ = errno;
|
|
else
|
|
ret = this;
|
|
}
|
|
#else
|
|
error_ = ENOTSUP;
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* This function can call pstreambuf::wait() and so may change the
|
|
* object's state if the child process has already exited.
|
|
*
|
|
* @return True if the associated process has exited, false otherwise.
|
|
* @see basic_pstreambuf<C,T>::wait()
|
|
*/
|
|
template <typename C, typename T>
|
|
inline bool
|
|
basic_pstreambuf<C,T>::exited()
|
|
{
|
|
return ppid_ == 0 || wait(true)==1;
|
|
}
|
|
|
|
|
|
/**
|
|
* @return The exit status of the child process, or -1 if wait()
|
|
* has not yet been called to wait for the child to exit.
|
|
* @see basic_pstreambuf<C,T>::wait()
|
|
*/
|
|
template <typename C, typename T>
|
|
inline int
|
|
basic_pstreambuf<C,T>::status() const
|
|
{
|
|
return status_;
|
|
}
|
|
|
|
/**
|
|
* @return The error code of the most recently failed operation, or zero.
|
|
*/
|
|
template <typename C, typename T>
|
|
inline int
|
|
basic_pstreambuf<C,T>::error() const
|
|
{
|
|
return error_;
|
|
}
|
|
|
|
/**
|
|
* Closes the output pipe, causing the child process to receive the
|
|
* end-of-file indicator on subsequent reads from its @c stdin stream.
|
|
*/
|
|
template <typename C, typename T>
|
|
inline void
|
|
basic_pstreambuf<C,T>::peof()
|
|
{
|
|
sync();
|
|
destroy_buffers(pstdin);
|
|
close_fd(wpipe_);
|
|
}
|
|
|
|
/**
|
|
* Unlike pstreambuf::exited(), this function will not call wait() and
|
|
* so will not change the object's state. This means that once a child
|
|
* process is executed successfully this function will continue to
|
|
* return true even after the process exits (until wait() is called.)
|
|
*
|
|
* @return true if a previous call to open() succeeded and wait() has
|
|
* not been called and determined that the process has exited,
|
|
* false otherwise.
|
|
*/
|
|
template <typename C, typename T>
|
|
inline bool
|
|
basic_pstreambuf<C,T>::is_open() const
|
|
{
|
|
return ppid_ > 0;
|
|
}
|
|
|
|
/**
|
|
* Toggle the stream used for reading. If @a readerr is @c true then the
|
|
* process' @c stderr output will be used for subsequent extractions, if
|
|
* @a readerr is false the the process' stdout will be used.
|
|
* @param readerr @c true to read @c stderr, @c false to read @c stdout.
|
|
* @return @c true if the requested stream is open and will be used for
|
|
* subsequent extractions, @c false otherwise.
|
|
*/
|
|
template <typename C, typename T>
|
|
inline bool
|
|
basic_pstreambuf<C,T>::read_err(bool readerr)
|
|
{
|
|
buf_read_src src = readerr ? rsrc_err : rsrc_out;
|
|
if (rpipe_[src]>=0)
|
|
{
|
|
switch_read_buffer(src);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Called when the internal character buffer is not present or is full,
|
|
* to transfer the buffer contents to the pipe.
|
|
*
|
|
* @param c a character to be written to the pipe.
|
|
* @return @c traits_type::eof() if an error occurs, otherwise if @a c
|
|
* is not equal to @c traits_type::eof() it will be buffered and
|
|
* a value other than @c traits_type::eof() returned to indicate
|
|
* success.
|
|
*/
|
|
template <typename C, typename T>
|
|
typename basic_pstreambuf<C,T>::int_type
|
|
basic_pstreambuf<C,T>::overflow(int_type c)
|
|
{
|
|
if (!empty_buffer())
|
|
return traits_type::eof();
|
|
else if (!traits_type::eq_int_type(c, traits_type::eof()))
|
|
return this->sputc(c);
|
|
else
|
|
return traits_type::not_eof(c);
|
|
}
|
|
|
|
|
|
template <typename C, typename T>
|
|
int
|
|
basic_pstreambuf<C,T>::sync()
|
|
{
|
|
return !exited() && empty_buffer() ? 0 : -1;
|
|
}
|
|
|
|
/**
|
|
* @param s character buffer.
|
|
* @param n buffer length.
|
|
* @return the number of characters written.
|
|
*/
|
|
template <typename C, typename T>
|
|
std::streamsize
|
|
basic_pstreambuf<C,T>::xsputn(const char_type* s, std::streamsize n)
|
|
{
|
|
std::streamsize done = 0;
|
|
while (done < n)
|
|
{
|
|
if (std::streamsize nbuf = this->epptr() - this->pptr())
|
|
{
|
|
nbuf = std::min(nbuf, n - done);
|
|
traits_type::copy(this->pptr(), s + done, nbuf);
|
|
this->pbump(nbuf);
|
|
done += nbuf;
|
|
}
|
|
else if (!empty_buffer())
|
|
break;
|
|
}
|
|
return done;
|
|
}
|
|
|
|
/**
|
|
* @return true if the buffer was emptied, false otherwise.
|
|
*/
|
|
template <typename C, typename T>
|
|
bool
|
|
basic_pstreambuf<C,T>::empty_buffer()
|
|
{
|
|
const std::streamsize count = this->pptr() - this->pbase();
|
|
if (count > 0)
|
|
{
|
|
const std::streamsize written = this->write(this->wbuffer_, count);
|
|
if (written > 0)
|
|
{
|
|
if (const std::streamsize unwritten = count - written)
|
|
traits_type::move(this->pbase(), this->pbase()+written, unwritten);
|
|
this->pbump(-written);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Called when the internal character buffer is is empty, to re-fill it
|
|
* from the pipe.
|
|
*
|
|
* @return The first available character in the buffer,
|
|
* or @c traits_type::eof() in case of failure.
|
|
*/
|
|
template <typename C, typename T>
|
|
typename basic_pstreambuf<C,T>::int_type
|
|
basic_pstreambuf<C,T>::underflow()
|
|
{
|
|
if (this->gptr() < this->egptr() || fill_buffer())
|
|
return traits_type::to_int_type(*this->gptr());
|
|
else
|
|
return traits_type::eof();
|
|
}
|
|
|
|
/**
|
|
* Attempts to make @a c available as the next character to be read by
|
|
* @c sgetc().
|
|
*
|
|
* @param c a character to make available for extraction.
|
|
* @return @a c if the character can be made available,
|
|
* @c traits_type::eof() otherwise.
|
|
*/
|
|
template <typename C, typename T>
|
|
typename basic_pstreambuf<C,T>::int_type
|
|
basic_pstreambuf<C,T>::pbackfail(int_type c)
|
|
{
|
|
if (this->gptr() != this->eback())
|
|
{
|
|
this->gbump(-1);
|
|
if (!traits_type::eq_int_type(c, traits_type::eof()))
|
|
*this->gptr() = traits_type::to_char_type(c);
|
|
return traits_type::not_eof(c);
|
|
}
|
|
else
|
|
return traits_type::eof();
|
|
}
|
|
|
|
template <typename C, typename T>
|
|
std::streamsize
|
|
basic_pstreambuf<C,T>::showmanyc()
|
|
{
|
|
int avail = 0;
|
|
if (sizeof(char_type) == 1)
|
|
avail = fill_buffer(true) ? this->egptr() - this->gptr() : -1;
|
|
#ifdef FIONREAD
|
|
else
|
|
{
|
|
if (::ioctl(rpipe(), FIONREAD, &avail) == -1)
|
|
avail = -1;
|
|
else if (avail)
|
|
avail /= sizeof(char_type);
|
|
}
|
|
#endif
|
|
return std::streamsize(avail);
|
|
}
|
|
|
|
/**
|
|
* @return true if the buffer was filled, false otherwise.
|
|
*/
|
|
template <typename C, typename T>
|
|
bool
|
|
basic_pstreambuf<C,T>::fill_buffer(bool non_blocking)
|
|
{
|
|
const std::streamsize pb1 = this->gptr() - this->eback();
|
|
const std::streamsize pb2 = pbsz;
|
|
const std::streamsize npb = std::min(pb1, pb2);
|
|
|
|
char_type* const rbuf = rbuffer();
|
|
|
|
if (npb)
|
|
traits_type::move(rbuf + pbsz - npb, this->gptr() - npb, npb);
|
|
|
|
std::streamsize rc = -1;
|
|
|
|
if (non_blocking)
|
|
{
|
|
const int flags = ::fcntl(rpipe(), F_GETFL);
|
|
if (flags != -1)
|
|
{
|
|
const bool blocking = !(flags & O_NONBLOCK);
|
|
if (blocking)
|
|
::fcntl(rpipe(), F_SETFL, flags | O_NONBLOCK); // set non-blocking
|
|
|
|
error_ = 0;
|
|
rc = read(rbuf + pbsz, bufsz - pbsz);
|
|
|
|
if (rc == -1 && error_ == EAGAIN) // nothing available
|
|
rc = 0;
|
|
else if (rc == 0) // EOF
|
|
rc = -1;
|
|
|
|
if (blocking)
|
|
::fcntl(rpipe(), F_SETFL, flags); // restore
|
|
}
|
|
}
|
|
else
|
|
rc = read(rbuf + pbsz, bufsz - pbsz);
|
|
|
|
if (rc > 0 || (rc == 0 && non_blocking))
|
|
{
|
|
this->setg( rbuf + pbsz - npb,
|
|
rbuf + pbsz,
|
|
rbuf + pbsz + rc );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
this->setg(NULL, NULL, NULL);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes up to @a n characters to the pipe from the buffer @a s.
|
|
*
|
|
* @param s character buffer.
|
|
* @param n buffer length.
|
|
* @return the number of characters written.
|
|
*/
|
|
template <typename C, typename T>
|
|
inline std::streamsize
|
|
basic_pstreambuf<C,T>::write(const char_type* s, std::streamsize n)
|
|
{
|
|
std::streamsize nwritten = 0;
|
|
if (wpipe() >= 0)
|
|
{
|
|
nwritten = ::write(wpipe(), s, n * sizeof(char_type));
|
|
if (nwritten == -1)
|
|
error_ = errno;
|
|
else
|
|
nwritten /= sizeof(char_type);
|
|
}
|
|
return nwritten;
|
|
}
|
|
|
|
/**
|
|
* Reads up to @a n characters from the pipe to the buffer @a s.
|
|
*
|
|
* @param s character buffer.
|
|
* @param n buffer length.
|
|
* @return the number of characters read.
|
|
*/
|
|
template <typename C, typename T>
|
|
inline std::streamsize
|
|
basic_pstreambuf<C,T>::read(char_type* s, std::streamsize n)
|
|
{
|
|
std::streamsize nread = 0;
|
|
if (rpipe() >= 0)
|
|
{
|
|
nread = ::read(rpipe(), s, n * sizeof(char_type));
|
|
if (nread == -1)
|
|
error_ = errno;
|
|
else
|
|
nread /= sizeof(char_type);
|
|
}
|
|
return nread;
|
|
}
|
|
|
|
/** @return a reference to the output file descriptor */
|
|
template <typename C, typename T>
|
|
inline pstreams::fd_type&
|
|
basic_pstreambuf<C,T>::wpipe()
|
|
{
|
|
return wpipe_;
|
|
}
|
|
|
|
/** @return a reference to the active input file descriptor */
|
|
template <typename C, typename T>
|
|
inline pstreams::fd_type&
|
|
basic_pstreambuf<C,T>::rpipe()
|
|
{
|
|
return rpipe_[rsrc_];
|
|
}
|
|
|
|
/** @return a reference to the specified input file descriptor */
|
|
template <typename C, typename T>
|
|
inline pstreams::fd_type&
|
|
basic_pstreambuf<C,T>::rpipe(buf_read_src which)
|
|
{
|
|
return rpipe_[which];
|
|
}
|
|
|
|
/** @return a pointer to the start of the active input buffer area. */
|
|
template <typename C, typename T>
|
|
inline typename basic_pstreambuf<C,T>::char_type*
|
|
basic_pstreambuf<C,T>::rbuffer()
|
|
{
|
|
return rbuffer_[rsrc_];
|
|
}
|
|
|
|
|
|
/*
|
|
* member definitions for pstream_common
|
|
*/
|
|
|
|
/**
|
|
* @class pstream_common
|
|
* Abstract Base Class providing common functionality for basic_ipstream,
|
|
* basic_opstream and basic_pstream.
|
|
* pstream_common manages the basic_pstreambuf stream buffer that is used
|
|
* by the derived classes to initialise an iostream class.
|
|
*/
|
|
|
|
/** Creates an uninitialised stream. */
|
|
template <typename C, typename T>
|
|
inline
|
|
pstream_common<C,T>::pstream_common()
|
|
: std::basic_ios<C,T>(NULL)
|
|
, command_()
|
|
, buf_()
|
|
{
|
|
this->std::basic_ios<C,T>::rdbuf(&buf_);
|
|
}
|
|
|
|
/**
|
|
* Initialises the stream buffer by calling
|
|
* do_open( @a command , @a mode )
|
|
*
|
|
* @param cmd a string containing a shell command.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, pmode)
|
|
*/
|
|
template <typename C, typename T>
|
|
inline
|
|
pstream_common<C,T>::pstream_common(const std::string& cmd, pmode mode)
|
|
: std::basic_ios<C,T>(NULL)
|
|
, command_(cmd)
|
|
, buf_()
|
|
{
|
|
this->std::basic_ios<C,T>::rdbuf(&buf_);
|
|
do_open(cmd, mode);
|
|
}
|
|
|
|
/**
|
|
* Initialises the stream buffer by calling
|
|
* do_open( @a file , @a argv , @a mode )
|
|
*
|
|
* @param file a string containing the pathname of a program to execute.
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see do_open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
template <typename C, typename T>
|
|
inline
|
|
pstream_common<C,T>::pstream_common( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode )
|
|
: std::basic_ios<C,T>(NULL)
|
|
, command_(file)
|
|
, buf_()
|
|
{
|
|
this->std::basic_ios<C,T>::rdbuf(&buf_);
|
|
do_open(file, argv, mode);
|
|
}
|
|
|
|
/**
|
|
* This is a pure virtual function to make @c pstream_common abstract.
|
|
* Because it is the destructor it will be called by derived classes
|
|
* and so must be defined. It is also protected, to discourage use of
|
|
* the PStreams classes through pointers or references to the base class.
|
|
*
|
|
* @sa If defining a pure virtual seems odd you should read
|
|
* http://www.gotw.ca/gotw/031.htm (and the rest of the site as well!)
|
|
*/
|
|
template <typename C, typename T>
|
|
inline
|
|
pstream_common<C,T>::~pstream_common()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Calls rdbuf()->open( @a command , @a mode )
|
|
* and sets @c failbit on error.
|
|
*
|
|
* @param cmd a string containing a shell command.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see basic_pstreambuf::open(const std::string&, pmode)
|
|
*/
|
|
template <typename C, typename T>
|
|
inline void
|
|
pstream_common<C,T>::do_open(const std::string& cmd, pmode mode)
|
|
{
|
|
if (!buf_.open((command_=cmd), mode))
|
|
this->setstate(std::ios_base::failbit);
|
|
}
|
|
|
|
/**
|
|
* Calls rdbuf()->open( @a file, @a argv, @a mode )
|
|
* and sets @c failbit on error.
|
|
*
|
|
* @param file a string containing the pathname of a program to execute.
|
|
* @param argv a vector of argument strings passed to the new program.
|
|
* @param mode the I/O mode to use when opening the pipe.
|
|
* @see basic_pstreambuf::open(const std::string&, const argv_type&, pmode)
|
|
*/
|
|
template <typename C, typename T>
|
|
inline void
|
|
pstream_common<C,T>::do_open( const std::string& file,
|
|
const argv_type& argv,
|
|
pmode mode )
|
|
{
|
|
if (!buf_.open((command_=file), argv, mode))
|
|
this->setstate(std::ios_base::failbit);
|
|
}
|
|
|
|
/** Calls rdbuf->close() and sets @c failbit on error. Returns
|
|
* process's exit status, as pclose(3) does. */
|
|
template <typename C, typename T>
|
|
inline int
|
|
pstream_common<C,T>::close()
|
|
{
|
|
if (!buf_.close())
|
|
this->setstate(std::ios_base::failbit);
|
|
return buf_.status();
|
|
}
|
|
|
|
/**
|
|
* @return rdbuf()->is_open().
|
|
* @see basic_pstreambuf::is_open()
|
|
*/
|
|
template <typename C, typename T>
|
|
inline bool
|
|
pstream_common<C,T>::is_open() const
|
|
{
|
|
return buf_.is_open();
|
|
}
|
|
|
|
/** @return a string containing the command used to initialise the stream. */
|
|
template <typename C, typename T>
|
|
inline const std::string&
|
|
pstream_common<C,T>::command() const
|
|
{
|
|
return command_;
|
|
}
|
|
|
|
/** @return a pointer to the private stream buffer member. */
|
|
// TODO document behaviour if buffer replaced.
|
|
template <typename C, typename T>
|
|
inline typename pstream_common<C,T>::streambuf_type*
|
|
pstream_common<C,T>::rdbuf() const
|
|
{
|
|
return const_cast<streambuf_type*>(&buf_);
|
|
}
|
|
|
|
|
|
#if REDI_EVISCERATE_PSTREAMS
|
|
/**
|
|
* @def REDI_EVISCERATE_PSTREAMS
|
|
* If this macro has a non-zero value then certain internals of the
|
|
* @c basic_pstreambuf template class are exposed. In general this is
|
|
* a Bad Thing, as the internal implementation is largely undocumented
|
|
* and may be subject to change at any time, so this feature is only
|
|
* provided because it might make PStreams useful in situations where
|
|
* it is necessary to do Bad Things.
|
|
*/
|
|
|
|
/**
|
|
* @warning This function exposes the internals of the stream buffer and
|
|
* should be used with caution. It is the caller's responsibility
|
|
* to flush streams etc. in order to clear any buffered data.
|
|
* The POSIX.1 function <b>fdopen</b>(3) is used to obtain the
|
|
* @c FILE pointers from the streambuf's private file descriptor
|
|
* members so consult your system's documentation for
|
|
* <b>fdopen</b>(3).
|
|
*
|
|
* @param in A FILE* that will refer to the process' stdin.
|
|
* @param out A FILE* that will refer to the process' stdout.
|
|
* @param err A FILE* that will refer to the process' stderr.
|
|
* @return An OR of zero or more of @c pstdin, @c pstdout, @c pstderr.
|
|
*
|
|
* For each open stream shared with the child process a @c FILE* is
|
|
* obtained and assigned to the corresponding parameter. For closed
|
|
* streams @c NULL is assigned to the parameter.
|
|
* The return value can be tested to see which parameters should be
|
|
* @c !NULL by masking with the corresponding @c pmode value.
|
|
*
|
|
* @see <b>fdopen</b>(3)
|
|
*/
|
|
template <typename C, typename T>
|
|
std::size_t
|
|
basic_pstreambuf<C,T>::fopen(FILE*& in, FILE*& out, FILE*& err)
|
|
{
|
|
in = out = err = NULL;
|
|
std::size_t open_files = 0;
|
|
if (wpipe() > -1)
|
|
{
|
|
if ((in = ::fdopen(wpipe(), "w")))
|
|
{
|
|
open_files |= pstdin;
|
|
}
|
|
}
|
|
if (rpipe(rsrc_out) > -1)
|
|
{
|
|
if ((out = ::fdopen(rpipe(rsrc_out), "r")))
|
|
{
|
|
open_files |= pstdout;
|
|
}
|
|
}
|
|
if (rpipe(rsrc_err) > -1)
|
|
{
|
|
if ((err = ::fdopen(rpipe(rsrc_err), "r")))
|
|
{
|
|
open_files |= pstderr;
|
|
}
|
|
}
|
|
return open_files;
|
|
}
|
|
|
|
/**
|
|
* @warning This function exposes the internals of the stream buffer and
|
|
* should be used with caution.
|
|
*
|
|
* @param in A FILE* that will refer to the process' stdin.
|
|
* @param out A FILE* that will refer to the process' stdout.
|
|
* @param err A FILE* that will refer to the process' stderr.
|
|
* @return A bitwise-or of zero or more of @c pstdin, @c pstdout, @c pstderr.
|
|
* @see basic_pstreambuf::fopen()
|
|
*/
|
|
template <typename C, typename T>
|
|
inline std::size_t
|
|
pstream_common<C,T>::fopen(FILE*& fin, FILE*& fout, FILE*& ferr)
|
|
{
|
|
return buf_.fopen(fin, fout, ferr);
|
|
}
|
|
|
|
#endif // REDI_EVISCERATE_PSTREAMS
|
|
|
|
|
|
} // namespace redi
|
|
|
|
/**
|
|
* @mainpage PStreams Reference
|
|
* @htmlinclude mainpage.html
|
|
*/
|
|
|
|
#endif // REDI_PSTREAM_H_SEEN
|
|
|
|
// vim: ts=2 sw=2 expandtab
|
|
|