/*/////////
//
//  smsh.c ( adapted from Bruce's smsh4.c )
//
//      AUTHOR: Russell Lowke
//   CSCIE-215: Assignment Five
//
//       Usage: smsh [script file name]
//
//  [var]=[value]  Assigns an internal var to a value
//            set  prints a list of all saved values
//         export  exports a value
//   exit [value]  exits smsh with [value]
//     read [var]  reads into [var] an assignment input from the keyboard
//  cd [pathname]  changes the current working directory
//  if [list ;then list ... [;else list] ;fi]
//                 Executes the list following if and, if it returns a 0
//                 (zero)exit status, executes the list following the first
//                 then. Otherwise, the else list is executed.
//
// Description: Create a Unix shell that allows users to manage processes
//              such as running programs and controlling input.
//              The shell has its own scripting language with variables and
//              if statements.
//
//      Method: Main reads from file or stdin, taking input and sending it to
//              splitline (splitline.c) to have variables substituted and to
//              be parsed into arguments.  varlib (varlib.c) is used to keep 
//              track of all variables added by the user, and to 
//              get unix environment vars.
//              Once arguments have been isolated they are sent to
//              handleCommand to be processed by an appropriate function,
//              in most cases a command is external, in which case it is
//              handled by execute(), who forks and exec's the command.
//              handleCommand also deals with IF related statements, keeping
//              careful track of their progress and only allowing the
//              correct then/else blocks to be processed.
//
//              smsh.c methods:
//
//                main - main checks for a script file for input, otherwise
//                       uses stdin input. the loop reads input line by line.  
//                       Each line has variables ($VAR)subsititued, then
//                       is split into its individual arguments.
//                       Finally, the arg count and all args are pased into
//                       handleCommand() which returns a state. If this state
//                       is != NORMAL on exit then there is a
//                       scripting bug ("no fi")
//               setup - All initialization goes here.
//       handleCommand - Works out where to pass the args to.
//                       Internal commands are set to their own specific
//                       function, external commands are delt with by execute().
//                       More importantly, handleCommand deals with the 
//                       possibility of if statements, by use of an 'ifState'
//                       var.  This var keeps track of the progress of the
//                       if statement.  HandleCommand returns the ifState,
//                       which main needs, in case it reaches EOF without
//                       a concludeing fi.
//               fatal - Fatal error message to stderr then exit (never returns)
//        gracefulExit - cleanup smsh before exiting
//    get_next_command - read next line from input_stream.  Return FALSE on EOF
//             execute - execute commands in argv.
//                       Uses fork to make a child duplicate to exe
//                       if sucessful exe will exit itself. information of
//                       child is held in child_info
//             readCmd - Assigns internal var [varname] equal to keyboard input
//         getKeyInput - Reads input from the keyboard, placing it in buffer
//               cdCmd - cd command, calls chdir with a path.
//              report - Report of important vars in info from child
//                       Good for debugging, this function is not used by smsh
//
//              see top of splitline.c for its methods.
//
//
//       Notes: VLtable2environ in execute calls malloc, but memory is
//              free()d with exec
*/


        
#include	"smsh.h"

extern char **environ;

/*
//  main
//
//     main checks for a script file for input, otherwise uses stdin input.
//     the loop reads input line by line.  Each line has variables ($VAR)
//     subsititued, then is split into its individual arguments
//     Finally, the arg count and all args are pased into handleCommand()
//     handleCommand returns a state.  If this state is != NORMAL on exit
//     then there is a scripting bug ("no fi")
//
//     args:  ac         --  argument count
//            **arg      --  pointer to arguments
*/
int main(int ac, char **arg)
{
    char    *argv[MAXARG + 1], 
             orgCmdline[MAXCMDLEN],                 /* original command line  */
             modCmdline[MAXCMDLEN];                 /* modified command line  */            
    int	     argc, cmdState;
    FILE    *inputVia = stdin;
    
    setup();                                        /* initalize smsh         */
    
    if (ac > 1) {                                   /* check for a filename   */
        if ( ! (inputVia = fopen(arg[1],"r")))      /* open file in arg[1],   */
            perror( arg[1] );                       /* couldn't open          */
    }
    
    /*
    // main loop
    */
    while ( get_next_command( DFL_PROMPT,
                              orgCmdline,
                              inputVia )) {         /* get input              */
        
        if ( ! substituteVars(orgCmdline,
                              modCmdline) )         /* substitute vars        */
            continue;                               /* bug... get next line   */
        
        if ( ! splitline(modCmdline,
                         &argc,
                          argv,
                          MAXARG) )                 /* split input            */
	    continue;                               /* bug... get next line   */
        
	cmdState = handleCommand( argc, argv );     /* deal with command      */
    }
    
    if (cmdState != NORMAL)                         /* error. no fi           */
        fatal("unexpected error: IF structure without FI");
    
    exit(0);

}

