/**************************************************************/
  /*                                                            */
  /*      UNIX 1 / WS 92/93       Gruppe  ux803                 */
  /*      7. Uebung - Aufgabe 5 - command.c                     */
  /*                                                            */
  /*      Vorname     Name        Matrikelnr.                   */
  /*     ---------   -------     -------------                  */
  /*      Torsten     Buller        117894                      */
  /*      Roman       Czyborra      127221                      */
  /*      Gerasimos   Paliatsaras   140956                      */
  /*                                                            */
  /**************************************************************/

#include <stdlib.h>    /* strchr, malloc etc. */
#include <fcntl.h>     /* O_RDONLY */
#include <sys/wait.h>  /* WNOHANG */
#include <errno.h>     /* ENOENT */
#include <termios.h>   /* TIOCSPGRP */
#include <sys/param.h> /* MAXPATHLEN */
#include "msh.h"       /* SLOTS, struct kommando */

 /* shell process table, hopefully initialized with zeros */
static struct proc { int pid, stopped; char *cmd; } job [SLOTS];

 /* useful global variables */
static waiting, fd [2]; extern shellpid;

static void quit ()              { waiting = 0; }
static void fatal (char * error) { perror (error); exit (0); }
static void giveterminal (pgrp)  { ioctl(0, TIOCSPGRP, & pgrp); }

/* find index to pid in job table
 * pid     == -1: any active job
 * findjob == -1: not found
 */
static findjob (pid)
{
  static slot;
  for (slot = 0; slot < SLOTS; ++ slot)
    if (pid == -1? job[slot].pid : job[slot].pid == pid) break;
  return (slot < SLOTS? slot: -1);
}

static char * stopsignal (jid)
{ switch (job[jid].stopped) {
    case 0:       return "running";
    case SIGSTOP: return "stopped-sig";
    case SIGTSTP: return "stopped-key";
    case SIGTTIN: return "stopped-inp";
    case SIGTTOU: return "stopped-out";
    default:      return "strange";
} }

/* process a wait () result */
static stopped (status, jid, echo)
{ 
  if (jid == -1) return 0;
  if (WIFSTOPPED (status)) return job[jid].stopped = WSTOPSIG (status);
  if (echo) printf ("[%d] done: %s\n", jid, job[jid].cmd);
  job[jid].pid = job[jid].stopped = 0;
  free (job[jid].cmd); return 0;
}

/* wait for started processes
 * jid:  mode:  when:
 *  -1     0     release zombies before prompt
 *  jid    0     foreground active, wait quietly
 *         1     builtin interruptible wait
 *  -1     1     for random first
 *  jid    1     for particular background process
 */
void do_wait (jid, mode)
{
  static pid, status, terminated, done;

  if (mode && findjob (-1) == -1) { printf ("no job active!\n"); return; }
  if (jid != -1) { pid = job[jid].pid;
       if (!pid) { printf ("job[%d] not active!\n", jid); return; }}

  /* wait quietly for foreground, then repossess terminal control */
  if (jid != -1 && !mode) {
    wait4 (pid, &status, WUNTRACED, 0);
    giveterminal (shellpid);
    if (stopped (status, jid, 0)) printf ("..suspended\n");
    return;
  }
  /* or wait for background processes with termination message */
  waiting = 1; if (mode) signal (SIGINT, & quit), signal (SIGQUIT, & quit);
  while (waiting && findjob (-1) != -1) 
    {
      done = findjob (terminated = wait3 (& status, WNOHANG | WUNTRACED, 0));
      terminated == 0  ? mode ? sleep (1) : quit () :
      terminated == -1 ? fatal ("wait3") : stopped (status, done, 1) ?
      printf  ("[%d] %s: %s\n", done, stopsignal(done), job[done].cmd) :
      mode ? jid == -1 || terminated == pid ? quit () : 0 : 0;
    }
  signal (SIGINT, SIG_IGN); signal (SIGQUIT, SIG_IGN);
}

void do_jobs ()
{
  static i; do_wait (-1, 0);
  for (i=0; i < SLOTS; i++) if (job[i].pid)
    printf ("[%d] %d %s: %s\n",i,job[i].pid, stopsignal(i), job[i].cmd);
}

