//-----------------------------------------------------------------------------
// Copyright © 2003 - Philip Howard - All rights reserved
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//-----------------------------------------------------------------------------
// package	libh/io
// homepage	http://libh.slashusr.org/
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	libh at ipal dot org
// homepage	http://phil.ipal.org/
//-----------------------------------------------------------------------------
// This file is best viewed using a fixed spaced font such as Courier
// and in a display at least 120 columns wide.
//-----------------------------------------------------------------------------

#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>

#include "io_lib.h"

extern char **environ;

__FMACRO_BEGIN__
//-----------------------------------------------------------------------------
// function	fopen_fork_exec
//
// purpose	Fork a child process with one or more pipes between parent and
//		child which will be stdin, stdout, and/or stderr for child.
//
// arguments	1 (FILE * *) where to store FILE pointer of write end of pipe
//			which will be the child's stdin.
//		2 (FILE * *) where to store FILE pointer of read end of pipe
//			which will be the child's stdout.
//		3 (FILE * *) where to store FILE pointer of read end of pipe
//			which will be the child's stderr.
//		4 (const char *) filename of program to execute in child
//		5-N (const char *) arguments to program being executed
//
// returns	(int) -1 if there is an error (see errno)
//		(int) child PID
//
// note		If a pointer has the value NULL, then no pipe will be created
//		for that particular purpose, and the child will inherit the
//		same FILE pointer as the parent.
//
// note		If the pointers given in arguments 2 and 3 are the same, then
//		only a single pipe will be created for child output, and that
//		pipe will be duplicated for both stdout and stderr.
//
// warning	Care should be taken when transferring data both to and from
//		a child process at the same time to ensure that the parent and
//		child do not get into deadlock condition waiting for buffered
//		data from each other.  This is especially important when the
//		child process executes another program which may not handle
//		buffering as expected (such as a data compression program).
//-----------------------------------------------------------------------------
#define fopen_fork_exec(i,o,e,p,a...) (fopen_fork_exec)((i),(o),(e),(p),a,(const char*)(NULL))

__FMACRO_END__

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	(fopen_fork_exec)
//
// purpose	Fork a child process with one or more pipes between parent and
//		child which will be stdin, stdout, and/or stderr for child.
//
// arguments	1 (FILE * *) where to store FILE pointer of write end of pipe
//			which will be the child's stdin.
//		2 (FILE * *) where to store FILE pointer of read end of pipe
//			which will be the child's stdout.
//		3 (FILE * *) where to store FILE pointer of read end of pipe
//			which will be the child's stderr.
//		4 (const char *) filename of program to execute in child
//		5-N (const char *) arguments to program being executed ended
//			with a NULL argument
//
// returns	(int) -1 if there is an error (see errno)
//		(int) child PID
//
// note		If a pointer has the value NULL, then no pipe will be created
//		for that particular purpose, and the child will inherit the
//		same FILE pointer as the parent.
//
// note		If the pointers given in arguments 2 and 3 are the same, then
//		only a single pipe will be created for child output, and that
//		pipe will be duplicated for both stdout and stderr.
//
// warning	Care should be taken when transferring data both to and from
//		a child process at the same time to ensure that the parent and
//		child do not get into deadlock condition waiting for buffered
//		data from each other.  This is especially important when the
//		child process executes another program which may not handle
//		buffering as expected (such as a data compression program).
//-----------------------------------------------------------------------------
int
(fopen_fork_exec) (
    FILE * *		arg_file_ptr_stdin
    ,
    FILE * *		arg_file_ptr_stdout
    ,
    FILE * *		arg_file_ptr_stderr
    ,
    const char *	arg_execute
    ,
    ...
    )
