/**********************************************************************
 *
 * os_process_unix.cpp -- Unix version of osprocess.  See os_process.h
 *                 for more details
 *
 * Copyright (C) 2010  The New Zealand Digital Library Project
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

#ifndef __WIN32__ // i.e Unix

#if defined(GSDL_USE_OBJECTSPACE)
#  include <ospace/std/iostream>
#elif defined(GSDL_USE_IOS_H)
#  include <iostream.h>
#else
#  include <iostream>
using namespace std;
#endif

#include <unistd.h>
#include <sys/wait.h>

#include "os_process_unix.h"

osprocessunix::osprocessunix(OSProcessPipeMode mode,
			     char* prog_name, char* argv[], char* envp[])
  : child_stdout_read_(-1), child_stdin_write_(-1),
    osprocess(mode,prog_name,argv,envp)
{
  
  // Create a pipe for the child process's STDOUT. 
  int stdout_pipe_fd[2];

  if (pipe(stdout_pipe_fd)!=0) {
    cerr << "osprocessunix::osprocessunix(): Failed to create stdout pipe for child process" << endl; 
    return;
  }

  child_stdout_read_ = stdout_pipe_fd[READ_PIPE_INDEX];
  int child_stdout_write = stdout_pipe_fd[WRITE_PIPE_INDEX];
  

  // Create a pipe for the child process's STDIN. 
  int stdin_pipe_fd[2];

  if (pipe(stdin_pipe_fd)!=0) {
    cerr << "osprocessunix::osprocessunix(): Failed to create stdin pipe for child process" << endl; 
    return;
  }

  int child_stdin_read = stdin_pipe_fd[READ_PIPE_INDEX];
  child_stdin_write_ = stdin_pipe_fd[WRITE_PIPE_INDEX];

  
  pid_ = fork();
  if (pid_ < 0) {
    cerr << "osprocessunix::osprocessunix(): Failed to create child process" << endl; 
    return;
  }

  if (pid_ == 0) {
    // Child process

    // Sort out input pipe
    //   child has no buisness accessing write end of input pipe => close
    ::close(child_stdin_write_); 
    child_stdin_write_=-1;

    if ((mode == uniWrite) || (mode == biReadWrite)) {
      //   wire up child's stdin read so its input comes from the parent's pipe
      dup2(child_stdin_read, FD_STDIN); 
    }
    else {
      // Parent is doing uniRead, which means we're not interested
      // in the child reading any input from the parent
      // => child input remains coming from stdin
      ::close(child_stdin_read);
    }

    // Sort out output pipe
    //   child has no buisness accessing read end of output pipe => close
    ::close(child_stdout_read_);
    child_stdout_read_=-1;

    if ((mode == uniRead) || (mode == biReadWrite)) {
      //   wire up child's stdout write so it is send down the pipe to the parent
      dup2(child_stdout_write, FD_STDOUT); 
    }
    else {
      // Parent is doing uniWrite, which means we're not interested
      // in any output produced by the child process
      // => child output remains going to stdout
      ::close(child_stdout_write);
    }

    
    // Used to be execve(), but this would appear not to search path for
    // 'prog_name'. The alternative execvp() does search PATH and so is
    // more compatible with how CreateProcess works for Windows

    // Need to connect envp with externally defined '**environ' ??

    execvp(prog_name,argv);
    
  }
  else {
    // Parent process

    // Sort out input pipe
    //    parent has no buisness accessing read end of input pipe => close
    ::close(child_stdin_read);

    // Sort out output pipe
    //    parent has no buisness accessing write end of output pipe => close
    ::close(child_stdout_write);


    switch(mode) 
    {
      case uniRead:
	::close(child_stdin_write_);
	child_stdin_write_ = -1;
	break;
      case uniWrite:
	::close(child_stdout_read_);
	child_stdout_read_ = -1;
	break;
      case biReadWrite:
	// nothing to do
	// the pipes are set up just the way we want them
	break;
    }
  }
}

osprocessunix::~osprocessunix()
{
  // close any file handles that are still open
  close();
}


int osprocessunix::write(char* buffer, const int buffer_len)
{

  int actual_write_len = ::write(child_stdin_write_, buffer, buffer_len);
  
  if (actual_write_len<0) {
    cerr << "osproessunix::write() Error: failed to write data" << endl;
  }

  return actual_write_len;
}

int osprocessunix::read(char* buffer, const int buffer_len)
{
  int actual_read_len = ::read(child_stdout_read_, buffer, buffer_len);

  if (actual_read_len<0) {
    cerr << "osproessunix::read() Error: failed to read data" << endl;
  }

  return actual_read_len;
}


void osprocessunix::wait()
{
  int child_status;
  //cerr << "**** waiting ..." << endl;
  ::waitpid(pid_,&child_status,0);

  //cerr << "*** exit status = " << WEXITSTATUS(child_status) << endl;

  //cerr << "**** done" << endl;
}


bool osprocessunix::close_write_pipe(OSProcessWarnStatus warn_status)
{
  int write_close_rv = 0;

  if (child_stdin_write_ != -1) {
    write_close_rv = ::close(child_stdin_write_);
    child_stdin_write_ = -1;
  }
  else if (warn_status == withWarning) {
    cerr << "osprocessunix::close_write_pipe(): Warning - Tried to close already closed pipe" << endl;
  }

  return (write_close_rv==0);
}


bool osprocessunix::close_read_pipe(OSProcessWarnStatus warn_status)
{
  int read_close_rv = 0;

  if (child_stdout_read_ != -1) {
    read_close_rv = ::close(child_stdout_read_);
    child_stdout_read_ = -1;
  }
  else if (warn_status == withWarning) {
    cerr << "osprocessunix::close_read_pipe(): Warning - Tried to close already closed pipe" << endl;
  }

  return (read_close_rv==0);
}






#endif
