PStreams

POSIX Process Control in C++

PStreams

Introduction

PStreams allows you to run another program from your C++ application and to transfer data between the two programs similar to shell pipelines.

In the simplest case, a PStreams class is like a C++ wrapper for the POSIX.2 functions popen(3) and pclose(3), using C++ IOStreams instead of C's stdio library.

The library provides class templates in the style of the standard IOStreams that can be used with any standard-conforming C++ compiler on a POSIX platform. The classes use a streambuf class that uses fork(2) and the exec(2) family of functions to create a new process and to write/read data to/from the process.

The advantages over the standard popen() function are:

The library is available under the GNU Lesser General Public License

To help improve PStreams see the SourceForge project page.

 

Status

Latest release is 0.5.2

PStreams is now at beta stage, with working ipstream and opstream classes for ISO C++-compliant compilers, providing a convenient C++ alternative to popen() and much more...

The stream buffer class, pstreambuf, creates a new process and uses up to three pipes to communicate with it. After the new process executes a user-supplied command the controlling PStream class has access to any combination of the process' stdin, stdout and stderr streams.

Another class, rpstream (Restricted PStream) is similar to pstream except that the child process' stdout and/or stderr cannot be read directly from the rpstream. To read from the process you must call either rpstream::out() or rpstream::err() to obtain a reference to an istream that reads from the process' corresponding output stream. This class is not as well tested as the others

The pstreambuf class can send signals to the controlled process and can report the process' exit status.

No code-conversion is performed on multi-byte character streams. It should be possible to use the PStream classes templatized with character types other than char (e.g. basic_pstream<int>) but note that characters are transfered in a bytewise manner, so it is the programmer's responsibility to interpret the resulting character strings. Since the classes are intended to be used to read/write data between processes, which will usually share an internal character representation, rather than to/from files, this behaviour should be sufficient.

For non-ISO C++-compliant compilers there are alternative versions of all classes. These alternative versions are unsupported and not under development! They use non-standard extensions found in some old IOStream implementations (such as attaching an fstream's buffer to a file descriptor with fstreambase::attach(int fd)) that may not be available on some systems. For GCC 2.7/2.8/2.9x/egcs these classes are functional, but are implemented using fstream and provide no more functionality than the pfstream classes provided with libg++. For all other compilers these classes have never even been compiled, let alone tested, so use them at your own risk.
The non-compliant versions are defined in the pstream_compat.h file and can be used by defining BACK_COMPAT (or GCC_BACK_COMPAT for the libg++ versions) to be 1.
Instantiating the backward-compatible ipstream or opstream currently sets errno to ESPIPE because the fstreambase tries to seek to the end of the stream, which is not allowed for pipes. errno is reset by the classes.

 

Documentation

The FAQ answers some general questions about PStreams.
PStreams uses Doxygen to automatically generate API documentation from the sources.
The docs for the latest release are available online.
Docs for previous releases can be generated from the sources.

 

Download

Latest release is 0.5.2

Releases and anonymous CVS details are on the download page.

 

Usage

Please refer to the doxygen-generated documentation.

Using the PStreams classes is similar to using a std::fstream, except that a shell command is given rather than a filename:


// print names of all header files in current directory
redi::ipstream in("ls ./*.h");
std::string str;
while (in >> str) {
    std::cout << str << std::endl;
}

The command argument is a pointer to a null-terminated string containing a shell command line. This command is passed to /bin/sh using the -c flag; Alias and wildcard interpretation, if any, is performed by the shell.

Alternatively, the process can be started with a vector of arguments:


// remove some files, capturing any error messages
std::vector<std::string> argv;
std::vector<std::string> errors;
argv.push_back("rm");
argv.push_back("./foo.txt");
argv.push_back("./bar.html");
redi::ipstream in("rm", argv, pstreambuf::pstderr);
std::string errmsg;
while (std::getline(in, errmsg)) {
    errors.push_back(errmsg);
}

If this form of initialisation is used and the file argument doesn't contain a slash then the actions of the shell will be duplicated in searching for an executable in PATH. The shell will not interpret the other arguments, so wildcard expansion will not take place if this interface is used.

If an rpstream was used in the example above it would be necessary to replace the while condition like so:


while (std::getline(in.err(), errmsg)) {
    errors.push_back(errmsg);
}

This form can also be used with the unrestricted pstream and ipstream classes, but it is not strictly necessary.

top

 

Credits

The inspiration for this project came from seeing a message on the libstdc++-v3 mailing list asking what had happened to the pfstream classes that came with libstdc++-v2. Because I'd used a similar C++ wrapper for popen() where I work, and because I want to learn about Standard IOStreams, I decided to try to write a solution that would work with standard-conforming C++ libraries. (The pfstream classes were based on the old AT&T-style IOStreams and relied on GCC-specific libraries.) PStreams is the result of that decision.

The following have helped in one way or another:
Angelika Langer & Klaus Kreft for the invaluable examples in their book Standard C++ IOStreams and Locales.
John Levon for comments and advice on the interface design.
Philippe Elie who wrote the ChildReader class for the OProfile project, which was a good starting point when replacing popen() with hand-rolled fork/exec code.
Per Bothner et al. for the pfstream classes in the old libg++.
SourceForge for hosting this site and the project.
Brett Williams for testing things and finding the bugs I miss.

top