[comp.unix.questions] Running stdin/out through a pipe to a child process

mark@cogent.UUCP (Mark Steven Jeghers) (01/12/87)

I need to know how I might create a child process with 2 pipes connected to
it, one with which the parent process feeds a stream to the childs stdin, 
and one from which the parent reads a stream from the childs stdout.  I 
understand how to use popen() to connect either stdin OR stdout between
processes, but I need to have BOTH.  The following picture demonstrates:

                      +-------------------+
                      |                   |
                      |  Parent Process   |
                      |                   |
                      +-------------------+
               Pipe that   |         ^ Pipe that
               feeds stdin V         | reads stdout
                      +-------------------+
                      |                   |
                      |   Child Process   |
                      |                   |
                      |   (could be a     |
                      |    filter like    |
                      |    sed or cat,    |
                      |    or a shell)    |
                      |                   |
                      +-------------------+

I imagine that I could probably use fork() for this, that is, I could
have all the pipes I wanted between the processes then.  But, after getting
the child process to exec() into the program I want there (like sed, cat, or
sh) how would I get those pipes to be hooked up to the child processes stdin
and stdout?
-- 
+----------------------------------------------------------------------------+
|     Mark Steven Jeghers         ECHOMPGULP - process has eaten it          |
| cryptography, terrorist, DES, drugs, cipher, secret, decode, NSA, CIA, NRO |
|                                                                            |
|     {ihnp4,cbosgd,lll-lcc,lll-crg}|{dual,ptsfa}!cogent!mark                |
|                                                                            |
| Cogent Software Solutions can not be held responsible for anything said    |
| by the above person since they have no control over him in the first place |
+----------------------------------------------------------------------------+

jpn@teddy.UUCP (John P. Nelson) (01/12/87)

Wanted:
>
>                      +-------------------+
>                      |                   |
>                      |  Parent Process   |
>                      |                   |
>                      +-------------------+
>               Pipe that   |         ^ Pipe that
>               feeds stdin V         | reads stdout
>                      +-------------------+
>                      |                   |
>                      |   Child Process   |
>                      |                   |
>                      |   (could be a     |
>                      |    filter like    |
>                      |    sed or cat,    |
>                      |    or a shell)    |
>                      |                   |
>                      +-------------------+
>

Here is a code fragment that does what you need.  Don't worry about the
fact that I am using low-level UNIX descriptors 0,1,2 instead of stdin,
stdout, stderr - your child task will not know the difference.

Note, however, that while this sets stdin and stdout descriptors to be
pipes, that the stdio package buffers pipes as if they were files, not
like terminals.  This means that the filter program must fflush it's
output periodically (or setbuf appropriately), or deadlock can occur
(both programs expecting input from other program).  Unfortunately, the
stdio buffering state does not inherit across exec(), so it can't be
preset.

Bottom line:  This technique is only useful with "well-behaved" child
processes - it does not work in the general case.

If your version of unix has psuedo-terminals, you will be MUCH better
off putting a psuedo terminal in the middle, rather than a pair of pipes.

------------------------------------------------------------------------
    int infds[0];	/* in relative to PARENT task */
    int outfds[0];	/* out relative to PARENT task */
#ifdef OPTIONAL
    FILE *input, *output;
#endif
 ...

    pipe(infds);	/* create one pipe */
    pipe(outfds);	/* create the other pipe */
    if (fork() == 0)
	{
	/* child task */
	/* close old stdin, stdout, stderr */
	close(0); close(1); close(2);
	/* use dup to set stdin, stdout, stderr */
	dup(outfds[0]);		/* outfds[0] -> stdin */
	dup(infds[1]);		/* infds[1] -> stdout */
	dup(infds[1]);		/* infds[1] -> stderr */
	/* close unused pipe ends */
	close(outfds[0]); close(outfds[1]); close(infds[0]); close(infds[0]);
	exec......
	exit(1);		/* exec fails */
	}
    else
	{
	/* parent */
	/* close unused descriptors */
	close(outfds[0]); close(infds[1]);
	/* now use read on infds[0], and write on outfds[1] */
#ifdef OPTIONAL
	/* create stdio type descriptors for parent to fread/fwrite */
	input = fdopen(infds[0], "r");
	output = fdopen(outfds[1], "w");
	/* of course, similar close of 0,1,2 followed by dups as above */
	/* in child task could make descriptors available as stdin, stdout */
#endif
	}

