#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/times.h>
#include <time.h>
#include <unistd.h>
/* #include <limits.h> */

typedef struct jobinfo {
  int pid; /* pid of job */
  char *info; /* info describing job */
  struct jobinfo *next; /* next job */
} jl_t;

static int fg_pid;
static int fg_stat;
static jl_t *joblist = NULL;

int list_insert(int pid,char *info) {
  jl_t *element;
  if ((element=(jl_t*)malloc(sizeof(jl_t)))==NULL) return -1;
  if ((element->info=strdup(info))==NULL) {
    free(element); 
    return -1;
  }
  element->pid = pid;
  element->next = joblist;
  joblist = element;

  return 0;
}

void list_free(jl_t* element) {
  free(element->info);
  free(element);
}


/*
 * Installiert einen Signalhandler
 */
void install_sighandler(int sig, void (*handler)(int) ) {
  struct sigaction act;
  memset(&act,0,sizeof(struct sigaction));
  act.sa_handler = handler;
  
  if (sigaction(sig,&act,NULL)) {
    perror("install signal handler");
    exit(EXIT_FAILURE);
  }
}

/*
 * Blockiert das Signal "signo" und speichert die alte
 * Signalmaske in "sigmask".
 */
void block_signal(sigset_t *sigmask, int signo) {
  sigset_t sigset;
  if (sigemptyset(&sigset) || sigaddset(&sigset,signo)) {
    perror("block_signal");
    exit(EXIT_FAILURE);
  }
  if (sigprocmask(SIG_BLOCK,&sigset,sigmask)) {
    perror("block_signal");
    exit(EXIT_FAILURE);
  }
}

/*
 * Stellt die in sigmask gespeicherte Signalmaske wiederher
 */
void restore_signals(sigset_t *sigmask) {
  if (sigprocmask(SIG_SETMASK,sigmask,NULL)) {
    perror("block_all_signals");
    exit(EXIT_FAILURE);
  }
}

/*
 * Bearbeitet SIGINT-Signale
 */
void sigint_handler(int signo) {
  jl_t *ptr;
  sigset_t sigmask;

  block_signal(&sigmask,SIGCHLD);

  for (ptr=joblist;ptr!=NULL;ptr=ptr->next) 
    if (ptr->pid>0) kill(ptr->pid,SIGKILL);

  restore_signals(&sigmask);
}

/*
 * Bearbeitet SIGCHLD-Signale
 */
void sigchild_handler(int signo) {
  int pid,status;
  jl_t* ptr;
  jl_t** prev;
  FILE* fd;
  sigset_t sigmask;
  struct tms t1;
  struct tms t2;
  float ut,st;

  if ((fd=fopen("tsh.log","a"))==NULL) {
    perror("open tsh.log");
    return;
  }

  block_signal(&sigmask,SIGINT);
  
  if (times(&t1)==(clock_t)-1) perror("times");

  while ((pid=waitpid(-1,&status,WNOHANG))>0) {

    if (times(&t2)==(clock_t)-1) perror("times");

    ut = ((float)(t2.tms_cutime-t1.tms_cutime))/CLK_TCK;
    st = ((float)(t2.tms_cstime-t1.tms_cstime))/CLK_TCK;

    if (pid==fg_pid) {
      fg_pid=0;
      fg_stat=status;
    }

    prev=&joblist;
    for (ptr=joblist;ptr!=NULL;ptr=ptr->next) {
      if (pid==ptr->pid) {
	fprintf(fd,"pid:%5d user:%6.2f sys:%6.2f status:%3d %s\n",
                pid, ut, st, WEXITSTATUS(status), ptr->info);
        *prev = ptr->next; 
        list_free(ptr);
	break;
      } else {
        prev=&(ptr->next);
      }
    }

    t1=t2;
  }

  restore_signals(&sigmask);

  fclose(fd);
}

/*
 * Zerlegt die Kommandozeile an Leerzeichen und Tabs und legt
 * die Parameter in argv ab.
 * 
 * buffer wird dabei zerstoert!
 * Liefert die Anzahl der Parameter zurueck.
 */
int parseCommandLine(char *buffer, char **argv) {
  int cnt=0;
  
  /* buffer ist leer -> kein Argument enthalten */
  if(! *buffer) return 0;

  argv[cnt++] = strtok(buffer, " \t");
  while((argv[cnt] = strtok(NULL, " \t")) != NULL) cnt++;
  
  /* argv mit NULL Zeiger fuer exec abschliessen */
  argv[cnt] = NULL;

  return cnt;
}

