Boost C++ Libraries

PrevUpHomeNext

5.Platform-specific usage

5.1. The POSIX platform
5.2. The Win32 platform

The usage chapter focused on explaining all features provided by Boost.Process that are available under all supported platforms. However, those features may be too limited when portability across different platforms is not a determining factor; in such cases, you will want to access the full power of the underlying operating system to manage processes. Boost.Process lets you do so through specialized classes — even if they are not enough for your use case, you can always design your own.

This chapter describes all platform-specific features available in Boost.Process. Keep in mind that using them will lower your application's portability.

As we saw earlier in the usage chapter, all platforms supported by Boost.Process provide three communication channels to each process. Although these are enough in almost all use cases, some applications can take advantage of more data flows. For example, they may support multiple input streams so that external processes can feed in different types of data, or emit messages through more than two output streams to clearly separate their purpose.

The POSIX platform allows the configuration of more than three communication channels thanks to the way fork(2) works: any file descriptor can be used to connect two different processes through an anonymous pipe. Boost.Process can take advantage of such feature and configure more than three data flows by using the specialized POSIX launcher and POSIX child classes, both based on the generic implementations.

Before continuing, it is interesting to remember that POSIX systems identify communication channel with plain integers because they are regular file descriptors. The three standard communication channels are typically attached to fixed file descriptors and the cstdlib standard header provides constants to refer to them; these constants shall be used instead of the integer values to achieve maximum portability and clarity.

Channel Symbolic constant Typical value
Standard input STDIN_FILENO 0
Standard output STDOUT_FILENO 1
Standard error STDERR_FILENO 2

The POSIX launcher adds two additional methods to the generic launcher that allow the user to specify the behavior of non-standard file descriptors; these are posix_launcher::set_input_behavior and posix_launcher::set_output_behavior. The former is used to configure a child's input stream and the latter an output one.

Once the streams are configured and the child process is running, the caller access the child's streams as it did with the generic child. However, non-standard streams are only available through two additional methods: posix_child::get_input and posix_child::get_output.

Non-standard streams can also be merged as done with stderrand stdoutin the generic case. This functionality is provided through the posix_launcher::merge_outputs method.

All these methods can be seen as general cases of those provided by the generic launcher. The following table illustrates the equivalences:

Portable call Equivalent to
launcher::set_stdin_behavior(b) posix_launcher::set_input_behavior(STDIN_FILENO, b)
launcher::set_stdout_behavior(b) posix_launcher::set_output_behavior(STDOUT_FILENO, b)
launcher::set_stderr_behavior(b) posix_launcher::set_output_behavior(STDERR_FILENO, b)
launcher::set_merge_out_err(true) posix_launcher::merge_outputs(STDERR_FILENO, STDOUT_FILENO)
child::get_stdin() posix_child::get_input(STDIN_FILENO)
child::get_stdout() posix_child::get_output(STDOUT_FILENO)
child::get_stderr() posix_child::get_output(STDERR_FILENO)

The following example program illustrates the use of these functions. It uses the D-BUS daemon application because it allows to print useful information to two non-standard streams (3 and 4 in the code below). The example utility captures these messages and provides them to the user:

extern "C" {
#include <unistd.h>
}

#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>

#include <boost/process.hpp>

//
// Error out early if we are trying to build this non-portable example
// code under a platform that does not provide the required posix_*
// classes.
//
#if !defined(BOOST_PROCESS_POSIX_API)
#   error "Unsupported platform."
#endif

namespace bp = ::boost::process;

int
main(int argc, char* argv[])
{
    //
    // Constructs a command line to launch a new D-BUS session daemon.
    //
    bp::command_line cl("/usr/pkg/bin/dbus-daemon");
    cl.argument("--fork");
    cl.argument("--session");

    //
    // The following arguments ask the dbus-daemon program to print the
    // new daemon's bind address and PID into two non-standard streams
    // (i.e. not stdout nor stderr).
    //
    cl.argument("--print-address=3");
    cl.argument("--print-pid=4");

    //
    // Constructs the launcher for the previous command line.  We ask
    // it to inherit our stdout and stderr for simplicity and we capture
    // the two non-standard streams into which the daemon will print the
    // communication information.
    //
    bp::posix_launcher l;
    l.set_output_behavior(STDOUT_FILENO, bp::inherit_stream);
    l.set_output_behavior(STDERR_FILENO, bp::inherit_stream);
    l.set_output_behavior(3, bp::redirect_stream);
    l.set_output_behavior(4, bp::redirect_stream);

    //
    // Spawns the child process.
    //
    bp::posix_child c = l.start(cl);

    //
    // Reads the information printed by the dbus-daemon child process
    // from the two non-standard channels.
    //
    std::string address;
    pid_t pid;
    c.get_output(3) >> address;
    c.get_output(4) >> pid;

    //
    // Waits until the process exits and parses its termination status.
    //
    bp::status s = c.wait();
    if (s.exited()) {
        if (s.exit_status() == EXIT_SUCCESS) {
            std::cout << "D-BUS daemon's address is: " << address << std::endl;
            std::cout << "D-BUS daemon's PID is: " << pid << std::endl;
        } else
            std::cout << "D-BUS daemon returned error condition: "
                      << s.exit_status() << std::endl;
    } else {
        std::cout << "D-BUS daemon terminated abnormally" << std::endl;
    }

    return s.exited() ? s.exit_status() : EXIT_FAILURE;
}