/*
//  setup
//
//     All initialization goes here.
*/
void setup()
{
    /* block ctrl c and ctrl backslash */
    signal(SIGINT,  SIG_IGN); // ctr c
    signal(SIGQUIT, SIG_IGN); // ctr backslash

    /* init the variable table */
    if ( VLenviron2table(environ) == 0 )
        exit(1);
}


/*
//  handleCommand
//
//     Works out where to pass the args to.
//     Internal commands are set to their own specific function,
//     external commands are delt with by execute().
//     More importantly, handleCommand deals with the possibility of if
//     statements, by use of an 'ifState' var.  This var keeps track of the
//     progress of the if statement.  HandleCommand returns the ifState,
//     which main needs, in case it reaches EOF without a concludeing fi.
//
//     args:  argc        --  argument count
//            *argv[]     --  list of args
*/
int handleCommand(int argc, char *argv[])
{
    char *cp;
    int static ifState = NORMAL;                    /* ifstates are:
                         NORMAL, NEEDTHEN, NEEDELSE, GOTTHEN,GOTELSE,NEEDFI   */

    if ( (cp=strchr(argv[0], '=')) != NULL ){       /* var=val assignment     */
        *cp = '\0';
        VLstore(argv[0], cp+1);                     /* store var              */
    }
    else if (! strcmp(argv[0], "set"))              /* set command            */
        VLset();
    else if (! strcmp(argv[0],"export") && argc == 2) /* export cmd           */
        VLexport(argv[1]);
    else if (! strcmp(argv[0],"exit")){             /* exit cmd?              */
        if (argc == 2)
            gracefulExit(atoi(argv[1]));            /* exit clean with arg    */
        else
            gracefulExit(1);                        /* exit clean             */
    }
    else if (! strcmp(argv[0], "read")  && argc == 2) /* read cmd?            */
        readCmd(argv[1]);
    else if (! strcmp(argv[0], "cd")    && argc == 2) /* cd cmd               */
        cdCmd(argv[1]);
    else if ( strcmp(argv[0], "if")     == 0 ){
        if ( execute( ++argv ) == 0 )               /* exec cmd after 'if'    */
             ifState = NEEDTHEN;                    /*...looking for then     */
        else
             ifState = NEEDELSE;                    /*...looking for else     */
    }
    else if ( strcmp(argv[0], "then")   == 0 ) {    /* got 'then'             */
        if ( ifState == NEEDTHEN )
             ifState =  GOTTHEN;
        else
            fatal("unexpected error: THEN without an IF");
    }
    else if ( strcmp(argv[0], "else")   == 0 ) {    /* got 'else'             */
        if ( ifState == NEEDELSE )
             ifState =  GOTELSE;
        else if ( ifState == GOTTHEN )
             ifState =  NEEDFI;                     /* need 'fi'              */
    }
    else if ( strcmp(argv[0], "fi")     == 0 ) {    /* got 'fi'               */
        if ( ifState == NEEDFI )
             ifState = NORMAL;
        else if ( ifState == GOTELSE )
             ifState = NORMAL;
        else                                        /* ...FI too early        */
            fatal("unexpected error: bad IF structure"); 
    }
    else {
        if ( ifState == NEEDTHEN)                   /* ...no THEN             */
            fatal("unexpected error: IF structure without THEN");
        if ( ifState == NORMAL  || 
             ifState == GOTTHEN || 
             ifState == GOTELSE )
        execute( argv );                            /* call external cmd      */
    }

    return ifState;                                 /* return the ifstate     */
}


/*
//  fatal
//
//     Fatal error message to stderr then exit (never returns)
//
//     args:  errStr      --  error message to display
*/
void fatal(char *errStr)
{
    fprintf(stderr, "%s\n", errStr);
    gracefulExit(1);
}


