/*==========================================================================*\
 |  This file is part of setiherder (http://www.sackheads.org/setiherder/)  |
 |  which was written by Brian L. Naylor <brian+seti@sackheads.org> and is  |
 |  under the GNU General Public License.  You should have recieved a copy  |
 |  of the GPL with this source code.  If not, we'd like to know about it.  |
\*--------------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <time.h>

#include "shtypes.h"
#include "shproc.h"
#include "shclients.h"
#include "shwin.h"
#include "shutil.h"
#include "setiview-pty.h"  /* Andy Sloane's simple pty code from setiview */

/* AIX (etc?) does not define this */
#ifndef WAIT_ANY
#define WAIT_ANY -1
#endif

#ifdef AIX
extern char *sys_errlist[];
#endif

sh_rc shproc_init(void)
{
  signal(SIGHUP,  shproc_signal_handler);
  signal(SIGUSR1, shproc_signal_handler);
  signal(SIGUSR2, shproc_signal_handler);
  signal(SIGCHLD, shproc_signal_handler);
  signal(SIGINT,  shproc_signal_handler);

  /*
   * Snag these next to in order to keep the shell from freezing
   * us when a child process decides to spew (or suck.)  I have
   * NO clue if this is how you're supposed to get around this, 
   * but fie on convention.  This works.
   */
  signal(SIGTTIN, shproc_signal_handler);

  return(0);
}

sh_rc shproc_client_spawn(sh_client_t *child)
{
  char           *seticmd, *next, *cmdtmp;
  char           *args[256];   /* plenty big enough! */
  int             i=1;
  int             oldtty;

  /* must copy seticmd into temp buffer; strtok is destructive */
  if((cmdtmp = (char *)malloc(strlen(child->seticmd)+1)) == NULL)
  {
    dbg(DBG_ERROR, "shproc_client_spawn() couldn't allocate %d bytes!\n",
        strlen(child->seticmd)+1);
    return SH_ERR_MEM;
  }
  strcpy(cmdtmp, child->seticmd);
 
  /*
   * Allocate new tty
   */
  pty_alloc(child);
  if(child->ptyfp == NULL) {
    dbg(DBG_ERROR, "Child not spawned: could't allocate pseudo-tty.\n");
    return SH_ERR_CHILD;
  }  
  
  child->pid = fork();
  
  /* child code */
  if(!(child->pid))
  {
    /*
     * switch to new process group 
     */
    if(setpgid(getpid(), getpid()) < 0)
    {
      dbg(DBG_ERROR, "Unable to set process group!\n");
      /* quit? */
    }
    
    /*
     * Disconnect from controlling terminal, ignoring SIGTTOU.
     */
    oldtty = open("/dev/tty", O_RDWR);
    if(oldtty) {
      void (*outhnd)() = signal(SIGTTOU, SIG_IGN);
      ioctl(oldtty, TIOCNOTTY, 0);
      close(oldtty);
      signal(SIGTTOU, outhnd);
    }
    
    dup2(fileno(child->ttyfp), fileno(stdout));
   
    /*
     * split seti command string out into individual strings for exec()
     */
    if(index(child->seticmd, ' ') != NULL)
    {
      seticmd = strtok(cmdtmp, " ");
      args[0] = seticmd;
      while((next = strtok(NULL, " ")) != NULL)
      { 
        args[i] = (char *)malloc(strlen(next)+1);
        strcpy(args[i], next);
        i++;
      }
      args[i] = NULL;
    }
    else {
      seticmd = cmdtmp;
      args[0] = seticmd;
      args[1] = NULL;
    }
   
    /* execute seti command */
    if(execvp(seticmd, args) < 0)
    {
      printf("execvp() failed with rc=%d\n", errno);
      sleep(3);
      _exit(SH_ERR_GENERIC);
    }
    /* should never reach this code in successful situations */
    return SH_ERR_GENERIC;
  }
 
  /* 
   * parent code 
   */
  
  sleep(1); /* let the child get started */
 
  return 0;
}

sh_rc shproc_client_terminate(sh_client_t *client)
{

  if(client->iotag != 0) 
    gdk_input_remove(client->iotag); 
  client->iotag = 0;

  if(client->pid <= 0) {
    dbg(DBG_WARN, "Oops, trying to kill a bit too much.  (pid==%d)\n"
                  "I'm quietly ignoring this.\n",
                  client->pid);
    return SH_SUCCESS;
  }
  
  if(kill(client->pid,SIGINT) < 0)
  {
   dbg(DBG_WARN, "Could not SIGINT pid %d (child #%d), lobbing a -9 at it..\n", 
       client->pid, client->index);
    /*
     * Is everybody dead?
     * Not everybody.  Look!  Six cannons, with gunners standing by.
     * Bows and arrows against the lightning.. they haven't seen the
     * heat ray yet.
     */
    if(kill(client->pid, SIGKILL) < 0)
    {
      dbg(DBG_WARN, "Eek.  -9 also failed, you're on your own.\n");
      pty_close(client);
      return SH_ERR_GENERIC;
    }
  }
  
  pty_close(client);
  return SH_SUCCESS;
}

