/*
 *   Copyright (c) 1998 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */

static const char rcsid[] = "$Id: s.rpopen.c 1.7 02/06/15 00:20:45-07:00 root@egypt.pell.portland.or.us $";

/*
 * rpopen: popen without any icky shells
 */
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#ifdef DEBUG
#   include <sys/stat.h>
#   include <fcntl.h>
#endif


/*
 * rpopen() and rpclose() implement a popen()-style version of fopen,
 * but instead of passing a command line to /bin/sh (which most likely
 * will not exist on an installer disk, which is where this is really
 * needed) it fork()s and execl()s the process directly.  To maintain
 * a linkage between files and process ids for rpclose(), we keep a
 * private glue array to bind FILE* to process ids and only pass FILE*
 * objects out to the unwashed masses.
 *
 * if rpglue[].childproc != 0, this slot is occupied, otherwise it's
 * free for the taking.
 */
#define NR_RPOPEN	20

struct rpglue {
    pid_t childproc;
    FILE *file;
} rpglue[NR_RPOPEN];


/*
 * rpopenio() exec a program, redirecting both input and output.
 */
FILE *
rpopenio(int otherfd, char *program, char **arglist, char *mode)
{
    pid_t child;
    int pfd[2];
    int ix;
    int rc;
    int reading = 0;

    /* find an open slot in the rpglue[] array */
    for (ix=0; ix<NR_RPOPEN; ix++)
	if (rpglue[ix].childproc == 0)
	    break;

    if (ix >= NR_RPOPEN) {
	errno = EMFILE;
	return (FILE*)0;
    }
#ifdef DEBUG
    printf("slot %d open\n", ix);
#endif

    /* validate open mode */
    if (strchr(mode, 'r') != 0)
	reading++;

    if (strchr(mode, 'w') != 0 || strchr(mode, 'a') != 0)
	if (reading) {
	    /* can't rpopen in r/w mode, sorry! */
	    errno = EINVAL;
	    return (FILE*)0;
	}

    /* get a pipe */
    if (pipe(pfd) != 0)
	return (FILE*)0;

    /* now we do the real work... */

    /* fork off a child */

    if ((child = vfork()) < 0) {
	/* fork failed.  Close down the pipe and exit */
	close(pfd[0]); close(pfd[1]);
	return (FILE*)0;
    }
    else if (child == 0) {
	/* We are the child */

#if 0
	/* close any open rpopen()ed files */
	for (ix = 0; ix < NR_RPOPEN; ix++)
	    if (rpglue[ix].childproc)
		fclose(rpglue[ix].file);
#endif

	/* glue input or output into the pipe */
	rc = (reading) ? dup2(pfd[1], 1) : dup2(pfd[0], 0);
#ifdef DEBUG
	fprintf(stderr, "rc = %d--force %d to %s\n", rc, 
				    reading ? pfd[1] : pfd[0],
				    reading ? "output" : "input");
#endif

	if ((rc != -1) && (otherfd != -1)) {
	    rc = dup2(otherfd, reading ? 0 : 1);
#ifdef DEBUG
	    fprintf(stderr, "rc = %d--force %d to %s\n", rc, 
				    otherfd,
				    reading ? "input" : "output");
	    if (rc == -1) perror("dup2");
#endif
	}

	if (rc == -1) {
	    /* couldn't glue the appropriate file descriptor to stdin/stdout
	     */
	    exit(127);
	}

	close(pfd[reading ? 0 : 1]);
	execv(program, arglist);
	exit(127);
    }
    /* We are the parent, so glue ourself to the appropriate
     * file descriptor, record ourself in rpglue[], and
     * return the FILE* to the caller 
     */
    close(pfd[reading ? 1 : 0]);	/* close the descriptor we don't need */
    if ((rpglue[ix].file = fdopen(pfd[reading ? 0 : 1],mode)) == (FILE*)0) {
	/* couldn't fdopen() our side of the pipe
	 */
	close(pfd[reading ? 0 : 1]);
	kill(child, SIGKILL);
	waitpid(child, &rc, 0);
	return 0;
    }
    rpglue[ix].childproc = child;
    return rpglue[ix].file;
} /* rpopen */


/*
 * rpopen() exec a program, piping input or output into a FILE*
 */
FILE *
rpopen(char *program, char **arglist, char *mode)
{
    return rpopenio(-1, program, arglist, mode);
}


/*
 * rpclose() shuts down a rpopen()'ed FILE
 */
int
rpclose(FILE *file)
{
    int ix;
    int rc, status;

    if (file == 0) {
	errno = EINVAL;
	return -1;
    }
    for (ix = 0; ix < NR_RPOPEN; ix++)
	if (rpglue[ix].file == file) {
#ifdef DEBUG
	    printf("rpclose: file match %d\n", ix);
#endif
	    /* wait for the process to finish
	     */
	    do {
		rc = waitpid(rpglue[ix].childproc, &status, 0);
	    } while (rc != rpglue[ix].childproc && errno == EINTR);
	    /* close down the file */
	    fclose(rpglue[ix].file);
	    /* free our resources */
	    rpglue[ix].file = 0;
	    rpglue[ix].childproc = 0;
	    /* return the childs exit status */
	    return status;
	}

    errno = EINVAL;
    return -1;
} /* rpclose */


/*
 * rpkill() sends a signal to the process at the other size of a
 * rpopen()ed FILE*
 */
int
rpkill(FILE* rpipe, int signal)
{
    int ix;

    for (ix = 0; ix < NR_RPOPEN; ix++)
	if (rpipe == rpglue[ix].file)
	    return kill(rpglue[ix].childproc, signal);
    errno = ESRCH;
    return -1;
} /* rpkill */


#if DEBUG
void
report(int status)
{
    if (status == -1)
	perror("rpclose");
    else {
	if (WIFEXITED(status))
	    printf("Normal exit: rc=%d\n", WEXITSTATUS(status));
	else if (WIFSIGNALED(status))
	    printf("Exit on signal %d\n", WTERMSIG(status));
	else if (WIFSTOPPED(status))
	    printf("Stopped on signal %d\n", WSTOPSIG(status));
	else
	    printf("mysterious exit status %d\n", status);
    }
}


void
main()
{
    int fd;
    char line[200];
    int linecount=0;
    int status;
    static char *lsargs[] = { "ls", "-CF", 0 };
    static char *odargs[] = { "od", "-c", 0 };
    FILE *ls, *l2;

    if ((ls = rpopen("/bin/ls", lsargs, "r")) != (FILE*)0) {
	while (fgets(line, sizeof line, ls) != 0)
	    printf("%d:%s", ++linecount, line);
	status = rpclose(ls);

	report(status);
    }
    else
	perror("rpopen");

    puts("\n--------------------------------------------------------\n");

    if ( (fd = open("rpopen.c", O_RDONLY)) >= 0 ) {
	linecount=0;
	if ((ls = rpopenio(fd, "/usr/bin/od", odargs, "r")) != 0) {
	    while (fgets(line,sizeof line, ls) != 0)
		printf("%d:%s", ++linecount, line);
	    status = rpclose(ls);
	    report(status);
	}
    }
    close(fd);
    puts("\n--------------------------------------------------------\n");

    if ((ls = rpopen("/bin/ls", lsargs, "r")) != 0) {
	if ( (l2 = rpopenio(fileno(ls),"/usr/bin/od", odargs, "r")) != 0) {
	    linecount=0;
	    while (fgets(line,sizeof line, l2) != 0)
		printf("%d:%s",++linecount,line);
	    status = rpclose(l2);
	    report(status);
	}
	status = rpclose(ls);
	report(status);
    }
}
#endif
