[comp.unix.programmer] More on how to do a pipe

emmonsl@athena.ecs.csus.edu (L. Scott Emmons) (03/06/91)

After some thinking about my pipe(), fork(), exec() implementation in my
last post, I knew there was something funky about the way I did it...

Here is another version of my pipe program, about as efficient as my
previous version, but a bit cleaner (unless use of SIGCHLD is used for
something other than just closing the pipe() down.)

Here it is, hope you find it useful:

---CUT HERE begin pipe2.c---
/*
	This program shows how to (or one way to) implement fork() and exec()
	on a pipeline, redirecting the stdout of the exec()d program into
	the pipeline.

	This code was written by L. Scott Emmons, emmons@csus.csus.edu.
*/

#include <stdio.h>
#include <signal.h>

int	fd[2];

main()
{
	char	ch=0;

	pipe(fd);

	if (fork()) {
		close(fd[1]);
		while(read(fd[0],&ch,1))
			putchar(ch);
		close(fd[0]);
	} else {
		dup2(fd[1],1);
		execl("/usr/ucb/last","last","emmonsl",(char *)0);
	}
}
---CUT HERE end pipe2.c---

			L. Scott Emmons
			---------------
	emmons@csus.csus.edu  <or>  ...[ucbvax]!ucdavis!csus!emmons
		Packet: kc6nfp@kg6xx.#nocal.ca.usa.na

brad@SSD.CSD.HARRIS.COM (Brad Appleton) (03/06/91)

In article <1991Mar6.025034.8697@csusac.csus.edu> emmonsl@athena.ecs.csus.edu (L. Scott Emmons) writes:
>
>After some thinking about my pipe(), fork(), exec() implementation in my
>last post, I knew there was something funky about the way I did it...
>
>Here is another version of my pipe program, about as efficient as my
>previous version, but a bit cleaner (unless use of SIGCHLD is used for
>something other than just closing the pipe() down.)

I hate to be critical but I didnt care for the way the status codes from
dup2 and pipe were ignored. I have a piece of code from a while back that
was created specifically for the purpose of illustrating how to use fork and
how to redirect parent/child i/o. Its a bit longer than yours but is cleaner
(IMHO) and is easy to read. (BTW let me know ASAP of any errors - I thought
they were all worked out be now but who knows):

------------------------------------- cut here --------------------------------
/* I/O Redirection example */
#include <stdio.h>

#define  BUF_LEN  256
#define  NULLSTR  (char *)NULL

  /* read & write ends of a pipe */
#define  READ_END  0
#define  WRITE_END 1

  /*
  ** macro to replace the dup2 system call (if its not present) */
  **
  **  NOTE: dup() returns the LOWEST NUMBERED available file descriptor.
  **        it is assumed below, that no file descriptors numbered lower
  **        than <from> are available before the dup2 macro is invoked!!
  */
#ifdef DONT_HAVE_DUP2_SYSTEM_CALL
# define  dup2(to,from)  ( (close(from) || (to = dup()) < 0) ? -1 : 0 )
#endif

main( argc, argv )
  int  argc;
  char *argv[];
{
  int  nbytes,         /* number of bytes read */
       status,         /* return-code/status for various system calls */
       pipe_fd[2];     /* pipe file-descriptor array */ 
  char buf[ BUF_LEN ]; /* character buffer*/

  /* open a pipe */
  if ( pipe(pipe_fd) < 0 ) {
    perror( "unable to open unnamed pipe\n" );
    exit( 1 );
  }
  
  switch ( fork() ) {
    case -1: /* failure */
      perror( "Unable to fork off a process" );
      break;

    case  0: /* child */
      /* redirect stdout to the write end of the pipe */

      if ( dup2( pipe_fd[ WRITE_END ], fileno(stdout) ) < 0 ) {
        perror( "unable to redirect STDOUT to write end of pipe" );
        exit( 1 );
      }

      fprintf( stderr, "Child: Going to exec uptime (pid=%x).\n", getpid() );
      fflush( stderr );
      execl( "/usr/ucb/uptime", "uptime", NULLSTR );
      break;

    default: /* parent */
      do {  /* read output from child (until see '\n' ) */
        printf( "\n\nParent: reading from pipe\n\n" );
        fflush( stdout );

        if ( (nbytes=read( pipe_fd[ READ_END ], buf, BUF_LEN )) <0 ) {
          perror( "failed on pipe read" );
        }

        if ( write( fileno(stdout), buf, nbytes ) != nbytes ) {
          perror( "failed on STDOUT write" );
        }
      } while ( buf[ nbytes-1 ] != '\n' );

      printf( "Parent: Waiting for child (pid=%x).\n", getpid() );
      fflush( stdout );

      wait( &status );
      printf( "Parent: Child died (status=%x).\n", status );
      fflush( stdout );
      close( pipe_fd[ READ_END ] );
      break;
  }

  exit( 0 );
}


______________________ "And miles to go before I sleep." ______________________
 Brad Appleton           brad@ssd.csd.harris.com       Harris Computer Systems
                             uunet!hcx1!brad           Fort Lauderdale, FL USA
~~~~~~~~~~~~~~~~~~~~ Disclaimer: I said it, not my company! ~~~~~~~~~~~~~~~~~~~

emmonsl@athena.ecs.csus.edu (L. Scott Emmons) (03/08/91)

In article <2506@travis.csd.harris.com> brad@SSD.CSD.HARRIS.COM (Brad Appleton) writes:
>I hate to be critical but I didnt care for the way the status codes from
>dup2 and pipe were ignored.