charlie@condor.UUCP (Charlie Havener) (01/13/87)

In article <136@cogent.UUCP> mark@cogent.UUCP (Mark Steven Jeghers) writes:
>I need to know how I might create a child process with 2 pipes connected to
>it, one with which the parent process feeds a stream to the childs stdin, 
>and one from which the parent reads a stream from the childs stdout.  I 
>understand how to use popen() to connect either stdin OR stdout between
>processes, but I need to have BOTH.

Here is the source code of a program that will do what you request:
--------------------------------------------------------------------

/* Sample pipe master diagnostic control program - cdh April 29 1985 */
/* this program shows how to spawn other programs, pass them command line */
/* arguments, and attach to their standard input and output so all */
/* I/O goes thru the parent program. This program works on both  */
/* the Berkeley 4.2BSD and on the 68000 Unisoft Sys V UNIX        */
/* this is a framework to be modified as you wish. It has more comment */
/* messages than you will want to keep in a finished product */
/* This is all the result of one day's experimenting. 	*/

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

char *pgms[] =	/* here is an array of programs this one will try to invoke */
    {
    "test2",
    "test1",
    0,
    };

char *argsv[]=	/* here are some sample command line arguments which */
    {		/* will be passed to the programs that are spawned */
    "vi",
    "fi",
    "fo",
    "fum",
    0,
    };

/* we will use two pipes for two way communication */

int fd1[2];	/* pipe channel from child to parent */
int fd2[2];	/* pipe channel from parent to child */

/*--------------------------------------------------------*/
main(argc,argv,envp)
    int argc;
    char **argv, **envp;
    {
    int i,k;
    char *pgmptr;	/* pointer to a test program name */
    int pid;
    int suicide_note;	/* where the child exit status goes */
    int death_note;	/* where the child exit status goes */
    int ch;
    char chr;
    char c;
    int status;
    int pid_listen;
    int pid_talker;
    int pid_program;
    int fda,fdb,fdc;

    for ( k=0 ; k < sizeof(pgms) ; k++ )
	{
	pgmptr = pgms[k];	/* get the name of a program to run */
	if ( pgmptr == 0 )
	    {
	    printf("No more test programs to execute\n");
	    exit(0);
	    }
	if ( (access(pgmptr,01) != 0) )	/* see if it is there before making pipes */
	    {
	    printf("test program %s not found\n",pgmptr);
	    continue;
	    }
	printf("now trying to get pipes for %s\n",pgms[k]);
	if ( pipe(fd1) < 0 )
	    {
	    perror("pipemaster");
	    printf("Couldn't get pipe file descriptors\n");
	    exit();
	    }
	if ( pipe(fd2) < 0 )
	    {
	    perror("pipemaster");
	    printf("Couldn't get pipe file descriptors\n");
	    exit();
	}
	printf("Got two pipes successfully\n");
	printf("In pipemaster diagnostic control program, fork() is next\n");
	/* don't use vfork() for this tricky stuff. No vfork on 68k anyway */
	if (  ( pid_program = fork()) < 0 ) /* fork failed */
	    {
	    printf("Couldn't fork");
	    exit();
	    }
	if ( pid_program == 0 )	/* the diagnostic program i.e. the child */
	    {
	    close(0);
	    close(1);
	    close(2);	/* close original stdin, stdout, stderr */
	    fda = dup(fd2[0]);	/* dup into childs stdin */
	    if ( fda == -1 )
		printf("dup returned error\n");
	    fdb = dup(fd1[1]);	/* dup into childs stdout */
	    if ( fdb == -1 )
		printf("dup returned error\n");
	    fdc = dup(fd1[1]);	/* dup into childs stderr */
	    if ( fdc == -1 )
		printf("dup returned error\n");
	    printf("fda=%d  fdb=%d  fdc=%d\n",fda,fdb,fdc);
	    /* close all extra descriptors */
	    close(fd1[0]);
	    close(fd1[1]);
	    close(fd2[0]);
	    close(fd2[1]);
	    execve(pgmptr,argsv,envp);   /* never returns if it works */
	    exit(0200);	/* report to parent that the exec failed */
	    }
	else		/* the parent, set up talker and listener processes */
	    {
	    close(fd2[0]);
	    close(fd1[1]);	/* close unused pipe ends for safety */
	    printf("Will now set up talker and listener processes\n");
	    if (  ( pid_talker = fork()) < 0 ) /* fork failed */
		{
		printf("Couldn't fork the 2nd time");
		exit();
		}
	    if ( pid_talker != 0 ) /* the parent, listen to pipe and echo */
		{
		while (  (status = read(fd1[0],&c,1) ) > 0  )
		    {
		    putchar(c);
		    }
		printf("sending kill signal to pid_talker process\n");
		kill(pid_talker,SIGKILL); /* kill the child */
		pid = wait(&death_note);  /* so no zombies left around */
		printf("pid %d, the wait status from death_note was  %o\n",pid,death_note );
		printf("wait for death_note completed, death_note = %d\n",death_note);
		}
	    else    /* the child talker, reads stdin and writes down the pipe */
		{
		while ( (ch = getchar()) != EOF )
		    {
		    chr = ch;	/* the 68000 byte ordering requires */
				    /* addr of char not int!!	*/
		    if ( write(fd2[1],&chr,1) != 1 )
			printf("Couldn't write to pipe\n");
		    }
		close(fd2[1]);
		printf("willingly leaving the talker subprocess\n");
		exit(0);
		}
	    }
	printf("in pipemaster main program, waiting for child to finish\n");
	pid = wait(&suicide_note);	/* wait for child to finish */
	printf("pid %d, the wait status was  %o\n",pid,suicide_note );
	if ( (suicide_note >> 8) == 0200 )
	    printf("the exec failed\n");
	close(fd2[1]);
	close(fd1[0]);  /* clean up */
	printf("clean up complete, end of for loop\n");
	}
    printf("exiting pipemaster main program\n");
    }

