Master/Slave Processes?

Scott Hess scott at erick.gac.edu
Sun Feb 10 11:39:04 AEST 1991


In article <1991Feb08.085034.13605 at ariel.unm.edu> mdiehl at hydra.unm.edu (J. Michael Diehl) writes:
   I am trying to spawn a process and be able to write to it's stdin and read
   from it's stdout.  Pipe, Exec, System don't seem to do the trick.  Any
   ideas?  I know this is possible since the window system seems to do this.
   Please Help Me!

[Since there appears to be some small interest in this (I've seen a couple
posts which do not quite answer this), and I'd really have enjoyed an
explaination when _I_ was trying to figure this out, I've posted a
simple explaination to the net. -scott]

What you want is a more capable version of the popen(3) call, I presume.
popen(3) only allows you to start a subprocess for reading from its
stdout, or for writing to its stdin.  There are good reasons for this.
The problem is deadlock.  If you were to open a file for both stdin
and stdout, you run the risk of having the child process waiting for
data on it's stdin before proceeding, while you are waiting for data
on the child's stdou before proceeding.  Also, if you attempt to write
more than some system defined amount to the stdin, the buffers will fill
(say you try to write, oh, 16k of data to the child's stdin.  Then,
the child processes some, and writes the result to stdout.  Let's
use cat(1) as the child.  If your system buffers could only hold
4k, you'll write 4k, the child transfers that to its stdout, you
write 4k, and you deadlock - the child's stdout is full, so it
cannot run anymore.  You're waiting to finish the 16k write, so
you cannot run anymore.  Apply kill(1) repeatedly :-).

That out of the way, how can you boldly forge ahead and accomplish
this thing anyhow?  The simplest case is fairly simple.  To get a
file descriptor for reading and writing to a child process's
stdin, stdout (and stderr, which I'll map to stdout) do something
like the following (under BSD 4.3, using ANSI-C):

int sys( const char *cmd)
{
  int fds[ 2];
  int cpid;

  if( socketpair( AF_UNIX, SOCK_STREAM, AF_UNSPEC, fds)==-1)
    return -1;
  if( !(cpid=fork()))
    {                           /* On the child side */
      const char *shell=getenv( "SHELL");
      if( shell==NULL)	shell="/bin/sh";
      close( fds[ 0]);		/* Close the parent's fd */
      dup2( fds[ 1], 0);        /* dup into stdin */
      dup2( fds[ 1], 1);        /* stdout */
      dup2( fds[ 1], 2);        /* and stderr */
      close( fds[ 1]);
      execl( shell, shell, "-c", cmd, NULL);
      exit( 1);			/* didn't succeed, exit w/error */
    }
  if( cpid==-1)			/* If there was a fork() error, */
    {
      close( fds[ 1]);		/* Close up the sockets */
      close( fds[ 0]);
      return -1;
    }
  close( fds[ 1]);		/* Close the child-side fd */
  return fds[ 0];		/* Return the parent-side file descriptor */
}

[This has been very minimally tested on a BSD 4.3 system, and probably
 won't work at all in the real world.  It's just meant as an example,
 anyhow -scott]

The socketpair() call returns a sort of two-way pipe [the pipe() call
returns a one-way pipe].  Either end can read/write from it.  After
the fork(), the child closes the parent's side, duplicates the child
side fd into 0, 1, and 2 (note that this code requires that 0, 1,
and 2 exist, so that socketpair() doesn't put one of the ends into
0, 1, or 2), closes the child side, and execs a shell to attempt to
run the program.

On the parent side, just close up the child side, and away we go.

This is very quick and dirty - you'll probably want more pre and
post processing in there.  It also doesn't address how to avoid
the deadlock problems - that's going to be up to you . . .

--
scott hess                      scott at gac.edu
Independent NeXT Developer	GAC Undergrad
<I still speak for nobody>
"Tried anarchy, once.  Found it had too many constraints . . ."
"Buy `Sweat 'n wit '2 Live Crew'`, a new weight loss program by
Richard Simmons . . ."



More information about the Comp.unix.questions mailing list