Actually, I left all that stuff out for the sake of making the example as
short as possible, not because it should be ignored in actual implementation.
Thanks for your response, though, and yes, of course all of the return codes
should be used for an actual implementation...

			L. Scott Emmons
			---------------
	emmons@csus.csus.edu  <or>  ...[ucbvax]!ucdavis!csus!emmons
		Packet: kc6nfp@kg6xx.#nocal.ca.usa.na

maart@nat.vu.nl (Maarten Litmaath) (03/08/91)

In article <2506@travis.csd.harris.com>,
	brad@SSD.CSD.HARRIS.COM (Brad Appleton) writes:
>[...]
>I hate to be critical but I didnt care for the way the status codes from
>dup2 and pipe were ignored.

Really?  Amazing...

>I have a piece of code from a while back that
>was created specifically for the purpose of illustrating how to use fork and
>how to redirect parent/child i/o. Its a bit longer than yours but is cleaner
>(IMHO) and is easy to read. (BTW let me know ASAP of any errors - I thought
>they were all worked out be now but who knows):

OK, I've patched up your program a little, especially your dup2()
alternative, which was completely wrong in various ways; you wrote:

># define  dup2(to,from)  ( (close(from) || (to = dup()) < 0) ? -1 : 0 )

	1) the call is dup2(from, to);

	2) dup() is supposed to have an argument (!);

	3) upon success dup2() returns the new descriptor,
	   instead of always 0;

	4) the return value of close() is unimportant (its argument
	   must be checked for validity, though);

	5) if its arguments are equal, dup2() is not to perform the
	   close() at all.

Right, compare the new version with the original code!

--------------------cut here for the patched version--------------------
/* I/O Redirection example */
#include <stdio.h>

#define  BUF_LEN  256
#define  NULLSTR  (char *)NULL

  /* read & write ends of a pipe */
#define  READ_END  0
#define  WRITE_END 1

  /*
  ** function to replace the dup2 system call (if it's not present)
  */
#ifdef DONT_HAVE_DUP2_SYSTEM_CALL
#include	<sys/param.h>
#include	<errno.h>

#ifndef	NOFILE
#define		NOFILE		20
#endif	/* !NOFILE */

int	dup2(old, new)
int	old, new;
{
	int	fd;
	void	_dup2();

	if ((unsigned) new >= NOFILE) {
		errno = EBADF;
		return -1;
	}

	if ((fd = dup(old)) < 0) {
		if (errno == EBADF)
			return -1;
	} else if (fd == new)
		return new;

	if (old != new) {
		close(new);
		_dup2(old, new);
	}

	if (fd >= 0)
		close(fd);

	return new;
}


static	void	_dup2(old, new)
int	old, new;
{
	int	fd;

	if ((fd = dup(old)) != new) {
		_dup2(old, new);
		close(fd);
	}
}
#endif

main( argc, argv )
  int  argc;
  char *argv[];
{
  int  nbytes,         /* number of bytes read */
       status,         /* return-code/status for various system calls */
       pid,            /* process id of child */
       pipe_fd[2];     /* pipe file-descriptor array */ 
  char buf[ BUF_LEN ]; /* character buffer*/

  /* open a pipe */
  if ( pipe(pipe_fd) < 0 ) {
    perror( "unable to open unnamed pipe\n" );
    exit( 1 );
  }
  
  switch ( pid = fork() ) {
    case -1: /* failure */
      perror( "Unable to fork off a process" );
      exit( 1 );
      break;

    case  0: /* child */
      /* redirect stdout to the write end of the pipe */

      if ( dup2( pipe_fd[ WRITE_END ], fileno(stdout) ) < 0 ) {
        perror( "unable to redirect STDOUT to write end of pipe" );
        exit( 1 );
      }

      /*
      **  The following close() is not necessary in this particular case,
      **  but it definitely is in general!  Else the reader of the pipe
      **  may hang later (think about it!).
      */
      close( pipe_fd[ WRITE_END ] );

      /*
      **  Likewise for the read end of the pipe: as long as there is
      **  a potential reader, a writer will not receive a SIGPIPE.
      */
      close( pipe_fd[ READ_END ] );

      fprintf( stderr, "Child: Going to exec uptime (pid=%d).\n", getpid() );
      fflush( stderr );
      execl( "/usr/ucb/uptime", "uptime", NULLSTR );
      break;

    default: /* parent */

      /*
      **  The following close() is not necessary in this particular case,
      **  but it definitely is in general!  Else the reader of the pipe
      **  may hang later (think about it!).
      */
      close( pipe_fd[ WRITE_END ] );

      do {  /* read output from child (until see '\n' ) */
        printf( "\n\nParent: reading from pipe\n\n" );
        fflush( stdout );

        if ( (nbytes=read( pipe_fd[ READ_END ], buf, BUF_LEN )) <0 ) {
          perror( "failed on pipe read" );
	  exit( 1 );
        }

        if ( write( fileno(stdout), buf, nbytes ) != nbytes ) {
          perror( "failed on STDOUT write" );
	  exit( 1 );
        }
      } while ( buf[ nbytes-1 ] != '\n' );

      printf( "Parent: Waiting for child (childpid=%d).\n", pid );
      fflush( stdout );

      wait( &status );
      printf( "Parent: Child died (status=0x%x).\n", status );
      fflush( stdout );
      close( pipe_fd[ READ_END ] );
      break;
  }

  exit( 0 );
}