----------------------------------------------------------------------
here are some sample programs for it to invoke
----------------------------------------------------------------

#include <stdio.h>

/* test1 - a sample program too be invoked by the expert system */
/*      Global Data  , Defined here and used in other modules */
char *version = "V1.00"; 	/* string built at initialize time */
int debuglevel = 0;   /* used to turn debug printouts on and off */
int expert = 0;		/* set to 1 if program is to run in expert mode */
int parameter = 0;
int subtest = 0;

/*-------------------------------------------------------------------*/

main(argc,argv)
    int argc;
    char *argv[];
    {
    int c;
    char buf[80];
    char *u;
    int status;

    setbuf(stdout,NULL);
    printf("argc = %d, argv[0] = %s, argv[1] = %s\n",argc,argv[0],argv[1]);
    while ( --argc > 0 && (*++argv)[0] == '-')
        for ( u = argv[0] + 1 ; *u != '\0' ; u++ ) switch(*u)
            {
            case 'd':   /* turns on debug */
                debuglevel++;	/* the more d's the higher the debug level */
                break;
	    case 'v':
		printf("\nVersion %s\n",version);
		exit(1);
		break;
	    case 's':
		sscanf(*++argv,"%d",&subtest);
		if ( debuglevel )
		    printf("subtest = %d\n",subtest);
		break;
	    case 'x':
		expert = 1;
		break;
	    case 'p':
		sscanf(*++argv,"%d",&parameter);
		if ( debuglevel )
		    printf("parameter = %d\n",parameter);
		break;
	    case 'f':
		/* replace description with your programs function */
		printf("This test verifies that the ibus is functional by\n");
		printf("reading and writing the page register on both T bus\n");
		printf("driver boards if both are present\n");
		exit(0);
            default:
                printf("\nIllegal option -%c\n", *u);
            case 'h':   /* help */
                printf("usage: -v -h -d -x -s # -p # -f\n");
		printf(" -d The more d's the higher the debug e.g. -ddddd \n");
		printf(" -v reports the version number of the program\n");
		printf(" -x invokes program in 'expert' mode\n");
		printf(" -h help, provides this flag option list\n");
		printf(" -s # where # is a number means do sub-test #\n");
		printf(" -p # is a parameter number for use by the program\n");
		printf(" -f print a brief functional description of test\n");
                exit(1);
            }
    printf("I am test1\n");
    while ( argc-- )
	{
	printf("arg%d = %s\n",argc,argv[argc]);
	}
    c = 'a'; 
    printf("enter text: ");
/* fflush(stdout); */
    gets(buf);
/*    while ( (status = read(0,&c,1)) > 0 )
	{
	printf("%c",c); 
	fflush(stdout);
	} */
    printf("\nI am leaving test1 now\n");
    }

