/*----------------------------------------------------------------------------
Name: hsh.c (Huyler Shell)
Author: Christopher Huyler
Date: February 12, 2002
Description:
A simple shell with the following features implimented:
 - simple program execution with input parameters
 - background processing using the '&' line terminator
 - input and output redirection '>','>>'(append), and '<'
 - piping from one program to another using '|'  (piping to 'more' works also)
 - setting and getting environment variables
   using set NAME=value and echo $NAME
 - changing directories using the 'cd' command
 - printing the current directory using 'pwd'
~---------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

// command structure
struct command_struct{
	int *argc;			// number of arguments
	char **argv;		// array of arguments
	char *path;			// string for path (if absolute)
};

int comm_pipe[2];		// communication between children and parent

#include "redirect.c"			// parses args for redirection symbols
#include "pipes.c"              // parses args for piping symbols

/* function prototypes */
void prompt();                  			// prints prompt to stdout
int shell_command(struct command_struct*);	// checks for shell specific commands
int set_path(struct command_struct*);   	/* sets path in command struct from
					   						   from first arg and returns type */


/* Main Function -------------------------------------------------------------
   main loop initializes shell, inputs commands and stores them in a command
   struct to be check by other functions
   calls fork() and wait() */
int main()
{	
	char lineBuf[256],				// input from stdin	
    	 **argv,					// ptr to curr argument
    	 *path,						// ptr to path
    	 *c;						// character pointer
 	int  *argc,						// number of arguments
 		 pid,						// process id from fork
 		 background,				// boolean to determine if bg proc
 		 path_type,					// boolean 0 if abs, 1 if rel
 		 i;							// iterator
	struct command_struct command;	// command structure
	
	// Shell initialization
	command.argv = malloc(30);			// allocate memory to argv array
	command.argc = (int*) malloc(1);    // allocate memory to argc ptr
 	prompt();                           // print prompt to screen
		
	// Main loop
	while(scanf("%s",lineBuf) != EOF &&
		  strcmp("exit",lineBuf) != 0 &&
		  strcmp("logout",lineBuf) != 0)
	{
		// initialize argument pointers
		argv = command.argv;
		argc = command.argc;
		*argc = 0;
		
		// set first argument pointer
		*argv = malloc(strlen(lineBuf+1));	
		strcpy(*argv++,lineBuf);
		*argc++;
		
		// read all remaining arguments
		while(getchar() != '\n' && scanf("%s",lineBuf) != EOF){
			*argv = (char*) malloc(strlen(lineBuf)+1);
			strcpy(*argv++,lineBuf);
			*argc+=1;
		}
		// set last argument to 0 (then go to previous arg)
		*argv-- = 0;
		
		// check last (nonzero) arg for background process
		c = *argv+strlen(*argv)-1;
		if(*c == '&') {			
			*c = '\0';      		// over write '&' with EOS
			if(strlen(*argv)==0)    // if length becomes zero
				*argv = 0; 			// set argument to zero
			background = 1;  		
		}
		else
			background = 0;
		
		// reset argv to front of arg list
		argv = command.argv;
		
		// set up communication pipe
		
		// command execution 		
		if(!shell_command(&command))		// do not fork for shell commands
		{	// not a shell command
			pid = fork();	
			
			if(pid == -1)
			{ // fork error
				fprintf(stderr, "Fork failure");
           		exit(EXIT_FAILURE);
    		}
			if(pid == 0)
			{ // this is the child
				set_redirect(&command);			// check for redirection
				set_pipes(&command);			// set any pipes
				path_type = set_path(&command);	// set path variable and type
				
				path = command.path; 			// set path pointer
				argv = command.argv;            // set argv pointer to front
	
	            if(path_type) { // relative path				
					if(execvp(path,argv) == -1)
						printf("hsh: %s: command not found\n",*argv);
				} 			
				else { // absolute path (don't search $PATH)				
					if(execv(path,argv) == -1)
						printf("hsh: %s: command not found\n",*argv);
				}
				exit(1);
		    }
		
			// this is the parent
			
			// check for background processing
			if(!background){
				while(wait(NULL) != pid); 	// wait for child
			}
			
		
		}// end not shell command
		
		// free argv memory
		for(i=0;i<*command.argc;i++)
			free(command.argv[i]);
			
		// end command, new prompt	
		//sleep(5);
		prompt();
	}
} // end main


