[comp.unix.questions] Need a 2-way alternative to popen

bugboy@portia.Stanford.EDU (Michael Frank) (02/25/90)

I'm writing a program, and I want it to be able to do two-way
communication with a csh process; it will occasionally type commands
to the csh and read csh's output.  I thought that

	popen("csh", "r+") 

might work, but I tried it, and it doesn't allow writing.  Well,

	popen("csh","w") 

allows me to write commands to the csh, but how can I
get its output to come to me instead of going to stdout?  I could do

	popen("csh > out","w"),

and read the output file as it's being written, but then I have to
worry about the file growing indefinitely.  I'd like to be able to
just have the output available in a pipe.  Well, popen() uses sh, and
the sh doc says something about redirecting to a file descriptor using
>&, so I thought I could do

	popen("csh >&4", "w");

where 4 was the file descriptor of one end of a pipe in my program,
and then I could read() from the other end.  But it didn't work.
Perhaps the sh process doesn't inherit my file descriptors.  I suppose
I *could* do

	popen("csh | sendoutputback","w")

where sendoutputback would be a separate program that would feed
me its standard input using IPC.  But that's ugly and a pain.
Besides, none of this is giving me access to csh's stderr either.

(Alternatively, I wish I could just have my program pretend to be me
logged in on some tty; that would accomplish all of this in one step.)

Can anybody help me?  This shouldn't be that hard to do!!!

Thanks,
Mike Frank