Processes under POSIX operating systems carry several properties that describe their security credentials. All of these can be configured through the POSIX launcher prior startup of a new process, as seen in the following table:

Concept Abbreviation POSIX launcher method
Real and effective user IDs UID posix_launcher::set_uid(uid)
Effective user ID EUID posix_launcher::set_euid(uid)
Real and effective group IDs GID posix_launcher::set_gid(gid)
Effective group ID EGID posix_launcher::set_egid(gid)

Note that changing the security credentials of a process is a privileged operation generally restricted to the super user. For more information you should see your operating system's documentation on the setuid(2), seteuid(2), setgid(2) and setegid(2) system calls.

Every process in a POSIX system has a root directory, used to resolve paths aside from the current working directory. This root directory is used to restrict processes to view only a part of the global file system: the process is not allowed to see the real file system's root directory; instead it sees the specified root directory as if it really were the file system's root. See the chroot(2) system call documentation for more details.

The specialized POSIX launcher supports chaning the root directory of a new process, always assuming that sufficient privileges are available (i.e. the caller must be the super user). This is done through the posix_launcher::set_chroot method.

The POSIX's wait(2) family of system calls returns a lot of information about the status of a finalized process, not only the exit status code provided on a normal exit. This information includes additional termination reasons such as if the process dumped a core file, if it exited due an external signal, etc.

The information described above can be queried through the posix_status class. This is built on top of the regular status class and includes additional methods to query all additional details. It can be used anywhere a status object is created thanks to its conversion constructor. For example:

extern "C" {
#include <unistd.h>
}

#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>

#include <boost/process.hpp>

//
// Error out early if we are trying to build this non-portable example
// code under a platform that does not provide the required posix_*
// classes.
//
#if !defined(BOOST_PROCESS_POSIX_API)
#   error "Unsupported platform."
#endif

namespace bp = ::boost::process;

int
main(int argc, char* argv[])
{
    if (argc < 2) {
        std::cerr << "Please provide a program name." << std::endl;
        return EXIT_FAILURE;
    }

    //
    // Constructs a command line based on the arguments provided to the
    // program.
    //
    bp::command_line cl(argv[1]);
    for (int i = 2; i < argc; i++)
        cl.argument(argv[i]);

    //
    // Sets up a launcher inheriting all the three standard streams.
    //
    bp::launcher l;
    l.set_stdin_behavior(bp::inherit_stream);
    l.set_stdout_behavior(bp::inherit_stream);
    l.set_stderr_behavior(bp::inherit_stream);

    //
    // Spawns the new child process.
    //
    bp::child c = l.start(cl);

    //
    // Waits until the process exits and parses its termination status.
    // Note that we receive a posix_status object even when the wait()
    // method returns a status one.
    //
    bp::posix_status s = c.wait();
    if (s.exited()) {
        std::cout << "Program returned exit code " << s.exit_status()
                  << std::endl;
    } else if (s.signaled()) {
        std::cout << "Program received signal " << s.term_signal()
                  << std::endl;
        if (s.dumped_core())
            std::cout << "Program also dumped core" << std::endl;
    } else if (s.stopped()) {
        std::cout << "Program stopped by signal" << s.stop_signal()
                  << std::endl;
    } else {
        std::cout << "Unknown termination reason" << std::endl;
    }

    return s.exited() ? s.exit_status() : EXIT_FAILURE;
}

The Win32 CreateProcess system call receives a STARTUPINFO object that contains multiple details on how to configure the new process. Among these are the handles for the three standard communication channels (internally set up by the library), hints to set up the application's main window, etc.

The Win32-specific launcher provides mechanisms to provide some of this platform-specific information to the new process. This class' constructor receives a pointer to an already initialized STARTUPINFO object that is later passed to the CreateProcess call. If no such object is provided, the launcher behaves as the generic launcher.

The example below demonstrates this feature. It relies on features provided by Win32 operating systems to start a GUI process with hints on how to create the main window. The example passes the suggested window position as well as size and then waits until the new process terminates.

extern "C" {
#include <windows.h>
}

