Tuesday, January 22, 2008

Fork/exec while redirecting I/O

Here's a common problem in (POSIX) C applications: let's say you want to execute another application, but you want to redirect this application's standard I/O (i.e., stdin, stdout, stderr) to a file descriptor(s) of your choice. Perhaps you want to log the output to a file, or stream the application's input from a file you just created, or collect error messages (stderr) for parsing later.

The function below illustrates how to do this:

pid_t
exec_redirect(
const char *path, char *const args[],
int new_in, int new_out, int new_err
)
{

pid_t child;

// Fork new process
child = fork();

if (child == 0) {

// Child process

// Redirect standard input
if (new_in >= 0) {
close(STDIN_FILENO);
dup2(new_in, STDIN_FILENO);
}

// Redirect standard output
if (new_out >= 0) {
close(STDOUT_FILENO);
dup2(new_out, STDOUT_FILENO);
}

// Redirect standard error
if (new_err >= 0) {
close(STDERR_FILENO);
dup2(new_err, STDERR_FILENO);
}

// Execute the command
execv(path, args);
}

// Parent process
return child;

}

This function takes the parameters path and args which is the same as what you would normally pass to execv(3). In addition, there are three parameters, new_in, new_out, and new_err which are (optional) file descriptors where the new application's standard input, output, and error, respectively, will be redirected from/to. To make redirection optional, we specify that if the descriptor parameter is -1, then no redirection will take place.

So how do we use this? The code snippet below executes the command line /bin/myapp -l -p, with input redirected from in.txt, output redirected to out.txt, and error redirected to err.txt:

    ...
char *cmd = "/bin/myapp";
char *args[] = { "/bin/myapp", "-l", "-p", NULL };

in = open("in.txt", O_RDONLY);
out = open("out.txt", O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
err = open("err.txt", O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);

exec_redirect(cmd, args, in, out, err);
...

On a closing note, it's also probably a good idea to close() the file handles in the parent process, after exec_redirect(), just to prevent "inadvertently" accessing them while the child process is also doing the same. Yes, the pun was intended ;-)

[Update: fixed some typos]

2 comments:

Pavan said...

Hi,
How do we Ensure that Parent waits for the child to Complete Execution ?
Do we need to call WaitPId with the childs Pid ?

Ron said...

@Pavan,
Yup, if you want to synchronize the parent with the child termination, call a waitpid() on the parent, with the child's pid (or -1 to wait for any child).

Alternatively, if you just want some notification that the child has terminated, you can also just register a SIGCHLD handler and read the exit status of the child from there (also via waitpid).