[alt.sources] Need a 2-way alternative to popen

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!"