/*   miniShell.c command shell with hashing
**
**   CSC209S Assignment #2 - Feb 29, 2000
**   Craig Armstrong
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>

#define MAXLINE 256
#define MAXPATH 256
#define MAXARGS 25
#define HASHSIZE 101

struct hashList {
    struct hashList *next;
    char *szCommand;
    char *szFullPath;
};

static struct hashList *hashTable[HASHSIZE];

/* hash:  form hash value for a command name */
unsigned hash(char *szCommand) {
    int i=0, sum=0;
    while(szCommand[i]) sum += szCommand[i++];
    return sum % HASHSIZE;
}

/* hashFind:  find command in hash table */
struct hashList *hashFind(char *szCommand) {
    struct hashList *hp;
    for(hp=hashTable[hash(szCommand)]; hp !=NULL; hp=hp->next)       
        if(strcmp(szCommand, hp->szCommand)==0) return hp;
    return NULL;    /* not found */
}

/* hashInsert:  insert command and path in hash table */
struct hashList *hashInsert(char *szCommand, char *szFullPath) {
    struct hashList *hp;
    unsigned hashVal;
    if((hp=hashFind(szCommand))==NULL) {  /* not found */
        hp=(struct hashList *)malloc(sizeof(*hp));
        if(hp==NULL || (hp->szCommand = strdup(szCommand))==NULL
           || (hp->szFullPath = strdup(szFullPath))==NULL)
               return NULL;    /* Couldn't add the hash entry */
        hashVal=hash(szCommand);
        hp->next = hashTable[hashVal];
        hashTable[hashVal]=hp;
    }
    return hp;
}

int main() {
    const char *szPrompt = "msh> ";
    char sInputBuf[MAXLINE];
    char szFilePath[MAXPATH];
    char *szDirPath;
    char *szEnvPath=strdup(getenv("PATH"));
    char *szCommand;
    char *szArgs[MAXARGS];
    DIR *dp;
    struct dirent *d;
    struct stat statBuf;
    struct hashList *hp;
    int i, status;
    pid_t pid;

    /* Scan PATH directories and add commands to hash table */
    szDirPath=strtok(szEnvPath, ":");
    while( szDirPath != NULL ) {
        if((dp=opendir(szDirPath)) != NULL) {
            while((d=readdir(dp))) {
                if(d->d_ino != 0) {
                    strcpy(szFilePath, szDirPath);
                    strcat(szFilePath, "/");
                    strcat(szFilePath, d->d_name);
                    if(lstat(szFilePath, &statBuf) >= 0)
                        if(S_ISREG(statBuf.st_mode) && access(szFilePath,X_OK)==0)
                            hashInsert(d->d_name, szFilePath);
                }
            }
            closedir(dp);
        }
        szDirPath = strtok(NULL, ":");
    }

    /* Get commands until Ctrl-D */
    fputs(szPrompt, stdout);                 /* Prompt for command input */
    while( fgets(sInputBuf, MAXLINE, stdin) != NULL ) {

        /* Parse command line arguments */
        szArgs[i=0] = strtok(sInputBuf, " \n");
        while( i<MAXARGS && szArgs[i] != NULL ) szArgs[++i] = strtok(NULL, " \n");

        if(szArgs[0] == NULL) szCommand = NULL;
        else {     /* Find path to command */
            if(strchr(szArgs[0], '/') != NULL) szCommand=szArgs[0];  /* path given */
            else {   /* look up full command path in hash table */
                if((hp=hashFind(szArgs[0])) != NULL) {                
                    szCommand=hp->szFullPath;
                } else {
                    szCommand = NULL;
                    fprintf(stderr, "%s: command not found.\n", szArgs[0]);
                }
            }
        }

        if(szCommand != NULL) {
            /* Fork and execute command, wait for completion */
            if((pid=fork()) < 0) {            /* Error */
                perror("Error in fork()");
                exit(1);
            } else if(pid == 0) {   /* run command in child process */
                execv(szCommand, szArgs);
                /* Reach here only if the command would not exec */
                perror(szCommand);    
                exit(1);
            }
            if(waitpid(pid, &status, 0) == -1) {
                perror("Error in waitpid()");
                exit(1);
            }
        }

        fputs(szPrompt, stdout);  /* Prompt for next command */
    }
    fputc('\n', stdout);

    return 0;
}

