![]() |
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 stderr
and stdout
in 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 |