--------------------------------------------------------------------------

#include <stdio.h>

main(argc,argv)
    int argc;
    char *argv[];
    {
    setbuf(stdout,NULL);    /* note that you should do this when using pipes */
    printf("I am test2\n");
    printf("argc = %d, argv[0] = %s, argv[1] = %s\n",argc,argv[0],argv[1]);
    while ( argc-- )
	{
	printf("arg%d = %s\n",argc,argv[argc]);
	}
    printf("I am leaving test2 now\n");
    }

mcvoy@rsch.WISC.EDU (Lawrence W. McVoy) (01/13/87)

In article <136@cogent.UUCP> mark@cogent.UUCP (Mark Steven Jeghers) writes:
>I need to know how I might create a child process with 2 pipes connected to
>it, one with which the parent process feeds a stream to the childs stdin, 
>and one from which the parent reads a stream from the childs stdout.  I 
>understand how to use popen() to connect either stdin OR stdout between
>processes, but I need to have BOTH.  The following picture demonstrates:
>
>                      +-------------------+
>                      |  Parent Process   |
>                      +-------------------+
>               Pipe that   |         ^ Pipe that
>               feeds stdin V         | reads stdout
>                      +-------------------+
>                      |   Child Process   |
>                      +-------------------+

# include	<stdio.h>
# define	R		0
# define	W		1

main() {
    int	in[2];
    int out[2];

    pipe(in);
    pipe(out);

    if (fork()) {
	/* OK, I'm the parent.  I want to to close the 
	 * read side of in and the write side of out (for tidiness).
	 */
	close(in[R]);
	close(out[W]);
    }
    else {
	/* OK, I'm the child.  I want to set up those pipes to feed 
	 * my stdin & stdout.  The general idea is to close the existing
	 * stdin/out and replace them with pipes.  Also, close the non-used
	 * sides of the pipes.
	 */
	close(in[W]);
	close(out[R]);
# if    BSD && HAVE_DUP2
	dup2(0, in[R]);
	dup2(1, out[W]);
# else
	/* This works because Unix always returns the LOWEST numbered 
	 * file descriptor available. ORDER is important.
	 */
	close(0);
	dup(in[R]);
	close(1);
	dup(out[W]);
# endif
    }
    /* OK, all set: exec or whatever */
}

/* It sounds like you would like to read an advanced unix programming
 * book.  Try this one:
 *
 * Advanced Unix Programming
 * Marc Rochkind
 * Prentice-Hall
 * ISBN 0-13-011800-1
 */
-- 
Larry McVoy 	        mcvoy@rsch.wisc.edu, 
      		        {seismo, topaz, harvard, ihnp4, etc}!uwvax!mcvoy

"They're coming soon!  Quad-stated guru-gates!"

m5d@bobkat.UUCP (Mike McNally ) (01/16/87)

In article <3112@rsch.WISC.EDU> mcvoy@rsch.WISC.EDU (Lawrence W. McVoy) writes:
>In article <136@cogent.UUCP> mark@cogent.UUCP (Mark Steven Jeghers) writes:
>>I need to know how I might create a child process with 2 pipes connected to
>>it, ......
>
># include	<stdio.h>
>       .
>       .
>       .
># if    BSD && HAVE_DUP2
>	dup2(0, in[R]);
>	dup2(1, out[W]);
># else
>       .
>       .
>       .
>Larry McVoy 	        mcvoy@rsch.wisc.edu, 
>      		        {seismo, topaz, harvard, ihnp4, etc}!uwvax!mcvoy
>
>"They're coming soon!  Quad-stated guru-gates!"

I usually don't respond to this kind of note; not that the question is
silly, it's just that I feel sure that fifty garzillion other people
will respond anyway.  But I'm bored.

Larry's solution is OK, but I would fix two things.  First, the
arguments to the dup2() calls are backwards:

	dup2(in[R], 0);   /* Duplicate input pipe to standard input */
	dup2(out[W], 1);  /* Duplicate output pipe to standard output */

We want the results to be fd's 0 & 1, so these must be the "newd" arguments.
Also, I would close the pipes afterward; no point keeping them open.

It is important for the parent to close the unused ends of the pipes for
a reason other than tidiness.  If the child dies, the parent may want
to know.  The system (BSD, at least) will send a SIGPIPE to the parent
if the child dies but the parent is still writing to the pipe AND the
child's copy of the descriptor is the only reader.  If the parent's copy
of the read end is still open, the system will not deliver SIGPIPE.

