static char rcsid[]="$Id: run.C,v 1.9 1995/09/28 03:19:18 wilmesj Exp $";
/* $Log: run.C,v $
 * Revision 1.9  1995/09/28  03:19:18  wilmesj
 * Major logic rewrite (thanks tim) and recommenting, fixes, pipes now work properly, etc.
 *
 * Revision 1.8  1995/09/28  02:06:28  wilmesj
 * Comments galore.
 *
 * Revision 1.7  1995/09/28  01:24:24  wilmesj
 * Total rewrite of the running code.. Integrated pipe and redirection support.
 * Unfortunately, pipes are still broken.
 *
 * Revision 1.6  1995/09/27  17:31:36  wilmesj
 * Redirection and Piping parse routines.  More commenting.
 *
 * Revision 1.5  1995/09/26  17:25:08  wilmesj
 * Extreme commenting, cleanup.
 *
 * Revision 1.4  1995/09/25  00:11:45  wilmesj
 * Merged bg and run into one function with a flag.
 *
 * Revision 1.3  1995/09/12  23:12:36  wilmesj
 * Switched kill sig 0 to wait with W_NOHANG for process checks
 *
 * Revision 1.2  1995/09/12  21:44:35  josh
 * *** empty log message ***
 *
 * Revision 1.1  1995/09/10  17:25:06  josh
 * Initial revision
 *
 */

#include "shell.h"
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>


//::::::::::::::::::::::::::::::::: Numwords :::::::::::::::::::::::::::::::://
/* Function:  numwords()
 * Purpose:   Count the number of words in a string, where a word is defined
 *            as a string of non-spaces.
 * Returns:   integer number of words
 * Uses:      str* (string.h)
 */
 
int numwords(char*& orig_string)
{
  char* string=orig_string;
  int result=0;
  if (strlen(string))
    {
      result=1;
      while (string)
	{
	  string=strchr(string, ' ');
	  if (string)
	    {
	      string++;
	      result++;
	    }
	}
    }
  return result;
}

//::::::::::::::::::::::::::::::: trim :::::::::::::::::::::::::::::::::::::://
/* Function:  trim
 * Purpose:   get rid of leading and trailing blanks, tabs (and LFs) from a
 *            normal string
 * Returns:   n/a
 * Uses:      n/a
 * Note:      This function is taken from the RPI SandBox library, found in
 *            /campus/rpi/sandbox/1.2/distrib/str/str.c
 *            It is very nice!
 */

void trim(char *s)
{
  char *from=s, *to=s;

  /* skip leading blanks */
  while(*from == ' ' || *from == '\t') from++;
  /* move chars till end of string */
  while(*from) *(to++) = *(from++);
  /* scan back for a non-blank */
  while(*(--to) == ' ' || *to == '\t' || *to == '\n');
  /* drop a null right after it */
  *(++to) = '\0';
}



//:::::::::::::::::::::::::::::::: char2argv :::::::::::::::::::::::::::::::://

/* Function:  char2argv()
 * Purpose:   Convert a flat character string to an argv style string vector.
 * Returns:   Pointer to char** vector.
 * Uses:      str*, c++ style new.
 */
 
char** char2argv (char* string)
{
  char** argv;
  int argc=numwords(string);
  argv=new char* [argc+1];

  if (argc==1)
    {
      argv[0]=new char [strlen(string)];
      strcpy(argv[0],string);
      argv[1]=NULL;
    }
  else
    {
      char* token=strtok(string," ");
      argv[0]=new char[strlen(token)];
      strcpy(argv[0],string);
      argv[1]=NULL;
      for (int i=1;i<argc;i++) {
	token=strtok(NULL," ");
	argv[i]=new char[strlen(token)];
	strcpy(argv[i],token);
	trim (argv[i]);
      }
      argv[i]=NULL;
    }
  return argv;
}


//:::::::::::::::::::::::::::::::: extractcmd ::::::::::::::::::::::::::::::://
/* Function:  extractcmd()
 * Purpose:   extract a token and its argument, marked by an initial delimeter
 *            and a set of final delimeters
 * Returns:   Pointer to char* of the token.
 * Uses:      str*, c++ style new
 * Note:      Remember that str is non-const, so passing const strings will
 *            segfault!  
 */

