Let's face it: spawning child processes in Unix is a "mess". Yes, the interfaces involved (fork, wait, pipe) are really elegant and easy to understand, but every single time you need to spawn a new child process to, later on, execute a random command, you have to write quite a bunch of error-prone code to cope with it. If you have ever used any other programming language with higher-level abstraction layers — just check Python's subprocess.Popen — you surely understand what I mean.

The current code in ATF has many places were child processes have to be spawned. I recently had to add yet another case of this, and... enough was enough. Since then, I've been working on a C API to spawn child processes from within ATF's internals and just pushed it to the repository. It's still fairly incomplete, but with minor tweaks, it'll keep all the dirty details of process management contained in a single, one-day-to-be-portable module.

The interface tries to mimic the one that was designed on my Boost.Process Summer of Code project, but in C, which is quite painful. The main idea is to have a fork function to which you pass the subroutine you want to run on the child, the behavior you want for the stdout stream and the behavior you want for the stderr steam. These behaviors can be any of capture (aka create pipes for IPC communcations), silence (aka redirect to /dev/null), redirect to file descriptor and redirect to file. For simplicity, I've omitted stdin. With all this information, the fork function returns you an opaque structure representing the child, from which you can obtain the IPC channels if you requested them and on which you can wait for finalization.

Here is a little example, with tons of details such as error handling or resource finalization removed for simplicity. The code below would spawn "/bin/ls" and store its output in two files named ls.out and ls.err:
static
atf_error_t
run_ls(const void *v)
{
system("/bin/ls");
return atf_no_error();
}

static
void
some_function(...)
{
atf_process_stream_t outsb, errsb;
atf_process_child_t child;
atf_process_status_t status;

atf_process_status_init_redirect_path(&outsb, "ls.out");
atf_process_status_init_redirect_path(&errsb, "ls.err");

atf_process_fork(&child, run_ls, &outsb, &errsb, NULL);
... yeah, here comes the concurrency! ...
atf_process_child_wait(&child, &status);

if (atf_process_status_exited(&status))
printf("Exit: %dn", atf_process_status_exitstatus(&status));
else
printf("Error!");
}
Yeah, quite verbose, huh? Well, it's the price to pay to simulate namespaces and similar other things in C. I'm not too happy with the interface yet, though, because I've already encountered a few gotchas when trying to convert some of the existing old fork calls to the new module. But, should you want to check the whole mess, check out the corresponding revision.

Go to posts index

Comments from the original Blogger-hosted post: