Capturing Input/Output of Another Process in C

In my travels in C programming, I periodically need to run another process and redirect its standard output back to the first process. While it is straight forward to perform, it is not always obvious. This article will explain the process of how this is done in three sections.

In my travels in C programming, I periodically need to run another process and redirect its standard output back to the first process. While it is straight forward to perform, it is not always obvious. This article will explain the process of how this is done in three sections.

  • High Level Overview
  • Explanation of each line
  • Code Sample

High Level Overview

  • Create a three pipe(2)s for standard input, output and error
  • fork(2) the process
  • The child process runs dup2(2) to over the pipes to redirect the new processes’s standard
  • input, output and error to the pipe.
  • The parent process reads from the pipe(2) descriptors as needed.

Explanation

A pipe(2) is a Unix system call API that creates two file descriptors. Data written to one end of the pipe can be read by the other. It provides simple FIFO functionality without the need to maintain an associated data structure. The process should initially create three pipe(2) file descriptor pairs for standard input, output and error. For our purposes, it will be used to bridge communication between the parent and second process.

Next, our program will run a standard Unix fork(2), which creates a copy of the running processes, the stack and machine code, except with a different process ID. The return value for the parent is the process ID (pid) of the child, while the child returns 0.

dup2(2)‘s documentation says it “duplicates” a file descriptor, but I found this to be a misleading misnomer. In layman’s terms, dup2(2) cause any reads or writes to the newfd to be redirected (pointed) to the oldfd descriptor while the original newfd is closed. For our uses, the child process will use dup2(2) to redirect its standard input, output and error to the pipe(2) descriptors.

At this point, the child process will run execl(2), which will replace the current process with a new process. This is different than spawning a new process, such as through system(3), thought the effect would be the same. Now, because of the dup(2) calls, any reads or writes to standard input, output or error will be redirected to the respective pipe(2)‘s.

On the other end, the parent process will use the other end of the pipe(2) to read or write to the child process, thus accomplishing our objective.

Example Code

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SAMPLE_STRING	"Bismillah"

int main() {
	int fdstdin[2];
	int fdstdout[2];
	int fdstderr[2];
	int pid;

	pipe(fdstdin);
	pipe(fdstdout);
	pipe(fdstderr);

	pid = fork();

	if (pid == 0) { /* Child process */
		int ret;
		/*
		 * Have the 2nd argument (oldd) point to
		 * the first argument, (newd)
		 */
		dup2(fdstdin[0], STDIN_FILENO);
		dup2(fdstdout[1], STDOUT_FILENO);
		dup2(fdstderr[1], STDERR_FILENO);

		close(fdstdin[1]);
		close(fdstdout[0]);
		close(fdstderr[0]);

		/* This simulates a simple writing to stderr */
		system("printf Hi > /dev/stderr");

		/* Simulates writing from stdin to a test file */
		system("cat > `mktemp`");

		/* Typical method is to run execl(2). Here just using printf */
		ret = execl("/usr/bin/printf", "printf", "Hello World!", NULL); 

		if (ret == -1) {
			/*
			 * execl(2) returns -1 if an error occurs. Any
			 * debugging messages to the console would be
			 * interpreted as output of the process. Therefore,
			 * we will simply exit.
			 * The parent process's read attempts will return -1
			 */
			exit(128);
		}
	}
	else { /* Parent process */
		char buf[1000];

		/* Close the other end of the pipe */
		close(fdstdin[0]);
		close(fdstdout[1]);
		close(fdstderr[1]);

		/* Read from the stderr */
		read(fdstderr[0], buf, 1000);
		printf("Stderr message from child, simulated by a "
		    "system(): %s\n", buf);

		/* Sending data to the stdin of the child process */
		printf("Sending string '%s' to stdin, written to mktemp file.\n"
		    SAMPLE_STRING);

		write(fdstdin[1], SAMPLE_STRING, strlen(SAMPLE_STRING));
		/* Closing the stdin pipe */
		close(fdstdin[1]);

		/* Read from the stdout */
		read(fdstdout[0], buf, 1000);
		printf("Stdout message from child, run with an execl(): %s\n",
		    buf);
	}
}

Compiling and running this code should give you the following output.

$ ./redirect
Stderr message from child, simulated by a system(): Hi
Sending string 'Bismillah' to stdin, written to mktemp file.
Stdout message from child, run with an execl(): Hello World!

I hope this helps someone going forward! Thoughts?

This work is heavily based off of Cameron Zwarich’s excellent 1998 article Pipes in Unix from C-Scene, issue #4. I have it in hard-copy from 2001 and periodically refer back to it.

Leave a Reply

Your email address will not be published. Required fields are marked *