Some of these questions haven't even been asked once, and if I write this maybe they never will be. If you have questions not answered here please send them to the author or one of the PStreams mailing lists.
pstream
always fail ?pstreambuf
destructor ?EOF
to the child process?open()
?popen()
?Aside from all the usual reasons to prefer IOStreams to stdio, the PStreams
classes give you access to any combination of a process' stdin
,
stdout
and stderr
, as well as interfaces to
wait(2),
kill(2)
and other system calls.
Most implementations of
popen(3)
only support
unidirectional I/O and do not
give you as much control of the process.
Simple text-based IPC between two processes. There are more flexible and powerful forms of IPC, but for many applications sending and receiving text on the standard streams is sufficient. PStreams makes this almost as easy as file IO (as it should be in UNIX!)
The original aim was to re-use functionality of existing programs rather than re-implement them, e.g. read the contents of a directory using ls(1), or send an email using mail(1), or perform arbitrary precision arithmetic using dc(1). The overhead of forking and communicating with a new process might not matter if a well-known and well-tested program already exists with the required functionality. This fits well in the UNIX tradition of combining simple tools into more complex systems using pipes and filters.
You can also use PStreams within a GUI application to run a child process and capture it's output for display in the parent process.
pstream
always fail ?
PStreams uses pipes (see
pipe(2)) to communicate
with the controlled process and it is not possible to reposition a stream
associated with a pipe.
The pstreambuf
class supports putting up to
pstreams::pbackfail
characters back
into the stream buffer, but this does not return the characters to the pipe
or have any effect on the controlled process.
Use open(string, vector<string>, pmode)
and call is_open()
.
Call pstreambuf::status() after the process has exited.
ipstream s("hostname"); s.close(); if (s.rdbuf()->exited()) std::cout << "Exit status was " << r.rdbuf()->status();
pstreambuf
destructor ?
The destructor waits for the child process to exit. Before the pstreambuf destructor
runs you should ensure the process exits (e.g. by sending EOF
if it
reads from stdin
) or send a signal using pstreambuf::kill()
.
TODO add a "kill_in_dtor" member to pstreambuf so the child gets killed in the dtor, so throwing an exception doesn't hang in dtor if the child is still running.
EOF
to the child process?If the child process needs to read all data before it produces output
and/or exits then you must close the child process' stdin
so that it receives the end-of-file indicator. This is done by calling
pstreambuf::peof()
, or inserting the peof
manipulator into the stream.
See How do I send EOF
to the child process?
If the child process has produced output but reading from the stream blocks
indefinitely then it is probably caused by the buffering done by
pstreambuf
. By default pstreambuf
will try to
fill its internal buffer when reading from the child process; if the number
of characters available in the pipe is less than the buffer size then the
read will block until more input is available. (The buffer size is given
by the constant pstreams::bufsz
.)
The std::istream
interface is not really designed for
non-blocking I/O but we do have the std::streambuf::in_avail()
function, which tells you how many characters can be read from the stream
without blocking. PStreams uses in_avail()
to avoid the default
blocking behaviour when reading. If in_avail()
is called
on a pstreambuf
when its internal character buffer is empty,
a non-blocking read will be used to fill the buffer. So if
in_avail()
returns a positive number then that many characters
are already in the buffer and can be read immediately without
blocking.
Rather than of calling in_avail()
directly, you can use
std::istream::readsome(char_type* s, streamsize n)
which
will read min(rdbuf()->in_avail(), n)
characters and store
them at s
.
PStreams does not provide any way of setting a time out but it's fairly easy to implement using signals. A signal can be scheduled using alarm(2) or setitimer(2) and a handler can set a flag to indicate the signal was raised. If the flag is set when the read returns it indicates that the signal was raised before the read completed, so you should take appropriate action, such as killing the child process.
If using alarm signals in this way, be sure to reset the alarm, the flag and the signal handler after the read returns.
If the pstream was opened with pstreams::pstderr
in the pmode
flags then the stream buffer will read from a pipe attached to the child process'
standard error stream (stderr
). The data from the process' standard output
and standard error are kept separate and the stream buffer can only read one at a time.
You can change which stream is currently active using the ipstream::out()
and ipstream::err()
member functions.
If you have read all the available data from the child's stdout
and reached EOF then eofbit
will be set in the stream state,
so you will need to clear it by calling clear()
on the pstream
object before you can read from the stderr
(and vice versa if you
reach EOF on the stderr
stream and then want to read
stdout
).
A simpler alternative when
launching a process with a string
is to create the child process with "./command 2>&1"
which
will cause its standard error to be combined with the standard output stream.
open()
?
The pstreambuf::open()
function and the constructors for all the
pstreams types are overloaded to allow them to be called in two ways:
These overloads use execvp(3) so emulate the behaviour of the shell in searching for a file to execute. Each argument is passed to the new process individually without any word splitting or expansion done by the shell.
In C++11 you can pass an initializer list to these overloads, e.g.
redi::pstream ip({ "grep", "pattern", "file" });
.
These overloads take a std::string
containing one or more
shell commands which will be run as /bin/sh -c command
by calling
execl(3).
The shell will process and run the commands in the string, performing the usual steps such as word splitting and expansions. This allows wildcards and redirections to be used but is vulnerable to shell injection attacks if used with arbitrary user input, so should be used with care.
Possible future extension: allow user to specify something other than /bin/sh
Possible future extension: allow user to choose to use execv instead of execvp, or execlp instead of execl.
You should be able to use PStreams with any fairly standard-conforming compiler on a POSIX system (i.e. UNIX-like). The most important thing is that the compiler supports standard IOStreams.
__USE_STD_IOSTREAM
to get standard IOStreams.
SIGCHLD
when child exits etc.
SIGPIPE
if write to closed pipe (after child exits).
Usual IOStreams exceptions, none from PStreams itself.
Signals should be handled by the caller.
TODO - make exception-safe, all allocations use ScopeGuard.
PStreams classes are designed to provide the basic exception-safety guarantee. If you find any leaks or crashes they are bugs that will be fixed if you report them.
However, If an exception is thrown and causes an active pstreambuf to be destroyed the process could hang indefinitely if the child process does not exit. TODO - pguard, calls kill in dtor.
Pstreams follows the same rules as the C++11 standard library:
accessing an object from multiple threads is only safe if all concurrent
accesses use const
members only.
If any thread uses a non-const
member function then you are
responsible for performing the necessary synchronisation to ensure that
no other thread accesses the object at the same time.
Only by using Cygwin, PStreams only works on POSIX systems.
If you only want a console application you can use the old popen-based
branch (release 0.17), which uses the Win32 functions _popen()
and _pclose()
, but these will not work in a GUI program (and
might blow up your PC, if Windows doesn't do it for you.)
This version doesn't have most of the library's features and is no longer
maintained or tested.
You could also try libexecstream instead, which is similar to PStreams but cross-platform.
Yes. The current code will eventually be released as version 1.0 without many more changes. Version 2.0 will have a new process class that pstreambuf will use, so there will be a number of changes due to that, but that may never actually get written! Since all PStreams classes are templates it is not possible to hide changes to the implementation in a library, so even if the public interface remains stable you might need to recompile all your code that uses PStreams if you use a newer version of pstream.h.
I might also consider the following changes before version 1.0.0:
is_open()
could be renamed to be clear it means "process was executed".
exited()
could be renamed to be clear it means "executed process has stopped running".