/*
//  gracefulExit
//
//     cleanup smsh before exiting
//
//     args:  exitVal      --  value to exit with
*/
void gracefulExit(int exitVal)
{
    signal(SIGINT, SIG_DFL);                      /* fix ctrl c               */
    signal(SIGQUIT, SIG_DFL);                     /* fix ctrl backslash       */
    
    exit(exitVal);                                /* die                      */
}


/*
//  get_next_command
//
//     read next line from input_stream.  Return FALSE on EOF
//
//     args: *prompt         --  symbol to use for prompt
//           *buffer         --  string buffer
//           *input_stream   --  source of the input (a file or stdin)
*/
int get_next_command( char *prompt, char *buffer, FILE *input_stream )
{
    if (input_stream == stdin)
        printf("%s", prompt);                      /* prompt user             */

    if ( fgets(buffer, MAXCMDLEN, input_stream) )  /* get line                */
    {
        buffer[strlen(buffer) - 1] = '\0';         /* remove nl               */
        return TRUE;                               /* say ok                  */
    }
    return FALSE;                                  /* no more                 */
}


/*
//  execute
//
//     execute commands in argv
//     fork makes a child duplicate to exe
//     if sucessful exe will exit itself.
//     information of child is held in child_info
//
//     args: *argv[]    --  pointer to arguments for exe
*/
int execute(char *argv[])
{

    int	pid = fork(),                              /* fork                    */
        child_info,
        myReturn = FALSE;
                                  
    if ( pid == -1 )
        perror("fork");
    else if ( pid == 0 ){                          /* child(new) process      */

        signal(SIGINT, SIG_DFL);                   /* set ctl chars, etc      */
        signal(SIGQUIT, SIG_DFL);

        /* "The header comment for VLtable2environ() in varlib.c says you
        // need to free this tbale to prevent memory leaks. That comment only
        // holds true if you call the functon within the parent process. If
        // you call the function only in the child,
        // the memory is free()d with exec." */
        environ = VLtable2environ();
        
        execvp(argv[0], argv);                     /* execute the args, execvp
                                                      auto exits if sucessful */

        perror("cannot execute command");          /* exe did not work        */
        gracefulExit(1);                           /* die                     */
    }
    else {                                         /* parent(old) process     */
        if ( wait( &child_info) == -1 )            /* wait for child to exit  */
	    perror("wait");
        else
            myReturn = (child_info >> 8);          /* return child's exit code*/
    }

    return myReturn;
}



/*
//  readCmd
//
//     Assigns internal var [varname] equal to keyboard input
//
//            *varname         --  name of variable being assigned
*/
void readCmd(char *varname)
{
    char myInput[MAXCMDLEN];                        /* string to hold input   */

    if (validVarName (varname)) {                   /* ensure legal var name  */
        getKeyInput(myInput);                       /* read input             */
        VLstore(varname, myInput);                  /* store var              */
    }
    else
        printf("%s: is not an identifier\n", varname);
}

/*
//  getKeyInput
//
//     Reads input from the keyboard, placing it in buffer
//
//            *buffer         --  buffer for string
*/
int getKeyInput(char *buffer)
{
    if ( fgets(buffer, MAXCMDLEN, stdin) )         /* get line	              */
    {
        buffer[strlen(buffer) - 1] = '\0';         /* remove nl	              */
        return TRUE;                               /* say ok	              */
    }
    return FALSE;                                  /* no more                 */
}


/*
//  cdCmd
//
//     cd command,  calls chdir with a path.
//
//     args: *pathname         --  path to switch directory to.
*/
void cdCmd(char *pathname)
{
    if ( chdir(pathname) == -1 )                   /* open file in arg[1],    */
        perror( pathname );                        /* couldn't open           */
}


/*
//  report
//
//     Report of important vars in info from child
//     Good for debugging, this function is not used by smsh
//
//     args:  info         --  information created by wait()
*/
void report( int info )
{
	int	from_exit, core_flag, signal_num;

	signal_num = info & 0x7F ;
	core_flag  = (info >> 7 ) & 1;
	from_exit  = (info >> 8) ;

	if ( signal_num != 0 ){
		printf("\n[child died from signal %d]", signal_num);
		if ( core_flag ) printf(" (core dumped)" );
	}
	else
		printf("\n[child exited with code %d]", from_exit);
	putchar('\n');
}

