Remember also that the pipes are file descriptors suitable for use with
read() and write().  If you want stream I/O, you'll have to use fdopen()
or something.  This is not important for the child; the stream package 
will start things up after the exec().  

--
****                                                         ****
**** At Digital Lynx, we're almost in Garland, but not quite ****
****                                                         ****

Mike McNally                                    Digital Lynx Inc.
Software (not hardware) Person                  Dallas  TX  75243
uucp: {texsun,killer,infotel}!pollux!bobkat!m5  (214) 238-7474

chris@mimsy.UUCP (01/18/87)

In article <423@bobkat.UUCP> m5d@bobkat.UUCP (Mike McNally) warns:
>It is important for the parent to close the unused ends of the pipes for
>a reason other than tidiness.  If the child dies, the parent may want
>to know.  The system (BSD, at least) will send a SIGPIPE to the parent
>if the child dies but the parent is still writing to the pipe AND the
>child's copy of the descriptor is the only reader.  If the parent's copy
>of the read end is still open, the system will not deliver SIGPIPE.

It is yet more important than that.  As long as the parent still
has open its chain to the read end of the pipe, the kernel will
(having duly noted that there is *some* process out there that can
read the pipe) eventually block the parent in a write() system call
on the pipe until someone reads some of the data from that pipe.
If the child dies or otherwise lets go of its own chain to the read
end, only the (blocked) writer will be able to read enough data to
unclog the pipe---but that writer can do nothing until someone
unclogs the pipe!

There *are* situations in which everything will work (the parent
may write just a little bit, not blocking, then exit), but this
is a bug just waiting to bite.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
UUCP:	seismo!mimsy!chris	ARPA/CSNet:	chris@mimsy.umd.edu

dce@mips.UUCP (David Elliott) (01/19/87)

With all of this discussion about pipes, I thought I'd bring up the
subject of a (4.[23]BSD) bug involving closing two pipes in succession:

1. A pipe is opened, written to, closed, and the child process is waited on.
After termination, this is done again. All goes as expected.

2. Two pipes are opened and written to. Next, both pipes are closed. Finally,
each child process is waited for. All goes as expected, except that I
find it odd that the second child is found by wait() first. This really
isn't the problem anyway.

3. Two pipes are opened and written two. Afterwards, each pipe is closed and
the child process waited for (that is, pipe 1 is closed and child 1 is
waited for and then pipe 2 is closed and child 2 is waited for). In this
case, the first wait() blocks, and the first child never terminates except
when the parent does.

I have reduced these 3 cases as much as possible by inlining the code for
popen() and pclose() and getting rid of the stdio code used by them (stdio
exists only to print error messages in the programs). The result is a set
of programs that directly call the system calls involved.

If anyone would like to work on this problem, I can send out copies of
these programs (the whole deal is about 8K of source, but I didn't want
to clutter up the net).

-- 
			David Elliott

UUCP: 	{decvax,ucbvax,ihnp4}!decwrl!mips!dce, DDD:  	408-720-1700

chris@mimsy.UUCP (Chris Torek) (01/22/87)

In article <145@quacky.mips.UUCP> dce@mips.UUCP (David Elliott) writes:
>2. Two pipes are opened and written to. Next, both pipes are closed. Finally,
>each child process is waited for. All goes as expected, except that I
>find it odd that the second child is found by wait() first.

Child collection order is not guaranteed anywhere, so do not count
on it working either way.  Yet this is a clue, to those who have keen
sight:

>3. Two pipes are opened and written two [sic]. Afterwards, each pipe
>is closed and the child process waited for (that is, pipe 1 is closed
>and child 1 is waited for and then pipe 2 is closed and child 2 is
>waited for). In this case, the first wait() blocks, and the first
>child never terminates except when the parent does.

An instant analysis:

Child 2 is still able to write to child 1, having in its posession
a descriptor for the write end of the pipe to child 1.  Child 1
will not exit until child 2 exits or otherwise discards this
descriptor.  This should be easy to confirm, by code inspection,
or by adding `gratuitous' close() calls.  This is yet another
example of why it is necessary to use care as to who has descriptors
to which ends of what pipes.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
UUCP:	seismo!mimsy!chris	ARPA/CSNet:	chris@mimsy.umd.edu