/* Copyright (C) 2005 Bjoern Beutel. */

/* Description. =============================================================*/

/* This module manages child processes with optional input and/or output
 * pipes. */

/* Includes. ================================================================*/

#define _POSIX_SOURCE
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <setjmp.h>
#include <glib.h>
#include "basic.h"
#include "files.h"
#include "input.h"
#include "processes.h"
#ifdef POSIX
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/wait.h>
#endif
#ifdef WIN32
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#endif

/* Functions. ===============================================================*/

bool_t 
start_process( process_t *process )
/* Check whether PROCESS is still running. If not, start PROCESS anew by
 * executing PROCESS->COMMAND_LINE.
 * The fields PROCESS->USE_INPUT, PROCESS->USE_OUTPUT, PROCESS->NAME and
 * PROCESS->COMMAND_LINE must have been set.
 * If PROCESS->USE_INPUT == TRUE, open a pipe from the child process to the
 * parent process. Connect it to stdout in the child process and to
 * INPUT_STREAM in the parent process.
 * If PROCESS->USE_OUTPUT == TRUE, open a pipe from the parent process to the
 * child process. Connect it to OUTPUT_STREAM in the parent process and to
 * stdin in the child process.
 * Return TRUE if process has actually been started now. */
{
#ifdef POSIX
  int to_child_fd[2];
  int from_child_fd[2];
  string_t arguments, argument;
  string_t *args;
  int_t arg_count, i;

  if (process->id != 0) 
  { 
    if (waitpid( (pid_t) process->id, NULL, WNOHANG ) == 0) 
      return FALSE;
    process->id = 0;
    close_stream( &process->input_stream, NULL );
    close_stream( &process->output_stream, NULL );
  }

  if (process->command_line == NULL) 
    complain( "Missing %s command line.", process->name );
  if (process->use_output && pipe( to_child_fd ) == -1)
  {
    complain( "Can't create pipe to %s: %s.", 
	      process->name, strerror( errno ) );
  }
  if (process->use_input && pipe( from_child_fd ) == -1)
  {
    complain( "Can't create pipe from %s: %s.", 
	      process->name, strerror( errno ) );
  }

  signal( SIGPIPE, SIG_IGN );
  switch (process->id = (ptr_t) fork()) 
  {
  case -1:
    complain( "Can't create %s: %s.", process->name, strerror( errno ) );
    break;
  case 0:
    if (process->use_output)
    {
      dup2( to_child_fd[0], STDIN_FILENO );
      close( to_child_fd[0] );
      close( to_child_fd[1] );
    }
    if (process->use_input)
    {
      dup2( from_child_fd[1], STDOUT_FILENO );
      close( from_child_fd[0] );
      close( from_child_fd[1] );
    }
    signal( SIGINT, SIG_IGN );

    /* Count arguments. */
    arg_count = 0;
    arguments = process->command_line;
    while (*arguments != EOS) 
    { 
      argument = parse_word( &arguments );
      free_mem( &argument );
      arg_count++;
    }

    /* Create argument vector. */
    args = new_vector( sizeof( string_t ), arg_count + 1 );
    arguments = process->command_line;
    for (i = 0; i < arg_count; i++) 
      args[i] = parse_word( &arguments );
    args[i] = NULL;

    /* Execute command line. */
    execvp( args[0], (char **) args );
    fprintf( stderr, "Can't start %s \"%s\": %s.\n", 
             process->name, args[0], strerror( errno ) );
    exit(1);
  default:
    if (process->use_output)
    {
      close( to_child_fd[0] );
      process->output_stream = fdopen( to_child_fd[1], "w" );
      if (process->output_stream == NULL)
      {
	complain( "Can't open pipe to %s: %s.", 
		  process->name, strerror( errno ) );
      }
    }
    if (process->use_input)
    {
      close( from_child_fd[1] );
      process->input_stream = fdopen( from_child_fd[0], "r" );
      if (process->input_stream == NULL)
      { 
	complain( "Can't open pipe from %s: %s.", 
		  process->name, strerror( errno ) );
      }
    }
  }
  return TRUE;
#endif

#ifdef WIN32
  HANDLE parent_read, parent_write, parent_read_local, parent_write_local;
  HANDLE child_read, child_write, old_stdin, old_stdout;
  SECURITY_ATTRIBUTES security;
  STARTUPINFO startup_info;
  PROCESS_INFORMATION process_info;
  DWORD status;
  int fd;

  /* Check if the process is still running. */
  if (process->id != 0) 
  {
    if (GetExitCodeProcess( (HANDLE) process->id, &status ) 
	&& status == STILL_ACTIVE)
    {
      return FALSE;
    }
    CloseHandle( (HANDLE) process->id );
    process->id = 0;
    close_stream( &process->input_stream, NULL );
    close_stream( &process->output_stream, NULL );
  }
  
  /* Check command line. */
  if (process->command_line == NULL) 
    complain( "Missing transmit command line." );

  /* We need pipes with inherited handles. */
  security.nLength = sizeof( SECURITY_ATTRIBUTES );
  security.bInheritHandle = TRUE;
  security.lpSecurityDescriptor = NULL;

  /* Create a pipe from the parent to the child. */
  if (process->use_output)
  {
    if (! CreatePipe( &child_read, &parent_write, &security, 0 ))
      complain( "Can't create pipe to %s.", process->name );

    /* Change STDIN for the child process. */
    old_stdin = GetStdHandle( STD_INPUT_HANDLE );
    SetStdHandle( STD_INPUT_HANDLE, child_read );
    
    /* Make sure the parent's handle is not inherited. */
    DuplicateHandle( GetCurrentProcess(), parent_write, 
		     GetCurrentProcess(), &parent_write_local,
		     0, FALSE, DUPLICATE_SAME_ACCESS );
    CloseHandle( parent_write );
  }

  /* Create a pipe from the child to the parent. */
  if (process->use_input)
  {
    if (! CreatePipe( &parent_read, &child_write, &security, 0 ))
      complain( "Can't create pipe from %s.", process->name );

    /* Change STDOUT for the child process. */
    old_stdout = GetStdHandle( STD_OUTPUT_HANDLE );
    SetStdHandle( STD_OUTPUT_HANDLE, child_write );
    
    /* Make sure the parent's handle is not inherited. */
    DuplicateHandle( GetCurrentProcess(), parent_read,
		     GetCurrentProcess(), &parent_read_local,
		     0, FALSE, DUPLICATE_SAME_ACCESS );
    CloseHandle( parent_read );
  }

  /* Start the process as child. */
  ZeroMemory( &startup_info, sizeof( startup_info ) );
  startup_info.cb = sizeof( startup_info );
  if (! CreateProcess( NULL, process->command_line, NULL, NULL, TRUE, 
		       CREATE_NEW_PROCESS_GROUP, 
		       NULL, NULL, &startup_info, &process_info ))
  {
    complain( "Can't create %s.", process->name );
  }
  process->id = (ptr_t) process_info.hProcess;
  CloseHandle( process_info.hThread );

  if (process->use_output)
  {
    /* Reset stdin for parent. */
    SetStdHandle( STD_INPUT_HANDLE, old_stdin );
    CloseHandle( child_read );

    /* Open stream to write to. */
    fd = _open_osfhandle( (long) parent_write_local, O_TEXT );
    process->output_stream = _fdopen( fd, "w" );
    if (process->output_stream == NULL) 
    {
      complain( "Can't open pipe to %s: %s.", 
		process->name, strerror( errno ) );
    }
  }

  if (process->use_input)
  {
    /* Reset stdout for parent. */
    SetStdHandle( STD_OUTPUT_HANDLE, old_stdout );
    CloseHandle( child_write );

    /* Open stream to read from. */
    fd = _open_osfhandle( (long) parent_read_local, O_TEXT );
    process->input_stream = _fdopen( fd, "r" );
    if (process->input_stream == NULL) 
    {
      complain( "Can't open pipe from %s: %s.", 
		process->name, strerror( errno ) );
    }
  }
  return TRUE;
#endif
}

/*---------------------------------------------------------------------------*/

void 
stop_process( process_t *process )
/* Stop the Malaga PROCESS. */
{
  close_stream( &process->input_stream, NULL );
  close_stream( &process->output_stream, NULL );
  free_mem( &process->command_line );
  if (process->id != 0) 
  { 
#ifdef POSIX
    kill( (pid_t) process->id, SIGTERM );
    waitpid( (pid_t) process->id, NULL, 0 );
#endif
#ifdef WIN32
    TerminateProcess( (HANDLE) process->id, 0 ); 
    CloseHandle( (HANDLE) process->id );
#endif
    process->id = 0;
  }
}

/* End of file. =============================================================*/
