/* This file is part of cardwords
   (c) 1998 1999 Tobias Peters
   see file COPYING for the copyright terms.
   
   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
*/

// cardwords_childprocess.cc

#include "cardwords_childprocess.hh"
#include <strstream.h>
#include <vector>
#include <string.h>
#include "cardwords_exceptions.hh"
#include "cardwords_gettext.hh"
#include <stdio.h>

// The map of child processes, needed by sigchldhandler:
map<pid_t, CardWords_ChildProcess *> CardWords_ChildProcess::process_map;

CardWords_ChildProcess::CardWords_ChildProcess (const string & progname)
  : pid (0),
    status (0),
    talkPipe(),
    listenPipe(),
    running(false)
{
  assert (progname != "");
#ifdef DEBUG
  // checking the pipes:
  assert (talkPipe.is_good());
  assert (listenPipe.is_good());
#endif

  add_argument(progname);
}

void CardWords_ChildProcess::start(bool with_pipes = true)
{
  assert (running == false);
  argv.push_back((const char *)0);
  
  // setting ther signal handler
  signal(SIGCHLD,&sigchldhandler);

  // forking:
  pid = fork();
#ifdef DEBUG
  cerr << "FORK RETURNED " << pid << endl;
#endif
  if (pid == -1) {
    running = false;
    talkPipe.close_talk(); talkPipe.close_listen();
    listenPipe.close_talk(); listenPipe.close_listen();
    throw CardWords_XLowResources (gettext("creating child process: "\
                                           "forking"));
  } else 
    if (pid == 0) {
      // in child code
      sleep(1);
      // before exiting, sleep to give the parent a chance to execute
      // process_map.insert() close unused ends of the pipes:
      talkPipe.close_talk();
      listenPipe.close_listen();

      // copy the pipes to cin and cout:
      if (with_pipes == true) {
        // This is a hack. I had this class for communication between
        // cardwords_server and cardwords_dicbot, with cardwords_dicbot cin
        // and cout piped to its parent.
        // Now I want to use it for the client to start a local server,
        // and I don't want diagnostic messages from the server get piped to
        // the client, they should appear on the terminal from where the
        // client started.
        // So if with_pipes is false, we get two unused fd's in the
        // client and in the server, I think we can live with them, but
        // for the future this is a FIXME: !
        if (dup2(talkPipe.get_listenFD(), 0) == -1) {
          sleep(1);
          throw CardWords_XLowResources (gettext("creating child process: "\
                                                 "in child code: "\
                                                 "copying file descriptor to "\
                                                 "cin"));
        }
        if (dup2(listenPipe.get_talkFD(), 1) == -1) {
          sleep(1);
          throw CardWords_XLowResources (gettext("creating child process: "\
                                                 "in child code: "\
                                                 "copying file descriptor to "\
                                                 "cout"));
        }
        // closing the now useless original FD's:
        talkPipe.close_listen();
        listenPipe.close_talk();
      }
      // now execing:
#ifdef DEBUG
      cerr << "EXECUTING:" << endl;
      for (int i = 0; argv[i] != 0; ++i) cerr << argv[i] << " ";
      cerr << endl;
#endif
      execvp (argv[0], (char *const *)(&argv[0]));
      // FIXME: What is the c++ cast for this?

      // execing failed if this is still executed:
      sleep(1);
      switch (errno) {
	// FIXME: Here we throw normal exceptions. Shouldn't the exceptions
	// indicate that we are currently in a child process? This is not known
	// somewhere upwards in the stack.

	//errors while accessing the file:
      case EACCES:
      case EPERM: 
      case ENOEXEC:
      case ENAMETOOLONG:
      case ENOENT:
      case ELOOP:
      case ENOTDIR:
	throw CardWords_XAccessingFile(gettext("creating child process: "\
                                               "in child code: "\
                                               "cannot execute file "),
                                       argv[0]);
	break;

      case E2BIG:
	// I think this could only show a bug in the programm.
	// The argument list must remain moderate.
	cerr << flush;
	perror(gettext("creating child process: "\
		       "in child code: "\
		       "too many arguments to execvp"));
	exit(1);
	break;

      case ENOMEM:
	throw CardWords_XLowResources(gettext("creating child process: "\
					    "in child code: "\
					    "calling execvp"));
	break;

      case EFAULT:
      default: assert(0);
      }
    }
    else {
      // pid != 0, in parent code
      process_map.insert(pair<pid_t, CardWords_ChildProcess*>(pid, this));

      // close unused ends of the pipes:
      talkPipe.close_listen();
      listenPipe.close_talk();

      running = true;
    }
}

int 
CardWords_ChildProcess::send_signal(int sig)
{
  if (!running) {
    // the child has already exited
    errno = EINVAL;
    return -1;
  }

  if (kill(pid, sig) == 0)
    return 0;

  switch (errno) {
  case EINVAL:
    // this is the caller's fault
    return -1;
    break;
  case ESRCH:
  case EPERM:
    // this would be a bug
    cerr << flush;
    perror(gettext("sending signal to child process"));
    exit(1);
    break;
  default:
    assert (0);
  }
  return -1;
}

// catch death of childs:
void CardWords_ChildProcess::sigchldhandler (int)
{
  signal(SIGCHLD,&sigchldhandler);
  int status;
  pid_t retval = waitpid(-1, &status, WNOHANG);

  if (retval == -1) {
    // all of these possibilitys would be bugs in the programm:
    cerr << gettext("bug: waitpid returned with ") << flush;
    switch (errno) {
    case ERESTART:
      // should not happen because of WNOHANG
      perror ("ERESTARTSYS");
      break;
    case ECHILD:
      // We had received a signal that a child exited, hadn't we?
      perror ("ECHILD");
      break;
    case EINVAL:
      // WNOHANG should not be invalid
      perror ("EINVAL");
      break;
    default: assert (0);
    }
    exit (1);
  }

  if (process_map.find(retval) == process_map.end()) {
    // A bug in the program:
    throw CardWords_XInternal_Error(gettext("waitpid returned an "   \
                                          "unregistered child pid"));
  }
  process_map[retval]->running = false;
  process_map[retval]->status = status;
  cerr << "EXITSTATUS OF CHILD pid " << retval << " = " << status << endl;
}

// add a command line argument:
void CardWords_ChildProcess::add_argument(const string & arg)
{
  assert(arg != "");
  strings.push_back(arg);
  argv.push_back(strings.back().c_str());
}