jik@athena.mit.edu (Jonathan I. Kamens) (02/26/90)

  (Note, I removed an unnecessary "usa" distribution on this thread.  I
don't see why this discussion would only be interesting in the United States.)

  I seem to recall this question being discussed in this newsgroup, or
in comp.unix.wizards, fairly recently, like within the past month.

  Popen() is specifically designed only to do one-way communication.  If
you want to do two-way communication with a process, the solution is to
do all of the pipe manipulation yourself.  You use two calls to pipe()
(or one call to socketpair()) to create a pair of pipes, and then you
fork.  In the child, you modify your stdin, stdout and stderr in such a
way that the pipes you just created are hooked up to it, and then you
exec the process to which you want to talk.  The parent can then read
from/write to the pipes in order to talk to the process.

  As others pointed out the last time this question was asked, you need
to be careful about the parent waiting for the output from the child and
vice versa, where buffering might occur, so the parent might be hung
waiting for output from the child, and the child hung waiting for output
from the parent.  If you use stdio, make sure to flush the output sent
to the child, and the output from the child sent to the parent.  Of
course, if you don't have the sources to the process you are executing
in the child, you can't make the child flush....

  If you have sources to your C library, I suggest you take a look at
what it does; you may be able to figure out for yourself how to expand
popen() to deal with both reading and writing to a process.

  See the pipe() and socketpair() man pages for more information.

Jonathan Kamens			              USnail:
MIT Project Athena				11 Ashford Terrace
jik@Athena.MIT.EDU				Allston, MA  02134
Office: 617-253-8495			      Home: 617-782-0710

mpf@csli.Stanford.EDU (Michael Frank) (02/26/90)

Jonathan Kamens at MIT writes:
>   Popen() is specifically designed only to do one-way communication.
> If you want to do two-way communication with a process, the solution
> is to do all of the pipe manipulation yourself.  You use two calls to
> pipe() (or one call to socketpair()) to create a pair of pipes, and
> then you fork.  In the child, you modify your stdin, stdout and stderr
> in such a way that the pipes you just created are hooked up to it, and
> then you exec the process to which you want to talk.  The parent can
> then read from/write to the pipes in order to talk to the process.

Yeah, I was afraid it would be something like that.  I just hoped
there would be an easier way.  Oh well, does anyone have example
source code that does this kind of thing?  Especially the hard parts?

>   As others pointed out the last time this question was asked, you
> need to be careful about the parent waiting for the output from the
> child and vice versa, where buffering might occur, so the parent might
> be hung waiting for output from the child, and the child hung waiting
> for output from the parent.  If you use stdio, make sure to flush the
> output sent to the child, and the output from the child sent to the
> parent.  Of course, if you don't have the sources to the process you
> are executing in the child, you can't make the child flush....

Such is the case.  Might there be another way to have output flushed
automatically, like with ioctl or something?  I really only need the
csh process to flush its output just as it does on a regular tty.  So
I just need the pipe to send the csh process's output through and not
wait for its buffer to fill.  Can do?  Yeah, I know; "RTFM".  I will.

>   If you have sources to your C library, I suggest you take a look at
> what it does; you may be able to figure out for yourself how to expand
> popen() to deal with both reading and writing to a process.

I don't have them; does anyone else out there care to send them to me?

Thanks for the info, Jonathan.

Mike Frank
bugboy@portia.stanford.edu

harrison@necssd.NEC.COM (Mark Harrison) (02/28/90)

In article <1990Feb26.071630.10948@athena.mit.edu>, jik@athena.mit.edu (Jonathan I. Kamens) writes:

>   ... excellent suggestions on using popen()
>   See the pipe() and socketpair() man pages for more information.

You might also look in _Advanced_Unix_Programming_, Marc J. Rochkind,
Prentice-Hall.  It is an excellent reference/tutorial on this and many
other Unix system calls.

bugboy@portia.Stanford.EDU (Michael Frank) (03/01/90)

Thanks to everyone who sent me help on ways to set up a 2-way
alternative to popen().  Some of you didn't hear this directly from
me, because the email bounced.  Anyway, I've put together here the 
core elements of the way to do it with pipe() instead of popen():

	int p1[2], p2[2];		/* Two pairs of fd's */
	pipe(p1); pipe(p2);		/* Create the two pipes */
	if (fork() == 0) {		/* - in child process - */
	  char * args[2];		/* array of args for command */
	  args[0] = "/bin/csh";		/* set up the args for the call */
	  args[1] = NULL;		/* (array is NULL-terminated) */
	  dup2(p1[0],0);		/* copy p1's read end to stdin */
	  dup2(p2[1],1);		/* copy p2's write end to stdout */
	  execv(args[0], args);		/* call the csh command */
	}
	tochild = p1[1]; fromchild = p2[0];	/* Parent's links to it */

Of course, to be neat about it you should also close the unused fd's
and check the system calls for errors.  But these are the central
parts.

Thaks again!
Mike

tchrist@convex.COM (Tom Christiansen) (03/06/90)

In <12430@csli.Stanford.EDU>, mpf@csli.Stanford.EDU (Michael Frank) writes:

>Yeah, I was afraid it would be something like that.  I just hoped
>there would be an easier way.  Oh well, does anyone have example
>source code that does this kind of thing?  Especially the hard parts?

Here is something I wrote to do this 5 years ago.  It worked for me then,
but I've not used it since.  It lints cleanly at least. :-)  It uses 
socketpair(2) and vfork(2).  Use this way:

	FILE *ip, *op;
	if (process("some csh_cmd", &ip, &op) == -1) { error(); }
	/* processing with fprintf(op, ...) and fscanf(ip, ...) */
	process_close(ip);

hope this helps,

--tom


/* process.c 
 *
 *  written by tom christiansen on Wed May 22 15:02:19 CDT 1985 to open
 *  a socket pair and let the user read and write from both sides.
 *  return -1 on error, otherwise put the correct file pointers
 *  into *input and *output.  
 *
 *  CAVEAT UTILITOR:
 *      you will block forever if one of the sides of the
 *      pipes blocks because of too much in the buffer.  
 */
 
#include <stdio.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <sys/socket.h>

#define READ    0
#define WRITE   1
#define CHILD   0
#define PARENT  1

/*
 *  define QUIET if you don't want error messages
 *  to print out if something blows up due to test
 *  macros.  this assumes you will perror() it yourself
 *  when the routine returns.
 */
#ifdef QUIET
#   define announce(x)  /* nothing at all */
#else
#   define announce(x) perror(x)
#endif QUIET

/*
 *  first some macros to avoid lots of typing and ugly
 *  code.
 */
#define test0(x)    \
    if (!(x)) { \
        announce("process: x"); \
        return -1;  \
    }

#define test(x) \
    if ( (x) < 0 ) {    \
        announce("process: x"); \
        return -1;  \
    }

char FD_READ[] = "r";
char FD_WRITE[] = "w";

/*
 *  next a static array to hold the pid of 
 *  the process we create so we can wait for
 *  it to die when we close up.  there is enough
 *  room for all possible file descriptors (NOFILE)
 *  so this function may be called repeatedly by
 *  a program with no ill effects.
 */
static  int pids[NOFILE];

/*****************************************************************
 *
 *  process - a function to do what popen does, but to 
 *            give you back file pointers for read as
 *            well as for write.
 *
 *****************************************************************/

int
process(cmd,input,output)
    char    *cmd;
    FILE    **input,**output;
{
    int sock[2];
    register  pid;


/*
 *  use connected socket pair so we can do reads and 
 *  writes on these things.  if we used a pipe() call,
 *  it would be unidirectional and we would have to 
 *  make two calls to get 4 file descriptors.
 */
    test(socketpair(AF_UNIX,SOCK_STREAM,0,sock));

/*
 *  fork for the child command.  don't bother doing
 *  a real fork, since we're just going to exec anyway,
 *  so borrow the parent's pages with a vfork.
 */
    if((pid = vfork()) == CHILD) { 

/*
 *  close old stdin and stdout to make room for socket
 *  descriptors.
 */
        test(close(READ));  
        test(close(WRITE));

/*  
 *  the kid will use the CHILD end.  connect both his stdin
 *  and stdout to the CHILD socket, which is fine because 
 *  unix domain sockets are bidirectional.
 */
        test(dup2(sock[CHILD], WRITE));
        test(dup2(sock[CHILD], READ));

/*
 *  don't need these anymore, and if we don't get rid of them, we 
 *  won't be happy later on, because the process doesn't seem to die.
 */
        test(close(sock[CHILD]));
        test(close(sock[PARENT]));
/*
 *  now do the command.  use the csh for tilda's sake.
 */
        execl("/bin/csh", "csh", "-fc", cmd, 0);
        perror("process kid: execl");
        _exit(1);
    }

/*
 *  -1 pid means we couldn't fork.
 */
    if(pid == -1) {
        perror("process: vfork");
        (void) close(sock[CHILD]);
        (void) close(sock[PARENT]);
        return -1;
    }

/*
 *  otherwise, we are the parent and healthy;
 *  remember the kid pid for a later close.
 */
    pids[sock[PARENT]] = pid;

/*
 *  connect up the user's input and output file
 *  pointers to our end of the socket connection.
 *  give him one for read and one for write.
 */
    test0(*input = fdopen(sock[PARENT],FD_READ));
    test0(*output = fdopen(sock[PARENT],FD_WRITE));

    test(close(sock[CHILD]));

    return 0;
}


/****************************************************************
 *  close up the passed file and wait for the 
 *  child to die.
 ***************************************************************/

#undef test
#define test(x) \
    if ( (x) < 0 ) {    \
        announce("process_close: x");   \
        return -1;  \
    }

/*
 *  don't need them both since they are the 
 *  same thing
 */
int
process_close(input)
FILE *input;
{
    register f,r, (*hstat)(), (*istat)(), (*qstat)();
    int status;

    f = fileno(input);
    test(fclose(input)); 
/*
 *  don't need to close also output, as it is the same 
 */

/*
 *  protect ourselves from unfriendly signals while
 *  waiting for child's death.
 */
    istat = signal(SIGINT, SIG_IGN);
    qstat = signal(SIGQUIT, SIG_IGN);
    hstat = signal(SIGHUP, SIG_IGN);

/*
 *  wait for the child to die, keeping track of status.
 *  we saved the kid pid in the pids[] array when we 
 *  first did the process call.
 */
    while((r = wait((union wait *)&status)) != pids[f] && r == -1)
        ;
    if(r == -1)
        status = -1;

/*  
 *  restore old sig values.
 */
    (void) signal(SIGINT, istat);
    (void) signal(SIGQUIT, qstat);
    (void) signal(SIGHUP, hstat);

    return(status);

}


/* lint output:
process.c:
*/
--

    Tom Christiansen                       {uunet,uiucdcs,sun}!convex!tchrist 
    Convex Computer Corporation                            tchrist@convex.COM
		 "EMACS belongs in <sys/errno.h>: Editor too big!"