[comp.sys.sgi] Pipes...

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

I'm trying to fork and exec run In a project I am working on, I want to create fork and exec another process with
pipes to *both& * the standard iinpunput and output of the new process.  This will
eventually be used to control the ''tar' program via a GUI.  Anyways, my test
programs all  seem s to create the 2 sets of pipes and fork the new program fine,
but when it comes time oto actually *write* anything to the stdandard input of
the second process, the first process sits there waiting to  (presumably blocked.)
The code goes something like this:

 pipe(stdin_pupipes);
 pipe(stdout_pipes);

 child+_pid = fork();
i if (child_pid == )0)
 { [  {
   dup2(stdin_pipes,[0]9, 0);
   dup2(stdout_pipes[1], 1);
   close(stdoutin_pipses[1]);
   close(stdout_pipes[09)]);

   execlp("/bin/sh", "sh", "-c", command, NULL);
   exit ((_exit(errno);

  };;

 if (child_id < 0) /* error forking! */
  {
   close(all filespipes);
   }
  else
  {
   close(stdin_pipes[0]);
   close(stdout_pipes[1]);
   child_stdin = fredopnen(stdin_pipes[1], "ew");
   child_stdout = gfdopen(stdin_pipesout_pipes[0]9, "r");
  };


The test program calls this with another test program (which read s fromnumber s from
the standard input and outputs x, x^2, x^3, an and x&^3), and then tries to send the
numbers 1-10.  It blocks on the first number, and the other test rpprogram 
progrduces no output  wis waiting for a number from stdin.  Any nonone have any ideas?  This really has
me stumped!

 -Mike

pj@sam.wpd.sgi.com (Paul Jackson) (05/12/91)

In article <4250.on.Fri,.10.May.91.20:53:34.EDT.@sct60a.sunyct.edu>,
sweetmr@SCT60A.SUNYCT.EDU (michael sweet) writes:
|> 
|> In a project I am working on, I want to fork and exec another process with
|> pipes to *both* the standard input and output of the new process.

Only call pipe once, not twice.  The pipe(2) system call provides
a pair of file descriptors, one good for reading and one for writing.
And after duping them in the child to its stdin/stdout, close the
file descriptors that you got from pipe.

See an example, using the command "cat" instead of "tar"
on page 130, Section 6.3, of Marc J. Rochind's "Advanced
Unix Programming", Prentice Hall,1985.

-- 

				I won't rest till it's the best ...
				Software Production Engineer
				Paul Jackson (pj@wpd.sgi.com), x1373

corbettm@cutmcvax.cs.curtin.edu.au (Michael Corbett) (05/13/91)

sweetmr@SCT60A.SUNYCT.EDU (michael sweet) writes:

>In a project I am working on, I want to fork and exec another process with
>pipes to *both* the standard input and output of the new process.  This will
>eventually be used to control the 'tar' program via a GUI.  Anyways, my test
>programs  seems to create the 2 sets of pipes and fork the new program fine,
>but when it comes time to actually *write* anything to the standard input of
>the second process, the first process sits there waiting t (presumably blocked.)

I think the problem is in closing all your pipes.  You see a pipe doesn't
get passed an EOF until all accesses to the writing end of the pipe have
been closed.  Someone else had a similar problem on this news group 
recently.  Basically, all you need to ensure is that all the file descriptors
open to the write end of your pipes have been close.  Obviously, you should
also close the read end of the pipe.  

>The code goes something like this:

I have made the changes to the code which I think should work.  And marked
the additional lines with a <------

> pipe(stdin_pipes);
> pipe(stdout_pipes);

> child_pid = fork();
> if (child_pid == 0)
>  {
>   dup2(stdin_pipes[0], 0);
>   dup2(stdout_pipes[1], 1);
    close(stdin_pipes[0]);			<------
>   close(stdin_pipes[1]);
>   close(stdout_pipes[0]);
    close(stdout_pipes[1]);			<------

>   execlp("/bin/sh", "sh", "-c", command, NULL);
>   exit(errno);

>  };

> if (child_id < 0) /* error forking! */
>  {
>   close(all pipes);
>  }
  else
>  {
>   close(stdin_pipes[0]);
>   close(stdout_pipes[1]);
>   child_stdin = fdopen(stdin_pipes[1], "w");
>   child_stdout = fdopen(stdout_pipes[0]"r");
    close(stdin_pipes[1]);			<------
    close(stdout_pipes[0]);			<------
    .... [insert your code]			<------
    fclose(child_stdin);			<------
    fclose(child_stdout);			<------
>  };

	[stuff deleted]
>me stumped!

hope it helps/works :-) 
--

>>           Michael Corbett             | "Last night, I thought my arm was <<
>> corbettm@anger.cipal.cs.curtin.edu.au | hanging out of bed.  So I got out <<
>> corbettm@cutmcvax.cs.curtin.edu.au    | to push it in."                   <<

sysmark@aurora.physics.utoronto.ca (Mark Bartelt) (05/13/91)

Michael Sweet writes:

| In a project I am working on, I want to fork and exec another process with
| pipes to *both* the standard input and output of the new process.
|               [ ... ]                                                my test
| program seems to create the 2 sets of pipes and fork the new program fine,
| but when it comes time to actually *write* anything to the standard input of
| the second process, the first process sits there waiting (presumably blocked.)

A couple days ago rec.humor.funny reprinted something from (I think) The
Guardian, containing some entries from a "programming is like sex because"
(or was it the reverse?) contest.  Somehow, the problem of communicating
bidirectionally with a child process seems like a good candidate:  It's
both a lot easier and a lot more fun once you've done it a few times :-)

This sort of thing, while not terribly difficult, isn't exactly trivial,
either.  A couple of caveats follow, and a (working!) example is appended,
but first a comment on Paul Jackson's followup:

| Only call pipe once, not twice.  The pipe(2) system call provides
| a pair of file descriptors, one good for reading and one for writing.
| And after duping them in the child to its stdin/stdout, close the
| file descriptors that you got from pipe.

I suspect that Paul didn't read Michael's original posting closely.  Note
that Michael wants to send stuff to the child's stdin *and* receive stuff
from the child's stdout.  I can't conceive of any way to do this without
using two pipes:  One for the parent to send stuff to the child, another
for the child to send stuff to the parent.  If this can be accomplished
by using a single pipe, I'd be interested in seeing how it's done.

Anyway, several things to watch out for ...

(1)  Be certain that all unnecessary file descriptors are closed.  Michael's
example doesn't close stdin_pipes[0] and stdout_pipes[1] in the child.  Note
that they're not needed, since the child has dup2()ed them to file descriptors
0 and 1.  It's not clear that failing to close them is actually the source of
the problem here, but it can't hurt.

(2)  If the child is producing output in response to input that it receives
from the parent, the parent had better read it.  If it doesn't, the "upward"
(child-to-parent) pipe will eventually constipate, and the child will block.
Because the child is blocked waiting for that pipe to partially drain, the
"downward" (parent-to-child) pipe will also constipate as the parent writes
more stuff into that pipe, so the parent will block as well, and the whole
business will hang.

(3)  If you're using stdio routines instead of read()/write() to move data
through the pipes, be sure that the streams are unbuffered (line buffered
may be good enough), or else one process will block waiting for data that
the other has written, but which hasn't actually gotten shoved into the back
end of the pipe since it's sitting instead in a partially-filled stdio buffer.

(4)  If the child is one which won't produce any output until it's seen all
its input (consider, for example, the child doing an exec() of /usr/bin/sort
just after the fork() and dup2()s), the parent should be sure to call close
(or fclose(), whichever is appropriate) on the back end of the "downward" pipe,
so that the child will see EOF on stdin.

(5)  If the child produces output on stdout, but perhaps not necessarily in
a predictable fashion (the example below produces exactly one line of output
for every line of input; spawning "sort" means that the child will produce no
output until it's seen all its input), the parent will have to sniff at the
"upward" pipe in a non-blocking way, in order to see whether there's anything
there that needs to be read (to keep the "upward" pipe from constipating).
You can do this sort of thing in a BSD environment by using FIONREAD; I'm not
certain of the best way to do this under IRIX.

Anyway, the example follows.  Note that these are really only templates, and
shouldn't be construed as bullet-proof an any sense:  No checking for error
returns from pipe() and fork(), for example; or for line-too-long situations
when calling fgets(); or several other things that really ought to be done.
Nonetheless, they should be useful as a vague outline of how to do it ...

Mark Bartelt                                                      416/978-5619
Canadian Institute for                                   mark@cita.toronto.edu
Theoretical Astrophysics                                 mark@cita.utoronto.ca

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

/*
 *	Parent process -- reads a line from stdin, sends it to
 *	child, reads a line back from child, sends it to stdout
 */

#include <stdio.h>

#define PCR	p_to_c[0]	/* parent-to-child pipe, read fd */
#define PCW	p_to_c[1]	/* parent-to-child pipe, write fd */
#define CPR	c_to_p[0]	/* child-to-parent pipe, read fd */
#define CPW	c_to_p[1]	/* child-to-parent pipe, write fd */

#define BUFSIZE 200

main()
{
	int	p_to_c[2];
	int	c_to_p[2];
	int	fk;
	FILE *	to_child;
	FILE *	from_child;
	char	buf[BUFSIZE];

	pipe(p_to_c);
	pipe(c_to_p);

	if ( (fk=fork()) == 0 ) {	/* Child process only */
		dup2(PCR,0);
		dup2(CPW,1);
	}

	close(PCR);			/* Both processes */
	close(CPW);

	if ( fk == 0 ) {		/* Child process only */
		close(PCW);
		close(CPR);
		execl("./sqrt","sqrt",0);
		fprintf(stderr,"sqrt exec failure\\n");
		exit(-1);
	}

	to_child   = fdopen(PCW,"w");
	from_child = fdopen(CPR,"r");
	setlinebuf(to_child);
	while ( fgets(buf,BUFSIZE,stdin) != NULL ) {
		fputs(buf,to_child);
		fgets(buf,BUFSIZE,from_child);
		fputs(buf,stdout);
	}

	close(PCW);
	close(CPR);
}

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

/*
 *	Child process -- reads a line from stdin, writes a line to
 *	stdout reporting square root of first numeric arg on input
 */

#include <stdio.h>
#include <math.h>

#define	BUFSIZE	200

char	buf[BUFSIZE];

main()
{
	double	val;

	setlinebuf(stdout);
	while ( fgets(buf,BUFSIZE,stdin) != NULL ) {
		sscanf(buf,"%lf",&val);
		printf("%.4f\n",sqrt(val));
	}
}

sysmark@aurora.physics.utoronto.ca (Mark Bartelt) (05/13/91)

Whoops, a minor bit of sloppiness in my previous posting.  At the very end
of the example parent process, I had

	close(PCW);
	close(CPR);

The example works as originally written, but it's really more proper to
call fclose() with the appropriate arguments, since we've done an fdopen()
on the file descriptors.  Also, a comment got dropped inadvertently.  So
the last section of the example parent process should look like:

	to_child   = fdopen(PCW,"w");	/* Parent process only */
	from_child = fdopen(CPR,"r");
	setlinebuf(to_child);
	while ( fgets(buf,BUFSIZE,stdin) != NULL ) {
		fputs(buf,to_child);
		fgets(buf,BUFSIZE,from_child);
		fputs(buf,stdout);
	}

	fclose(to_child);
	fclose(from_child);

There are undoubtedly other minor screwups in the example as well ...

Mark Bartelt                                                  416/978-5619
Canadian Institute for                               mark@cita.toronto.edu
Theoretical Astrophysics                             mark@cita.utoronto.ca