__PROTO_END__
{
    FILE *		file_stdin	;
    FILE *		file_stdout	;
    FILE *		file_stderr	;
    va_list		args_list	;
    char * *		args_array	;
    char * *		args_ptr	;
    int			args_count	;
    int			pipe_stdin	[2];
    int			pipe_stdout	[2];
    int			pipe_stderr	[2];
    int			fork_pid	;


    //---------------------------------------
    // Initialize pipe descriptors as closed.
    //---------------------------------------
    pipe_stdin[0]  = pipe_stdin[1]  = -1;
    pipe_stdout[0] = pipe_stdout[1] = -1;
    pipe_stderr[0] = pipe_stderr[1] = -1;

    //---------------------------------------------
    // Initialize standard file pointers as closed.
    //---------------------------------------------
    file_stdin = file_stdout = file_stderr = NULL;

    //--------------------
    // Create the pipe(s).
    //--------------------
    if ( arg_file_ptr_stdin ) {
	if ( pipe( pipe_stdin ) < 0 ) goto error_cleanup_return;
    }
    if ( arg_file_ptr_stdout ) {
	if ( pipe( pipe_stdout ) < 0 ) goto error_cleanup_return;
    }
    if ( arg_file_ptr_stderr ) {
	if ( arg_file_ptr_stderr == arg_file_ptr_stdout ) {
	    pipe_stderr[0] = pipe_stdout[0];
	    pipe_stderr[1] = pipe_stdout[1];
	} else {
	    if ( pipe( pipe_stderr ) < 0 ) goto error_cleanup_return;
	}
    }

    //------------------------------
    // Flush standard library files.
    //------------------------------
    fflush( stdout );
    fflush( stderr );

    //--------------------------
    // Create the child process.
    //--------------------------
    fork_pid = fork();
    if ( fork_pid < 0 ) goto error_cleanup_return;

    //--------------------
    // Handle child setup.
    //--------------------
    if ( fork_pid == 0 ) {

	//------------------------------------------------
	// Get the stdin descriptor into proper alignment.
	//------------------------------------------------
	if ( arg_file_ptr_stdin ) {
	    close( pipe_stdin[1] );
	    if ( pipe_stdout[1] == STDIN_FILENO ) {
		if ( ( pipe_stdout[1] = dup( STDIN_FILENO ) ) < 0 ) _exit( 1 );
	    }
	    if ( pipe_stderr[1] == STDIN_FILENO ) {
		if ( ( pipe_stderr[1] = dup( STDIN_FILENO ) ) < 0 ) _exit( 1 );
	    }
	    if ( pipe_stdin[0] != STDIN_FILENO ) {
		if ( dup2( pipe_stdin[0], STDIN_FILENO ) < 0 ) _exit( 1 );
		close( pipe_stdin[0] );
	    }
	}

	//-------------------------------------------------
	// Get the stdout descriptor into proper alignment.
	//-------------------------------------------------
	if ( arg_file_ptr_stdout ) {
	    close( pipe_stdout[0] );
	    if ( pipe_stderr[1] == STDOUT_FILENO ) {
		if ( ( pipe_stderr[1] = dup( STDOUT_FILENO ) ) < 0 ) _exit( 1 );
	    }
	    if ( pipe_stdout[1] != STDOUT_FILENO ) {
		if ( dup2( pipe_stdout[1], STDOUT_FILENO ) < 0 ) _exit( 1 );
		close( pipe_stdout[1] );
	    }
	}

	//-------------------------------------------------
	// Get the stderr descriptor into proper alignment.
	//-------------------------------------------------
	if ( arg_file_ptr_stderr ) {
	    close( pipe_stderr[0] );
	    if ( pipe_stderr[1] != STDERR_FILENO ) {
		if ( dup2( pipe_stderr[1], STDERR_FILENO ) < 0 ) _exit( 1 );
		close( pipe_stderr[1] );
	    }
	}

	//---------------------------------------
	// Count the number of program arguments.
	// Also count for program name and NULL.
	//---------------------------------------
	args_count = 2;
	va_start( args_list, arg_execute );
	while ( va_arg( args_list, const char * ) ) ++ args_count;
	va_end( args_list );

	//----------------------------------
	// Make an array with the arguments.
	//----------------------------------
	args_array = alloca( args_count * sizeof (const char *) );
	if ( ! args_array ) _exit( 1 );
	args_ptr = args_array;
	* args_ptr ++ = (char *) arg_execute;
	va_start( args_list, arg_execute );
	while ( ( * args_ptr = va_arg( args_list, char * ) ) ) ++ args_ptr;
	va_end( args_list );

	//------------------------------------------
	// Execute specified program with arguments.
	//------------------------------------------
	execve( arg_execute, args_array, environ );
	_exit( 1 );
    }

    //------------------------------------------
    // The parent must not have child ends open.
    //------------------------------------------
    if ( pipe_stdin[0]  >= 0 ) close( pipe_stdin[0]  );
    if ( pipe_stdout[1] >= 0 ) close( pipe_stdout[1] );
    if ( pipe_stderr[1] >= 0 ) close( pipe_stderr[1] );

    //-----------------------------------
    // Store FILE pointers for caller.
    //-----------------------------------
    if ( arg_file_ptr_stdin  ) {
	if ( ! ( file_stdin = fdopen( pipe_stdin[1], "w" ) ) ) goto error_cleanup_return;
	* arg_file_ptr_stdin = file_stdin;
    }
    if ( arg_file_ptr_stdout ) {
	if ( ! ( file_stdout = fdopen( pipe_stdout[0], "r" ) ) ) goto error_cleanup_return;
	* arg_file_ptr_stdout = file_stdout;
    }
    if ( arg_file_ptr_stderr ) {
	if ( arg_file_ptr_stderr == arg_file_ptr_stdout ) {
	    file_stderr = file_stdout;
	} else {
	    if ( ! ( file_stderr = fdopen( pipe_stderr[0], "r" ) ) ) goto error_cleanup_return;
	}
	* arg_file_ptr_stderr = file_stderr;
    }

    //-----------------------------------------------------
    // Return child process ID to caller in parent process.
    //-----------------------------------------------------
    return fork_pid;

    //----------------------------------------------
    // Do error cleanup and return error indication.
    //----------------------------------------------
 error_cleanup_return:
    if ( file_stdin  )		fclose( file_stdin  );
    if ( file_stdout )		fclose( file_stdout );
    if ( file_stderr )		fclose( file_stderr );
    if ( pipe_stderr[0] >= 0 )	close( pipe_stderr[0] );
    if ( pipe_stderr[1] >= 0 )	close( pipe_stderr[1] );
    if ( pipe_stdout[0] >= 0 )	close( pipe_stdout[0] );
    if ( pipe_stdout[1] >= 0 )	close( pipe_stdout[1] );
    if ( pipe_stdin[0]  >= 0 )	close( pipe_stdin[0]  );
    if ( pipe_stdin[1]  >= 0 )	close( pipe_stdin[1]  );
    return -1;
}

