0

I need to fork a process, redirect output (stdout and stderr) in buffer. My code seems to work with most of binary but not all. For example I can run my code with a very long "ls" like ls -R /proc/ and it is working perfectly. When I run mke2fs process, my code does not work anymore.

If I run mke2fs in a fork and wait for it, it is working perfectly. Now if I add redirect stuff, my programs never finish to run.

I wrote a little main to test this specific trouble :

#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main ()
{
  pid_t pid;
  int status = -42;
  int pipefd_out[2];
  int pipefd_err[2];
  char buf_stderr[1024];
  char buf_stdout[1024];
  int count;
  int ret;

  pipe(pipefd_out);
  pipe(pipefd_err);

  memset (buf_stdout, 0, 1024);
  memset (buf_stderr, 0, 1024);

  pid = fork ();

  if (pid == -1)
  {
    fprintf (stderr, "Error when forking process : /usr/sbin/mke2fs\n");
    return 1;
  }

  if (pid == 0)
  {
    close(pipefd_out[0]);
    close(pipefd_err[0]);

    dup2(pipefd_out[1], 1);
    dup2(pipefd_err[1], 2);

    close(pipefd_out[1]);
    close(pipefd_err[1]);

    char **args;

    args = malloc (sizeof (1024));
    args[0] = strdup("/usr/sbin/mke2fs");
    args[1] = strdup("/dev/sda4");
    args[2] = strdup("-t");
    args[3] = strdup("ext4");
    args[4] = NULL;

    execvp ("/usr/sbin/mke2fs", args);

    /*
    args = malloc (sizeof (1024));
    args[0] = strdup("/bin/ls");
    args[1] = strdup("-R");
    args[2] = strdup("/proc/irq");
    args[3] = NULL;

    execvp ("/bin/ls", args);
    */
    perror ("execv");
    fprintf (stderr, "Error when execvp process /usr/sbin/mke2fs\n");
    return 1;
  }
  close(pipefd_out[1]);
  close(pipefd_err[1]);

  if (waitpid(pid, &status, 0) == -1)
  {
    fprintf (stderr, "Error when waiting pid : %d\n", pid);
    return 1;
  }

  do
  {
    count = read(pipefd_out[0], buf_stdout, sizeof(buf_stdout));
  }
  while (count != 0);
  do
  {
    count = read(pipefd_err[0], buf_stderr, sizeof(buf_stderr));
  }
  while (count != 0);

  ret = WEXITSTATUS(status);

  FILE* file = NULL;
  file = fopen("/root/TUTU", "w");

  if (file != NULL)
  {
    fwrite(buf_stdout, 1, sizeof(buf_stdout), file);
    fwrite(buf_stderr, 1, sizeof(buf_stdout), file);
    fclose(file);
  }

  return 0;
}

If I run ps, I could see my child process running :

# ps | grep sda4
  936 root      2696 S    {mke2fs}  /dev/sda4 -t ext4

I am not able to understand why I got this strange behavior. Not sure if its related, but output of mke2fs is not classic. Instead of print output and move forward the prompt, the process seems to update the output during the computing. It is a kind of progress bar. Not sure if my explanation is really clear.

Thanks, Eva.

ArthurLambert
  • 749
  • 1
  • 7
  • 30

1 Answers1

2

You can't wait for the program to finish (what you do with waitpid) before reading its stdout/stderr from the pipe. When the program writes to the pipe and its full it will sleep until you read from the pipe to make space in it. So the program waits until there's more space in the pipe before it can continue and exit, while you're waiting for the program to exit before you read from the pipe to make space in it.

The simplest solution in this case would be to just move waitpid until after you're done reading from the pipes. It should be fine since the program you execute will close the pipes when exiting.

Art
  • 19,807
  • 1
  • 34
  • 60
  • 1
    There will still be a potential deadlock since the parent tries to read all of the child's stdout, then all of its stderr. If the stderr pipe fills up before the program finishes, it will be blocked writing to stderr while the parent is still trying to read its stdout. "Capture both stdout and stderr" is not a trivial job. –  Dec 17 '13 at 16:06
  • @Wumpus Q. Wumbley : Exactly, I got this issue because I read stdout then stderr. I guess that I could perhaps try to use pthread to read them in the same time. – ArthurLambert Dec 18 '13 at 12:52