#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

static pid_t *pids = NULL;
static int pids_size = 0;
static int pids_top = 0;
static pthread_mutex_t pids_lock = PTHREAD_MUTEX_INITIALIZER;

FILE *popen(const char *cmd, const char *mode)
{
    int fds[2], parent_fd, child_fd, child_target, new_size, i;
    pid_t pid, *new_pids;

    /* Verify the mode. */
    if ((*mode != 'r' && *mode != 'w') || mode[1] != 0)
	return NULL;

    /* Generate fds, and choose the parent and child fds. */
    if (pipe(fds) < 0)
	return NULL;
    parent_fd = (*mode == 'r') ? fds[0] : fds[1];
    child_fd = (*mode == 'r') ? fds[1] : fds[0];

    /* Ensure that there is space in the pid table. */
    pthread_mutex_lock(&pids_lock);
    if (pids_size <= parent_fd) {
	new_size = getdtablesize();
	new_pids = malloc(new_size * sizeof(pid_t));
	if (new_pids == NULL) {
	    close(parent_fd);
	    close(child_fd);
	    return NULL;
	}
	while (pids_size < new_size)
	    new_pids[pids_size++] = -1;
	if (pids)
	    free(pids);
	pids = new_pids;
    }
    pthread_mutex_unlock(&pids_lock);

    /* Fork off a child process. */
    pid = fork();
    if (pid < 0) {
	/* Failed to fork. */
	close(parent_fd);
	close(child_fd);
	return NULL;
    }

    if (pid == 0) {
	/* Child.  Set the child fd to stdout or stdin as appropriate,
	 * and close the parent fd. */
	child_target = (*mode == 'r') ? STDOUT_FILENO : STDIN_FILENO;
	if (child_fd != child_target) {
	    dup2(child_fd, (*mode == 'r') ? 1 : 0);
	    close(child_fd);
	}
	close(parent_fd);

	/* Close all parent fds from previous popens(). */
	for (i = 0; i < pids_top; i++) {
	    if (pids[i] != -1)
		close(i);
	}

	execl("/bin/sh", "sh", "-c", cmd, NULL);
    }

    /* Record the parent fd in the pids table. */
    pthread_mutex_lock(&pids_lock);
    pids[parent_fd] = pid;
    if (pids_top < parent_fd + 1)
	pids_top = parent_fd + 1;
    pthread_mutex_unlock(&pids_lock);

    /* Close the child fd and return a stdio buffer for the parent fd. */
    close(child_fd);
    return fdopen(parent_fd, mode);
}

int pclose(fp)
    FILE *fp;
{
    int fd;
    int omask;
	int pstat;
    pid_t pid, result;

    /* Make sure this is a popened file. */
    pthread_mutex_lock(&pids_lock);
    fd = fileno(fp);
    if (pids_top <= fd || pids[fd] == -1)
	return -1;
    pid = pids[fd];
    pthread_mutex_unlock(&pids_lock);

    /* Wait for the subprocess to quit. */
    omask = sigblock(sigmask(SIGINT) | sigmask(SIGQUIT) | sigmask(SIGHUP));
    do {
	result = waitpid(pid, &pstat, 0);
    } while (result == -1 && errno == EINTR);
    sigsetmask(omask);

    /* Remove the pid entry from the table. */
    pthread_mutex_lock(&pids_lock);
    pids[fd] = -1;
    while (pids_top > 0 && pids[pids_top - 1] == -1)
	pids_top--;
    pthread_mutex_unlock(&pids_lock);

    /* Close the file.  (This releases fd, which could allow a popen(), so
     * do it last.) */
    fclose(fp);

    return (result == -1) ? -1 : pstat;
}

