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.
#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).