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