Question

I have code where I want to encapsulate a particular piece of code into a forked child so that if the code causes issues, it will not affect the parent and the parent will be able to report.

I am not execing after the fork call. Just plain fork and then the code runs that would have normally run in the parent.

The code in the child outputs stuff to stdout and stderr. Due to issues with interleaving output and stdio buffering, I was now looking into TLPI for a better solution.

The idea I came up with was roughly:

  • pipe() for each stream
  • fork()
  • in child:
    • close() read end of pipes
    • dup2() the write ends onto the file descriptors for stdout and stderr respectively
    • setbuf(fd, NULL) to turn off stdio stream buffering inside the child.
  • in parent:
    • close() write ends of pipes
    • use select/pselect/epoll etc (has to run across Linux, BSDs, AIX, Solaris etc) to watch the read ends of the pipes for new data and when it arrives write() it straight to the respective file descriptors in the parent process.

Now, I presume there is one step missing between the dup2 and the setbuf inside the child. What is it?

setbuf takes a FILE* whereas of course dup2 acts on int.

freopen came to mind, but it requires a path. If I merely want to assign a new fileno to the streams, how can I do that?

Was it helpful?

Solution

The extern variables stdin, stdout and stderr are FILE * pointers. You can pass these to setbuf / setvbuf. However, note that the child has a separate address space so whilst it will inherit the state of the buffers at the time of the fork(), both can continue to use the buffers safely, and if you fflush STDOUT and STDERR, the output buffers will be empty anyway.

This is the way I would do it in the child (untested, and please do add some error handling):

void
child ( ... )
{
  const char *devnull = "/dev/null";

  /* turn off buffering */

  setvbuf (stdin, NULL, _IONBF);
  setvbuf (stdout, NULL, _IONBF);
  setvbuf (stderr, NULL, _IONBF);

  for (i = getdtablesize () - 1; i >= 0; i--)
    {
      if ((i != write_end_of_pipe1) && (i != write_end_of_pipe2))
        close (i);
    }

  i = open (devnull, O_RDWR);
  if (i == -1)
    {
      fprintf (stderr, "Unable to open /dev/null\n");
      return;
    }

  i = open (devnull, O_RDONLY);
  if (i != 0)
    {
      dup2 (i, 0);
      close (i);
    }

  dup2 (write_end_of_pipe1, 1);
  close (write_end_of_pipe1);

  dup2 (write_end_of_pipe2, 2);
  close (write_end_of_pipe2);

  return;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top