/* prompt --------------------------------------------------------------------
   static function prints prompt to screen
   calls getenv sys command		*/
void prompt() {
	char prompt[40],        // shell prompt
		 *user,             // user name ptr
		 *hostName,      	// host name ptr
		 *curDir;           // full path ptr
	
	// get environment data	
    user = getenv("USER");
    hostName = getenv("HOSTNAME");
    curDir = getenv("PWD");
    curDir = strrchr(curDir,'/'); // get last name in current dir
    if(strlen(curDir) > 1)
    	curDir++;                 // cut off slash at begining

	// make prompt string
    strcpy(prompt,user);
    strcat(prompt,"@");
    strcat(prompt,hostName);
    strcat(prompt," ");
    strcat(prompt,curDir);

    printf("[%s]> ",prompt);
}// end prompt


/* set_path ------------------------------------------------------------------
   takes in a command structure and
   parses the first argument to determine the
   path of the command and whether the path
   is absolute or relative.  Saves path in
   command structure, returns path type. */
int set_path(struct command_struct *command){
	int path_type;
	// point argv to first argument
	char **argv;
	argv = command->argv;
	
	// determine path
	path_type = (strncmp("/",*argv,1)!=0  &&
	        strncmp("./",*argv,2)!=0 &&
	        strncmp("../",*argv,3)!=0)?1:0;
	command->path = (char*) malloc(strlen(*argv)+1);
	// copy to path
	strcpy(command->path,*argv);	
	// set first arg as prog name
	if (strrchr(*argv,'/') != NULL) {
		*argv = strrchr(*argv,'/');
		*argv++;
	}
	
	return path_type;
}// end set_path


/* shell_command--------------------------------------------------------------
   check first argument for a shell command
   reads char array of arguments and returns 1
   if command is executed, 0 if not.
   may call chdir(), getcwd() and putenv() sys commands */
int shell_command(struct command_struct *command)
{
	int  *argc,			// argc pointer
		 fd;            // file_descriptor
	char buf[256],		// input buffer for path
		 **argv,		// argument pointer
	     *cur_pwd,		// current directory in env
	     *new_pwd,  	// new directory to be stored in env
	     *echo,			// echo ptr
	     *c;            // character ptr
	
	argc = command->argc;
	argv = command->argv;
	
	// check for cd "change directory"
	if(strcmp("cd",*argv) == 0) {
		*argv++;					// set dir to second argument
		if (chdir(*argv) != 0)		// call to chdir sys command
			printf("hsh: %s: No such directory\n",*argv);
		else {
			cur_pwd = getcwd(buf,(size_t) 256);  // call to getcwd sys command
			new_pwd = malloc(strlen(cur_pwd)+6);
			strcpy(new_pwd,"PWD=");
			strcat(new_pwd,cur_pwd);
			putenv(new_pwd);
		}
		return 1;
    }// end cd	

    // check for echo
    if(strcmp("echo",*argv) == 0) {	
    	argv++;					// echo second argument
    	
    	// check for environment variable
    	if(**argv == '$'){			// '$' means print env variable
    		*++*argv;
    		echo = getenv(*argv);
    	}
    	else
    		echo = *argv;
    	
    	// check next arg for a redirection call		
    	argv++;
    	if(*argv != NULL && **argv == '>'){ // redirect '>'
    		if(*(*argv+1)=='\0')			// check 2nd char of string for eos
			{	 // take next arg as file name		
				shift_left(argv);   		// shift all args left one
			}
			else // cut off '>'
				(*argv)++;
			fd = open(*argv,O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR);
			write(fd,echo,strlen(echo));
			close(fd);
		}
		else
			printf("%s\n",echo);
			
    	return 1;
    }// end echo

    // check for set environment variable
    if(strcmp("set",*argv) == 0) {
    	argv++;
    	putenv(*argv);
    	return 1;
    } // end set

    // check for pwd - print working directory
    if(strcmp("pwd",*argv) == 0) {
    	printf("%s\n",getenv("PWD"));
    	return 1;
    } // end pwd
    	
    // not a shell_command
	return 0;
}// end shell_command


