/* popen2 - version of popen that uses separate fds for writing and reading
**
** Partly derived from:
** http://media.unpythonic.net/emergent-files/01108826729/popen2.c
** https://raw.githubusercontent.com/vi/syscall_limiter/master/writelimiter/popen2.c
*/

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>

#include "popen2.h"


static void set_ndelay( int fd )
    {
    int flags, newflags;

    flags = fcntl( fd, F_GETFL, 0 );
    if ( flags != -1 )
	{
	newflags = flags | (int) O_NDELAY;
	if ( newflags != flags )
	    (void) fcntl( fd, F_SETFL, newflags );
	}
    }


int popen2( const char* cmd, phandle2* handle )
    {
    int pipe_stdin[2], pipe_stdout[2];
    pid_t p;

    if ( pipe( pipe_stdin ) != 0 )
	return -1;
    if ( pipe( pipe_stdout ) != 0 )
	return -1;

    p = fork();
    if ( p < 0 )
	return -1;
    if ( p == 0 )
	{
	/* Child */
        (void) close( pipe_stdin[1] );
        (void) dup2( pipe_stdin[0], 0 );
        (void) close( pipe_stdout[0] );
        (void) dup2( pipe_stdout[1], 1 );
        (void) execl( "/bin/sh", "sh", "-c", cmd, (char*) 0 );
        perror( "execl" );
	exit( 99 );
	}

    /* Parent. */
    handle->child_pid = p;
    (void) close( pipe_stdin[0] );
    handle->to_child = pipe_stdin[1];
    handle->from_child = pipe_stdout[0];
    set_ndelay( handle->to_child );
    set_ndelay( handle->from_child );
    (void) close( pipe_stdout[1] );
    return 0; 
    }


int pwriteread2( phandle2* handle, pwriteread_cb write_cb, void* write_cd, pwriteread_cb read_cb, void* read_cd )
    {
    fd_set writeset, readset;

    FD_ZERO( &writeset );
    if ( handle->to_child != -1 )
	FD_SET( handle->to_child, &writeset );
    FD_ZERO( &readset );
    if ( handle->from_child != -1 )
	FD_SET( handle->from_child, &readset );
    if ( select( FD_SETSIZE, &readset, &writeset, (fd_set*) 0, (struct timeval*) 0 ) == -1 )
	return -1;
    if ( handle->to_child != -1 && FD_ISSET( handle->to_child, &writeset ) )
	{
	if ( write_cb( handle, write_cd ) == -1 )
	    return -1;
	}
    else if ( handle->from_child != -1 && FD_ISSET( handle->from_child, &readset ) )
	{
	if ( read_cb( handle, read_cd ) == -1 )
	    return -1;
	}
    return 0;
    }


int pclose2( phandle2* handle )
    {
    if ( handle->to_child != -1 )
	(void) close( handle->to_child );
    if ( handle->from_child != -1 )
	(void) close( handle->from_child );
    if ( waitpid( handle->child_pid, (int*) 0, 0 ) != 0 )
	return -1;
    return 0;
    }