static job2Bcont (jid)
{
  do_wait (-1, 0);
  jid == -1 ? (jid = findjob (-1)) == -1 ? printf ("no job active!\n") : 0
  : job[jid].pid ? 0 : printf ("job not active!\n", jid = -1);
  return jid;
}

static void contin (jid) { killpg (job[jid].pid, SIGCONT); 
                           job[jid].stopped = 0; }

void do_bg (jid)
{
  (jid = job2Bcont (jid)) 
  == -1? 0 : printf ("[%d] %s &\n",jid,job[jid].cmd), contin (jid);
}

void do_fg (jid)
{
  jid = job2Bcont (jid); if (jid == -1) return;
  printf ("...%s:\n",job[jid].cmd); giveterminal (job[jid].pid);
  contin (jid); do_wait (jid, 0);
}

static S, L;
static sumlen (char ** argv)
{ for (S = 0; *argv; ++ argv) S += strlen (*argv); return S;}
static char *copy, *write;
static void copyword (char *src) { while (*write++ = *src++); write[-1] = ' ';}
static void copyargv (char **argv) { while (*argv) copyword (*argv++);}
static char *copycommand (struct kommando * kp)
{
  L = kp->num_tok1 + kp->num_tok2 /*spaces*/ + sumlen (kp -> token_1);
  if (kp -> is_pipe) L += 2 + sumlen (kp -> token_2);
  if (kp -> inp_tok) L += 2 + strlen (kp -> inp_tok);
  if (kp -> out_tok) L += 2 + strlen (kp -> out_tok);
  copy = write = malloc (L);
 
  if (write) {
    copyargv (kp -> token_1);
    if (kp -> inp_tok) copyword ("<"), copyword (kp -> inp_tok);
    if (kp -> is_pipe) copyword ("|"), copyargv (kp -> token_2);
    if (kp -> out_tok) copyword (">"), copyword (kp -> out_tok);
    write[-1] = '\0';
  }
  return copy;
}

/* redirect stdin (0) or stdout (1) to fd[io] */
static void redirect (io)
{ close(io); dup (fd [io]) == io ? close(fd [io]) : fatal ("dup"); }

/* execute a command */
void do_command (struct kommando *kp)
{
  static pid, slot;
  static char chr, *path, *next, *name, **argv, longname [MAXPATHLEN];

  argv = kp->token_1; name = argv [0]; slot = findjob (0);
  if (slot == -1) printf("job table too full for %s ...\n", name); 

  else switch (pid = fork ())
    {
    case -1: fatal ("fork");
    case 0:
      /* execute command */
      pid = getpid (); if (! kp -> is_ampersand) giveterminal (pid);
      setpgrp (pid, pid);      signal(SIGTSTP, SIG_DFL);
      signal(SIGTTIN, SIG_DFL); signal(SIGTTOU, SIG_DFL); 
      signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); 
      if (kp->out_tok) { fd[1] = creat (kp->out_tok, 0666);
                         fd[1] == -1 ? fatal ("creat") : redirect (1);
                       }
      if (kp->inp_tok) { fd[0] = open (kp->inp_tok, O_RDONLY);
                         fd[0] == -1 ? fatal ("open") : redirect (0);
                       }
      if (kp->is_pipe) { pipe(fd) == -1? fatal ("pipe"): 0;
                         switch (fork ()) {
                           case -1: fatal ("fork");
                           case 0:  /* child writes into pipe */
                             redirect (1); close (fd[0]); break;
                           default: /* parent reads from pipe */
                             argv = kp->token_2; name = argv [0]; 
                             redirect (0); close (fd[1]);
                       } }
      if (!strchr (name, '/') && (path = getenv ("PATH")))
	/* parse $PATH and compose long name */
	for (chr = *path; chr;) {
	  /* copy path name */
	  for (next = longname; (chr = *path++) && chr != ':'; *next++ = chr);
	  /* append slash */
	  next > longname ? *next++ = '/': 0;
	  /* append command name and go */
	  strcpy (next, name); execv (longname, argv);
          /* continue when failed */
	}
      else execv (name, argv);
      errno == ENOENT ? printf ("command %s not found\n",name) : perror (name);
      exit (0);
 
    default:
      /* fill out birth certificate */
      job[slot].pid = pid; job[slot].cmd = copycommand (kp);
      kp -> is_ampersand ? printf("[%d] %d\n", slot, pid) : do_wait (slot, 0);
    }
}