#include <cstdlib>
#include <iostream>

#include <boost/process.hpp>

//
// Error out early if we are trying to build this non-portable example
// code under a platform that does not provide the required win32_*
// classes.
//
#if !defined(BOOST_PROCESS_WIN32_API)
#   error "Unsupported platform."
#endif

namespace bp = ::boost::process;

int
main(int argc, char* argv[])
{
    //
    // Creates a new STARTUPINFO object, specific to the Win32 platform,
    // that specifies the position and size of the child process' main
    // window.  Note that this is just a hint to the process, which may
    // choose to ignore our settings.
    //
    STARTUPINFO si;
    ::ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.dwFlags |= STARTF_USEPOSITION | STARTF_USESIZE;
    si.dwX = 0;
    si.dwY = 0;
    si.dwXSize = 640;
    si.dwYSize = 480;

    //
    // The application we have to launch.  We default to Notepad because,
    // depending on its version, it is possible that it honors the window
    // settings described above.
    //
    std::string app = argc < 2 ? "notepad" : argv[1];

    //
    // The value returned by the example program.
    //
    int exitstatus = EXIT_FAILURE;

    try {
        //
        // Constructs a command line to launch the application chosen
        // above.  If the name does not contain any directory component,
        // it will be searched in the PATH.
        //
        bp::command_line cl(app);

        //
        // Constructs a Win32-specific launcher with the start settings
        // we configured.
        //
        bp::win32_launcher l(&si);

        //
        // Starts the application and waits for its termination, reporting
        // the results to the user.
        //
        bp::status s = l.start(cl).wait();
        if (s.exited()) {
            std::cout << "Application exited successfully" << std::endl;
            exitstatus = EXIT_SUCCESS;
        } else {
            std::cout << "The application returned an error" << std::endl;
        }
    } catch (bp::not_found_error< std::string > e) {
        std::cout << "Could not find " << app << " in path." << std::endl;
    }

    return exitstatus;
}

The Win32 CreateProcess system call starts a new process and returns a handle and an identifier for both the application's process and its main thread. Due to portability restrictions, the generic child implementation does not allow access to this information but, fortunately, the Win32-speficic child does. The win32_child class provides access to the information returned by the CreateProcess system call as described below:

PROCESS_INFORMATION field Win32 child method
hProcess win32_child::get_handle
dwProcessId win32_child::get_id
hThread win32_child::get_primary_thread_handle
dwThreadId win32_child::get_primary_thread_id

Win32 child objects can only be constructed by using the Win32-specific launcher even if the user does not need any of the extra features provided by that class.

The following example demonstrates how a program can retrieve all the information returned by Win32's CreateProcess system call; that is: the process' and primary thread's identifier and handle. It relies on the Win32-specific launcher and child classes to be able to access this information:

extern "C" {
#include <windows.h>
}

#include <cstdlib>
#include <iostream>

#include <boost/process.hpp>

//
// Error out early if we are trying to build this non-portable example
// code under a platform that does not provide the required win32_*
// classes.
//
#if !defined(BOOST_PROCESS_WIN32_API)
#   error "Unsupported platform."
#endif

namespace bp = ::boost::process;

int
main(int argc, char* argv[])
{
    int exitstatus = EXIT_FAILURE;

    try {
        //
        // Constructs a command line to launch Notepad, looking for its
        // availability in the PATH.
        //
        bp::command_line cl("notepad");

        //
        // Constructs a Win32-specific launcher.  We do not need any of
        // its extra features (compared to the regular launcher), but we
        // must use it in order to construct a Win32-specific child.
        //
        bp::win32_launcher l;

        //
        // Starts the process.
        //
        bp::win32_child c = l.start(cl);

        //
        // Prints out information about the new process.  Note that,
        // except for the process handle, the other information is only
        // available because we are using the win32_child.
        //
        std::cout << "Process handle            : 0x"
                  << c.get_handle() << std::endl;
        std::cout << "Process identifier        : "
                  << c.get_id() << std::endl;
        std::cout << "Primary thread handle     : 0x"
                  << c.get_primary_thread_handle() << std::endl;
        std::cout << "Primary thread identifier : "
                  << c.get_primary_thread_id() << std::endl;

        //
        // Waits until the process terminates and reports status.
        //
        bp::status s = c.wait();
        if (s.exited()) {
            std::cout << "Application exited successfully" << std::endl;
            exitstatus = EXIT_SUCCESS;
        } else {
            std::cout << "The application returned an error" << std::endl;
        }
    } catch (bp::not_found_error< std::string > e) {
        std::cout << "Could not find notepad in path." << std::endl;
    }

    return exitstatus;
}
Copyright 2006 Julio M. Merino Vidal

PrevUpHomeNext