// mike melamed
//
// mysh.c	- a simple unix shell
// 
// compile: gcc mysh.c -o mysh
//
// notes: 
// 	supports up to 100 processes to be in background
//	allows users to change directories
//	only supports one pipe (eg.: mysh# command1 | command2)
//	use 'logout', 'exit', or carriage return to exit shell
//	if user leaves the shell with processes in the background
//	the shell notifies the user (by e-mail [just kidding..])


#include <sys/signal.h>
#include <string.h>
#include "parser.c"

#define CMDLEN 80         // length of commands
#define PIDTABLESIZE 100  // allow total 100 processes in background

int pidtable[PIDTABLESIZE]; // table stores pids of backgrnd proccesses

void add(int pid)	  // add pid to pidtable[]
{
  int i;

  for(i=0;i<PIDTABLESIZE;i++)
  {
    if(pidtable[i]==0)
    {
      pidtable[i]=pid;
      return;
    }
  }
} 

void delete(int pid)	// deletes pid from pidtable[]
{
  int i;

  for(i=0;i<PIDTABLESIZE;i++)
  {
    if(pidtable[i]==pid)
    {
      pidtable[i]=0;
      break;
    }
  }
}

int deleteany(void)	// deletes an element from pidtable[]
{
  int i,pid;

  for(i=0;i<PIDTABLESIZE;i++)
  {
    if(pidtable[i]!=0)
    {
      pid=pidtable[i];    // save pid before it's erased
      pidtable[i]=0;      // erase pid from table
      return pid;
    }
  }
}


int isempty(void)	// checks if pidtable[] is empty (0==empty)
{
  int i;

  for(i=0;i<PIDTABLESIZE;i++)
    if(pidtable[i]!=0)
      return -1;

  return 0;
}

int execmd(char *file, char *argv[], int i)	// execute single comamnds
{
  FILE *fp;
  int pid=-1;

  if((pid=fork())==0)
  { 
    if(commands[i].outfile!=NULL)		// if command redirected >
    {
      if((fp=freopen(commands[i].outfile,"w",stdout))==NULL) // redirect outfile to stdout 
	perror("error: redirect outfile");
      if(execvp(file,argv)==-1)                 // execute command
      {
        perror("execvp error");
        exit(0);
      }
    }
    else
    {
      if(commands[i].infile!=NULL)		// if redirected <
      {
        if((fp=freopen(commands[i].infile,"r",stdin))==NULL)  // redirect infile to stdin
	   perror("error: redirect infile");
      }
      if(execvp(file,argv)==-1)			// execute command
      {
	perror("execvp error");
        exit(0);
      }
    }
  }
  if(background==YES)				// if command in background mode
  {
    add(pid);					// add it to pidtable[]
    printf("%d placed in background\n",pid);	
  }
  else
    wait(0);					// otherwise wait for it to end
}


int pipeline(void)				// process piped commands
{
  int f_des[2], pid1,pid2;
  FILE *fp;
  char ch;

  if(pipe(f_des)==-1)				// create a pipe
   {
     perror("pipe");
     exit(1);
   }
 
 if((pid1=fork())==0)
   {
     dup2(f_des[1],fileno(stdout));	        // redirect stdout to pipe in
     close(f_des[0]);
     close(f_des[1]); 
     if(commands[0].infile!=NULL)
     {
       if((fp=freopen(commands[0].infile,"r",stdin))==NULL)  // handle < redirection
	 perror("error: redirect infile");
     }
     if(execvp(commands[0].cmdname,commands[0].argv)==-1) // execute command1
     {
       perror("execvp error");
       exit(2);
     }
   }
  else if((pid2=fork())==0)
  {
    dup2(f_des[0],fileno(stdin));	 // use pipe out as stdin
    if(commands[1].outfile!=NULL)
      {						// handle > redirection
	printf("redirecting stdout to %s\n",commands[1].outfile);
	if((fp=freopen(commands[1].outfile,"w",stdout))==NULL)
	  perror("error: redirect");
      }
    close(f_des[0]);			// close both the output end
    close(f_des[1]);			// close input end
    if(execvp(commands[1].cmdname,commands[1].argv)==-1)  // execute command2
    {
      perror("execvp error");
      exit(3);
    }
  }

  close(f_des[0]);            // parent closes pipes so that 
  close(f_des[1]);           // processes terminate on time
  if(background==YES)
  {
    printf("%d in background\n%d in background\n",pid1,pid2);
    add(pid1);		    // add each process to the table
    add(pid2);
  }
  else
  {
    wait(0);		   // wait for each process to leave
    wait(0);		   // if not in background mode
  }
}

void sigint_handler(int incoming_signal)   // take care of ctrl+c
{
  int pid;

  fprintf(stderr,"SIGINT received, exiting.\n");
  // fflush(NULL);                      // flush buffered i/o
  while(isempty()!=0)
  {
    pid=deleteany();		     // kill all background tasks
    printf("killing %d\n",pid);
    kill(pid,SIGKILL);               // we are done buddy. time to go
  }
  exit(0);
}

void bgd(void)	// check pidtable[] periodically
{
  int i;
 
    for(i=0;i<PIDTABLESIZE;i++)
    {
     if(pidtable[i]!=0)
     { 
      if(kill(pidtable[i], 0)==0)   // check if process pidtable[i] exited
	{ }  // process still active.. don't display anything
      else
      {
        printf("%d exited\n",pidtable[i]);	// process exited. great
        if(isempty()!=0)
	{
	  delete(pidtable[i]);			// delete it from pidtable[]
	  wait(0);				// wait for formal exit
	}
      }
     }
    }
}

void main(void)
{
  char *cmd;
  int ncmds, pid;


  pid=getpid();			      // add shell's pid to table 
  add(pid);			      // in case of ctrl+c interrupt
 
  signal(SIGINT,sigint_handler);       // ctrl+c interrupt handler
  cmd=malloc(CMDLEN*sizeof(char)+1);   // allocate space for cmd

  do
  {
    bgd();			      // handle background processes
    printf("mysh# ");		      // display prompt
    gets(cmd);			      // get input from terminal
    bgd();
    if(cmd!=0)                         // check for command
      {
        ncmds=parsecmd(cmd);	       // parse command
	if(ncmds==1) {		       // if it's a 1-command
	    if(!strcmp(cmd,"cd"))      // check if it's change-dir
            {
		if(chdir(commands[0].argv[1])==-1)	// changedir
		   perror("cd:");
	    }
	    else
	     execmd(commands[0].cmdname,commands[0].argv,0); // execute command
        }
	else if(ncmds==2)		// if it's 2 commands
         {
	    if(commands[0].pipe==YES)
	      pipeline();		// execute pipe commands
	    else
	      printf("error:pipe expected\n");
	}
	else
	  printf("error in command #%d\n",ncmds);
      }
  } while(cmd[0]!=0 && strcmp(cmd,"logout") && strcmp(cmd,"exit"));
  if(isempty()!=0)
    printf("there are open jobs!\n");
  printf("logout\n");
}

