Chapter 15. Forking other processes

Index

Redirecting file descriptors

Starting execution threads, with either x::run() or by using std::thread is generally mutually-exclusive with a multi-process based approach that uses fork(). This is because another execution thread could be in the middle of using some mutex or a condition variable, at fork() time. This results in a child process inheriting a potentially inconsistent internal contents of the mutex or a condition variable.

It is safe to fork() and immediately exec() something else. x::forkexec provides a convenient way to implement this. Here's an example. This is not really the best way to read the contents of a directory, this is just an example of using x::forkexec;

#include <x/forkexec.H>

if (x::forkexec("ls", "-al").system() != 0)
{
    std::cout << "Where did ls go?" << std::endl;
}

x::forkexec itself is just a container for an external executable's command line parameters and other attributes. The constructor takes a variadic list of strings, naming the program and any arguments. Alternatively, the constructor can also take a single std::vector<std::string>, which cannot be empty.

system() forks and has the child process exec the program, then waits for the child process to exit. This is exactly equivalent to:

#include <x/forkexec.H>

x::forkexec lsal("ls", "-al");

pid_t p=lsal.spawn();

if (x::forkexec::wait4(p) != 0)
{
    std::cout << "Where did ls go?" << std::endl;
}

system() is equivalent to a spawn(), which forks and execs the child process without waiting for it to terminate, followed by wait4().

spawn_detached() is an alternative to spawn() that forks twice before execing, with the first child process exiting, leaving the parent process without a child process. init becomes the exec'd process's parent.

The name of the process to execute gets taken from the first argument vector string. program overrides it:

#include <x/forkexec.H>

if (x::forkexec("-sh").program("/bin/bash").system() != 0)
{
    std::cout << "Where did ls go?" << std::endl;
}

This executes /bin/bash, but it gets -sh as its argv[0].

In all cases, the child process's filename can have an explicit path, or use PATH to find it.

If the process cannot be executed for some reason, system(), spawn(), or spawn_detached() throws an exception. There's also an exec() method, which execs the current process. Either it succeeds, and never returns, or throws an exception.

Redirecting file descriptors

#include <x/forkexec.H>

x::forkexec wc("wc", "-l");

x::fd wcstdin = wc.pipe_to();
x::fd wcstdout = wc.pipe_from();

pid_t p=wc.spawn();

// ...

After constructing an x::forkexec but before starting the process, pipe_to() makes arrangements to attach a pipe to the new process's standard input, and returns the write side of the pipe. Similarly, pipe_from() attaches a pipe to the new process's standard output, and returns the read side of the pipe.

pipe_from() and pipe_to() take an optional int argument, naming an explicit file descriptor, rather than standard input or output. There's also a socket_fd(), that creates a bidirectional pipe socket, attaches one end of it to the new process's file descriptor, then returns the other end of the socket pipe. Finally, set_fd() takes a numerical file descriptor number, and an x::fd that you created, and attaches it to the new process's file descriptor (when it actually gets started).