/*
 * Erzeugt einen neuen Prozess, fuehrt das angegebene Programm aus und
 * schreibt den Exitstatus (falls Childprozess tatsaechlich terminierte)
 * auf die Standardausgabe
 */
void execute_fg(char *commandLine, int argc, char **argv) {
  sigset_t sigmask;

  block_signal(&sigmask,SIGCHLD);
  block_signal(NULL,SIGINT);
  
  switch(fg_pid=fork()) {
  case -1 : perror("fork failed");return;
  case 0  :
    restore_signals(&sigmask);
    execvp(argv[0], argv);
    perror(argv[0]);
    exit(EXIT_FAILURE);
  default:
    if (list_insert(fg_pid,commandLine)) perror("insert job");      
  }
  
  while (fg_pid!=0) sigsuspend(&sigmask); 
  printf(" %s Exitstatus = %d\n",commandLine,WEXITSTATUS(fg_stat));

  restore_signals(&sigmask);
}

/*
 * Erzeugt einen neuen Prozess, fuehrt das angegebene Programm im 
 * Hintergrund aus.
 */
void execute_bg(char *commandLine, int argc, char **argv) {
  pid_t pid;
  sigset_t sigmask;
  
  block_signal(&sigmask,SIGCHLD);
  block_signal(NULL,SIGINT); 
  
  switch(pid=fork()) {
  case -1 : perror("fork failed");return;
  case 0  :
    restore_signals(&sigmask);
    block_signal(NULL,SIGINT);
    execvp(argv[0], argv);
    perror(argv[0]);
    exit(EXIT_FAILURE);
  default:
    if (list_insert(pid,commandLine)) perror("insert job");      
  }

  restore_signals(&sigmask);
}

int main(int argc, char *argv[]) {
  int maxArgsLength,arglen;
  char *lineBuffer, *commandLine;
  char **argVector;

  if ((maxArgsLength=sysconf(_SC_ARG_MAX))==-1) {
    perror("sysconf for ARG_MAX");
    return EXIT_FAILURE;
  }

  /* Interrupt-Handler aufsetzen */

  install_sighandler(SIGINT,sigint_handler);
  install_sighandler(SIGCHLD,sigchild_handler);
  
  /* Speicher fuer Kommandozeile anfordern */
  if(!(lineBuffer = (char *)malloc(maxArgsLength+1))) {
    perror("malloc for lineBuffer");
    return EXIT_FAILURE;
  }
  if(!(commandLine = (char *)malloc(maxArgsLength+1))) {
    perror("malloc for commandLine");
    return EXIT_FAILURE;
  }

  /* Speicher fuer Kommando und Argumentvektor anfordern */
  if(!(argVector = (char **)malloc((maxArgsLength/2)*sizeof(char *)))) {
    perror("malloc for argVector");
    return EXIT_FAILURE;
  }

  while(1) {
    printf("tsh> ");
    while (!fgets(commandLine, maxArgsLength, stdin)) {
      if(feof(stdin)) 
	exit(EXIT_SUCCESS);
      if (errno==EINTR) continue;
      perror("fgets");
      continue;
    }

    if(commandLine[strlen(commandLine)-1] == '\n')
      commandLine[strlen(commandLine)-1] = '\0';
    
    strcpy(lineBuffer, commandLine);
    
    if ((arglen=parseCommandLine(lineBuffer, argVector)) < 1) 
      continue;

    if (strncmp(argVector[0],"jobs",4)==0) {
	sigset_t sigmask;
        jl_t *ptr;

	block_signal(&sigmask,SIGCHLD);

        for (ptr=joblist;ptr!=NULL;ptr=ptr->next) {
	  printf("%d ",ptr->pid);
	  if (ptr->info!=NULL) printf("%s",ptr->info);
	  printf("\n");
	}

	restore_signals(&sigmask);
    } else if (strncmp(argVector[0],"exit",4)==0) {
      return 0;
    } else if (strncmp(argVector[arglen-1],"&",1)==0) {
      if (arglen<2) continue;
      argVector[arglen-1]=NULL;
      execute_bg(commandLine, arglen, argVector);
    } else {    
      execute_fg(commandLine, arglen, argVector);
    }
  }
}