char* extractcmd (char delim, char* edelim, char* str)
{
  char* token_begin, *token_end=NULL, *token=NULL;
  token_begin=strchr(str,delim);

if (token_begin)
    {
      // Try to find valid tokens with each ending delimeter
      for (char* i=edelim;*i;i++)
	if ((! *i) || (token_end=strchr(token_begin+1,*i)))
	  break;

      if (!token_end)  // The token is the last on the line..
	{
	  token=strdup (token_begin+1);
	  *token_begin='\0';
	}   	
      else             // The token is in-line.
	{
	  token_end--;  
	  token=new char[token_end-token_begin+1];
	  *token_end='\0';  // Null terminate the token

	  // Copy it out.
	  memcpy(token,(token_begin+1),token_end-token_begin+1); 

	  // And paste the string back together around it.
	  *token_begin='\0'; // Null terminate the first fragment
	  strcat(str,(token_end+1));  // Copy the end fragment down.
	}

      trim(token);  // Get rid of leading and trailing whitespace.
      trim(str);
      return token;
    }
  else
    {
      return NULL; // No occurrance of the delimeter in this string.
    }
}


//::::::::::::::::::::::::::::::::::: run ::::::::::::::::::::::::::::::::::://
/* Function: run()  (Highlevel run)
 * Purpose:  Processes commandline from run command and calls the run()
 *           function.  Handles pipes and redirections
 * Returns:  runcmd's result
 * Uses:     runcmd() extractcmd()
 * Notes:    Gaz{inta,outa} is my tribute to dfinkel@wpi.wpi.edu
 */

#define DEBUG(x) fprintf(stderr,"%s\n", x); x

int run(char* commandline, unsigned char bg)
{
  char* gazinta=NULL,* gazouta=NULL, **pipeline;
  int numprocs=1,numpipes=0;
  int status=0;
  int inpipe,outpipe,ifd,ofd,z;
  pid_t pid;

  pipeline=new char*[255];  /* FIX- Maximum number of pipes is 255 because of
			       this. */
  
  gazinta=extractcmd('>',"|<>",commandline);
  gazouta=extractcmd('<',"|<>",commandline);
  
  while ( pipeline[numprocs]=extractcmd('|',"|<>",commandline) )
    numprocs++;

  trim (commandline);

  numpipes=numprocs-1;
  pipeline[0]=commandline;
  
  // Allocate the FD array.
  int fd[numpipes][2];
  
  // And Pipe them.
  for (int i=0;i<numpipes;i++)
    pipe(fd[i]);
  
  for (i=0;i<numprocs;i++)
    {
      pid=fork();
      
      if (pid==0)  // Child process (command)
	{
	  signal (SIGINT, SIG_DFL);  // Ensure that we can kill misbehaving children
	  
	  if (bg==BG) setpgrp();// If it's being backgrounded, we need to give
	                        // its own process groups so it won't catch
	                        // signals meant for the shell

       	  inpipe=i-1; outpipe=i;

      	  // Input Redirection //
	  if (gazouta && i == 0)
	    {
	      if ((ifd=open(gazouta,O_RDONLY)) == -1) {
		printf("Error: <%s> Invalid file for reading\n", gazinta);
		exit(1);
	      }
	      close(0);
	      dup(ifd);
	      close(ifd);
	    }

	  // Output Redirection //
	  if (gazinta && i == numprocs - 1)
	    {
	      if ((ofd=open(gazinta,O_CREAT | O_RDWR, 0644)) == -1) {
		printf("Error: <%s> Invalid file for writing\n", gazouta);
		exit(2);
	      }
	      close(1);
	      dup(ofd);
	      close(ofd);	      
	    }

	  // Stdin Piping //
	  if (i != 0)  // (Only if not the first one)
	    {
	      close(0);
	      dup2(fd[inpipe][0],0);  // Hook our input to the one before us.
	    }

	  // Stdout Piping //
	  if (i != numprocs - 1)  // (Only if not the last one)
	    {
	      close(1);
	      dup2(fd[outpipe][1],1);  // Hook our output into the output pipe.
	    }
	  
	  // Close the leftover file descriptors //
	  for (z=0;z<numpipes;z++)
	    {
	      close (fd[z][0]);
	      close (fd[z][1]);
	    }

	  // Now we exec the function...
	  trim (pipeline[i]);
	  char** argv=char2argv(pipeline[i]);  // Convert string to arg vector.
	  execvp (argv[0],argv);               // Run the command.
	  perror ("exec");
	  exit (1);  
	}
    }

  // Back in the parent shell, after ALL of the children have been spawned..

  // Wipe the old file descriptors //
  for (z=0;z<numpipes;z++)
    {
      close (fd[z][0]);    
      close (fd[z][1]);    
    }

  // Handle Backgrounded Processes //
  if (bg==BG)
    {
      addproc (pid,commandline);
      printf ("[%i]: %s &\n",pid,commandline);
    }
  else  // If not backgrounded, wait until it exits.
    if (waitpid (pid,&status,0) < 0) printf("PID %i exited.\n",pid);
  
  return status;
}