void shproc_signal_handler(int sig)
{
  int pid, status, i;

  switch(sig)
  {
    case SIGCHLD: 
      dbg(DBG_DIAG, "Got SIGCHLD\n");
      while(1) 
      {
        pid = waitpid(WAIT_ANY, &status, WNOHANG);
        if(pid < 0)
        {
          if(errno != ECHILD) {
#ifndef NO_SYS_ERRLIST
            dbg(DBG_ERROR, "shproc_signal_handler waitpid = %d (%s)\n", errno,
                sys_errlist[errno]);
#else
            dbg(DBG_ERROR, "shproc_signal_handler waitpid = %d\n", errno);
#endif            
          }
          break;
        }
        if(pid == 0)
          break;
        /* find client entry with appropriate pid and reset 'pid' */
        for(i=0; i<=sh_client_idx; i++)
        {
          dbg(DBG_DIAG, "checking pid %d <> %d\n", sh_client_list[i].pid, pid);
          if(sh_client_list[i].pid == pid) {
            sh_client_list[i].pid  = SH_DEFAULT_DEAD_PID;
            if(sh_client_list[i].user_stopped == 0)
            {
              /* don't mark it as "dead" if the user stopped it. */
              sh_client_list[i].died = time(NULL);
            }

            if(sh_client_list[i].iotag != 0)
              gdk_input_remove(sh_client_list[i].iotag);
            sh_client_list[i].iotag = 0;

            dbg(DBG_DIAG, "Signalling death of child %d\n", pid);
            usleep(500);
            if(sh_client_list[i].user_stopped)  
              sh_client_list[i].state = SH_STATE_STOPPED;
            else
              sh_client_list[i].state = SH_STATE_CRASHED;
            shwin_status_change(i);
            i = sh_client_idx;
          }
        }
        dbg(DBG_INFO, "Child %u exited with rc=%d.\n", pid, status); 
      }
      break;
    case SIGHUP:
      dbg(DBG_ERROR, "Got HUP; not currently handled.\n"); break;
    case SIGUSR1:
      dbg(DBG_ERROR, "Got USR1; not currently handled.\n"); break;
    case SIGUSR2:
      dbg(DBG_ERROR, "Got USR2; not currently handled.\n"); break;
    case SIGINT:
      dbg(DBG_INFO, "Got SIGINT - terminating..\n");
      cleanup();
      exit(1);
      break;
    case SIGTTIN:
      dbg(DBG_WARN, "A child process is asking for input on its stdin."
                    "  That's no good.\nYou'll want to keep it from doing"
                    " that in the future.\n");
      break;
    default:
      dbg(DBG_ERROR, "Do-huh?  (shproc_signal_handler is confused!\n");
      break;
  };
}

gint shproc_restart_check(gpointer data)
{
  int i, rc;

  for(i=0; i<=sh_client_idx; i++) {
    if(  sh_client_list[i].died                  && 
        (sh_client_list[i].user_stopped == 0)    &&
       ((time(NULL) - sh_client_list[i].died) > sh_client_list[i].delay))
    {
      if((rc = sh_client_start(sh_client_get(i))) != SH_SUCCESS)
      {
        continue; /* try next client */
      }
      else 
      {
        sh_client_get(i)->died = 0;
      }
      sh_client_get(i)->state = SH_STATE_WORKING;
      shwin_status_change(i);
      dbg(DBG_INFO, "Restarted client %d (%s) after %d seconds.\n",
          i, sh_client_list[i].hostname, sh_client_list[i].delay);
    }
  }
  return TRUE;  /* required to remain in idle loops */
}

void cleanup()
{
  int i;
  sh_client_t *sc;
  
  for(i=0; i<=sh_client_idx; i++)
  {
    sc = sh_client_get(i);
    dbg(DBG_DIAG, "cleanup(): checking client %d..\n", i);
    if(sc->pid > 0)
    {
      dbg(DBG_DIAG, "cleanup(): client %d alive, terminating..\n", i);
      sh_client_stop(sc);
    }
    sh_client_free(sc->index);
  }
}


