[comp.sys.sgi] Piping summary

sweetmr@SCT60A.SUNYCT.EDU (michael sweet) (05/14/91)

Thanks to all who gave me example code.  As it turns out, the biggest problem
was that I was not flushing the output to the pipes (thanks, Cameron!)  As he
points out, pipes in UNIX do not behave as ttys do, and a newline will not 
force a flush of the internal buffers (this is different than in OS-9, the 
OS I use most of the time...)  At first I didn't think he was right, as turning
buffering off with setbuf() hadn't worked before.  Then I remembered that I
had only turned buffering off in the parent process (not the child....)

Ok, to summarize:

 1) When using dual pipes, you have to: a) flush after output, or b) select()
    on the pipes in question until something comes in.
 2) Most programs don't flush the output (i.e. tar won't), so you'll probably
    have to use the select() function to jump when something comes in.

(when using select(), you'll also have to do a wait3() or signal(SIGCHLD)the
to determine when the piped-process is done...  EOF won't be available....)

The *real* code follows, and may even be good enough to use as a general 
purpose piping routine if you need it.

 -Mike

---------CUT HERE------------------------------CUT HERE-------------
/*
* Fork 'command', returning file pointers to the two pipes created.
* 'fp_array[1]' receives the standard output from 'command', while
* 'fp_array[0]' sends to the standard input of 'command'...
*/

int Pipe_Command(fp_array, command)
FILE **fp_array;
const char *command;
{
 int pipe_paths[2][2];
 int child_pid;


/*
* First, some coredump protection...
*/

 if (fp_array == NULL)
  {
   fputs("Pipe_Command(): fp_array is NULL!\n", stderr);

   return (-1);
  };

 if (command == NULL)
  {
   fputs("Pipe_Command(): command is NULL!\n", stderr);

   return (-1);
  };

/*
* Create a pair of pipes for use with the piped process.
*/

 if (pipe(pipe_paths[0]) < 0) /* get the pipe for the new process's */
  {                           /* standard input...                  */
   return (-1);
  };

 if (pipe(pipe_paths[1]) < 0) /* get the pipe for the new process's */
  {                           /* standard output...                 */
   close(pipe_paths[0][0]);   /* clean-up on error */
   close(pipe_paths[0][1]);

   return (-1);
  };

/*
* Finally, fork() and execvp() the requested program.
*
* Stdin and stdout are redirected by the child; stderr stays the
* same so that error messages go to the invokation window...
*/

 signal(SIGCHLD, SIG_IGN);   /* ignore child error status... */

 child_pid = fork();         /* fork the child */

 if (child_pid == 0)         /* the child process executes this... */
  {
   dup2(pipe_paths[0][0], 0); /* change stdin and stdout... */
   close(pipe_paths[0][0]);

   dup2(pipe_paths[1][1], 1);
   close(pipe_paths[1][1]);

   close(pipe_paths[0][1]);
   close(pipe_paths[1][0]);

   execl("/bin/sh", "sh", "-c", command, NULL);
   _exit(errno);
  };

 close(pipe_paths[0][0]);
 close(pipe_paths[1][1]);

 if (child_pid < 0)          /* error forking! */
  {
   close(pipe_paths[0][1]);
   close(pipe_paths[1][0]);

   return (-1);
  };

/*
* If we get this far, the fork() was successful and we need to create the
* FILE (stream) pointers for normal access by functions.  If an error occurs
* we close the pipes which should send a SIGPIPE signal to the child and
* kill it for us.  Just in case, we send a SIGKILL to make sure.  Since
* we have setup the SIGCHLD signal to be ignored, we don't have to do a
* wait(2).
*/

 if ((fp_array[0] = fdopen(pipe_paths[0][1], "w")) == NULL)
  {
   close(pipe_paths[1][0]);   /* clean-up */
   close(pipe_paths[0][1]);
   kill(child_pid, SIGKILL);  /* and hack the kid to death! */

   return (-1);
  };

 if ((fp_array[1] = fdopen(pipe_paths[1][0], "r")) == NULL)
  {
   fclose(fp_array[0]);       /* clean-up */
   close(pipe_paths[0][1]);
   kill(child_pid, SIGKILL);  /* and hack the kid to death! */

   return (-1);
  };


/*
* Ah, success!  Return 0 to indicate success.
*/

 return (0);
}