/* Name: Steve Berger * File name: main.c * Course: CS 284 * Section: A * Instructors name: Matt Johnson * Due date: March 4, 2005 * Description: This program is the fourth assignment of CS 284 this semester. * i sincerely apologize for this pile... */ /*---------------------------------------------------------------------------*/ /* January 21, 2005 * - Created this file. * - Program builds and prints prompt. * - Program handles command line arguments and echoes input back to the * user. */ /*---------------------------------------------------------------------------*/ /* February 2, 2005 * - Added the ability to read a file named .mshell which contains * path information. The path in the environment is set based on the * information in this file. * - The shell also supports the cd command. * - Changed the long name of the -p option from path to pwd. */ /*---------------------------------------------------------------------------*/ /* February 4, 2005 * - Added the ability to handle mkdir and rmdir. * - Added the ability to fork and exec a new process and wait for it to * - complete. */ /*---------------------------------------------------------------------------*/ #define _GNU_SOURCE 1 #include #include #include #include #include #include #include #include #include #include #include "parsedTokens.h" using namespace std; struct prompt { char * prompt_; int promptLen_; int usePWD_; char lastChar_; }; typedef struct prompt Prompt; typedef enum { /* The program should exit immediately. */ QUIT, /* The program should print the help message and then exit. */ PRINT_HELP, /* The program should enter the shell loop. */ RUN } Action; typedef enum { FALSE = 0, TRUE = 1 } Bool; /* PreConditions: The pointer pr points to an initialized Prompt * object, argc and argv are the program arguments and are * untouched. * PostConditions: The command line has been parsed, the prompt structure has * been defined, and the function returns the action that * needs to be done. */ Action HandleCommandLineArgs(int argc, char **argv, Prompt * pr); /* PreConditions: The command line arguments have been handled, and the * prompt has been initialized and defined. * PostConditions: The shell will execute until the user ends the program. */ void RunShellLoop(Prompt *pr); /* PreConditions: The pointer pr points to a Prompt object. * PostConditions: The elements of the structure have been set to reasonable * initial values. */ void InitializePrompt(Prompt * pr); /* PreConditions: The pointer pr points to a defined Prompt object. * PostConditions: The prompt has been printed to standard out. */ void PrintPrompt(Prompt * pr); /* PreConditions: The pointer pr points to a Prompt object. * PostConditions: The memory used by the prompt is given back to the * operating system.*/ void FreePrompt(Prompt * pr); /* PreConditions: None. * PostConditions: A help message is printed to standard out. */ void PrintHelp(); /* PreConditions: A configuration file named .mshell exists in the current * directory. It is formatted as follows: PATH=/dir:/dir2 * PostConditions: Configuration file has been read. If it contains a path, * the path has been set. */ void ReadConfigurationFile(); /* PreConditions: The command arg is some command the user typed in. The arg * strLen must be a non-negative integer indicating the length * of the command, which should include the NULL character. * PostConditions: If the command is a shell command, it is executed and the * function returns true. Otherwise, the function returns * false. */ Bool IsShellCommand(char * command, int strLen); /* PreConditions: The argc argument is the number of tokens in the command. * argv is a 2-D character array indicating what command to * execute. * PostConditions: The command has been executed in its own process. */ void Execute(char **argv); int main(int argc, char **argv) { Prompt prmpt; Action act; /* Read Configuation File */ ReadConfigurationFile(); /* Set initial values for the prompt. */ InitializePrompt(&prmpt); /* Look at the command line and decide what to do next. */ act = HandleCommandLineArgs(argc, argv, &prmpt); /* Take whatever action is appropriate based on the command line. */ switch(act) { case PRINT_HELP: PrintHelp(); break; case RUN: PrintPrompt(&prmpt); RunShellLoop(&prmpt); FreePrompt(&prmpt); case QUIT: default: printf("\nGoodbye.\n\n"); }; return 0; } void InitializePrompt(Prompt * pr) { pr->prompt_ = NULL; pr->promptLen_ = 0; pr->usePWD_ = 0; pr->lastChar_ = '>'; } void PrintPrompt(Prompt * pr) { int promptLength = 0; char * hostNamePtr = getenv("HOSTNAME"); char * pwdPtr = getenv("PWD"); char * tempPtr = NULL; /* Calculate the length of the host name. */ if(hostNamePtr) { promptLength = strlen(hostNamePtr) + 3; } /* Add the length of the working directory if it is needed. */ if(pr->usePWD_) { if(pwdPtr) { promptLength += strlen(pwdPtr) + 1; } } /* Allocate more memory for the prompt if it is needed. */ if(pr->promptLen_ < promptLength) { pr->promptLen_ = promptLength; tempPtr = (char *) realloc(pr->prompt_, promptLength); if(tempPtr) { pr->prompt_ = tempPtr; tempPtr = NULL; } else { fprintf(stderr, "Memory allocation error - program exiting\n"); exit(EXIT_FAILURE); } } /* Build the prompt. */ if(hostNamePtr) { strncpy(pr->prompt_, hostNamePtr, strlen(hostNamePtr) + 1); } strncat(pr->prompt_, ":", 1); if(pr->usePWD_) { if(pwdPtr) { strncat(pr->prompt_, pwdPtr, strlen(pwdPtr)); } strncat(pr->prompt_, ":", 1); } strncat(pr->prompt_, &(pr->lastChar_) , 1); /* Print the prompt. */ printf("%s", pr->prompt_); } void FreePrompt(Prompt * pr) { if(pr->prompt_) { free(pr->prompt_); } } void PrintHelp() { printf("Synopsis: Matt's Shell - it doesn't do much.\n"); printf("Usage: mshell [[option] [option_arg]]\n"); printf(" -h, --help prints this help message\n"); printf(" -l, --lastchar requires a one character argument which" \ " becomes the last\n"); printf(" character of the prompt\n"); printf(" -p, --pwd tells the shell to include the current" \ " working directory in\n"); printf(" the prompt\n"); } Action HandleCommandLineArgs(int argc, char **argv, Prompt * pr) { int oc = 0; Action ret = RUN; struct option longopts[] = { {"help", no_argument, NULL, 'h'}, {"lastchar", required_argument, NULL, 'l'}, {"pwd", no_argument, NULL, 'p'}, {0, 0, 0, 0} }; while((oc = getopt_long(argc, argv, ":hl:pW;", longopts, NULL)) != -1) { switch(oc) { case 'h': return PRINT_HELP; break; case 'l': /* Define the last character of the prompt. */ pr->lastChar_ = optarg[0]; ret = RUN; break; case 'p': /* Indicate that we are using pwd in the prompt. */ pr->usePWD_ = 1; ret = RUN; break; case 0: /* getopt_long set a variable - nothing to do */ break; case ':': /* missing option argument */ fprintf(stderr, "%s: option '-%c' requires an argument\n", argv[0], optopt); ret = QUIT; break; case '?': /* Invalid option */ default: /* Invalid option */ fprintf(stderr, "%s: option '-%c' is invalid: ignored\n", argv[0], optopt); ret = RUN; break; } } return ret; } void RunShellLoop(Prompt *pr) { unsigned int ii; int stillGoing = 1; ssize_t readReturn = 0; size_t bufLen = 0; int inputLength; char *input = NULL; char ** command = NULL; while(stillGoing) { /* The buffer will contain the new line character and the null char */ readReturn = getline(&input, &bufLen, stdin); if(readReturn == -1) { fprintf(stderr, "getline function call failed\n"); stillGoing = 0; } else if(readReturn == 5 && (strncmp(input, "exit", 4) == 0)) { stillGoing = 0; } else { /* Get rid of the new line that came in with the readline. */ for(ii = 0; ii < bufLen; ++ii) { if(input[ii] == '\n') { input[ii] = '\0'; } } inputLength = strlen(input) + 1; if(!IsShellCommand(input, inputLength)) { command = (char**) malloc(sizeof(char*) * 30); command[0] = (char*) malloc(sizeof(char) * inputLength); command[1] = NULL; strncpy(command[0], input, inputLength); Execute(command); free(command[0]); free(command); command = NULL; } PrintPrompt(pr); } } if(input) { free(input); } } void ReadConfigurationFile() { int fd = 0; FILE *fp = 0; int stillGoing = 1; ssize_t readReturn = 0; char *input = NULL; size_t bufLen = 0; fd = open(".mshell", O_RDONLY); fp = fdopen(fd, "r"); if(fd < 0) { fprintf(stderr, "Cannot open .mshell for reading.\n"); fprintf(stderr, "%s\n", strerror(errno)); } else if(fp == NULL) { fprintf(stderr, "Cannot fdopen .mshell for reading.\n"); fprintf(stderr, "%s\n", strerror(errno)); } else { while(stillGoing) { readReturn = getline(&input, &bufLen, fp); if(readReturn == -1) { stillGoing = 0; } else { if(strncmp(input, "PATH=", 5) == 0) { if(strlen(input) > 6) { if(setenv("PATH", &input[5], 1) != 0) { fprintf(stderr, "Insufficient space in environment for" \ " PATH. PATH not set.\n"); } } } } } if(input) { free(input); } } fclose(fp); close(fd); } Bool IsShellCommand(char * command, int strLen) { Bool ret = FALSE; char *buf = NULL; if(strLen >= 5) { /* change directory */ if(strncmp(command, "cd ", 3) == 0) { /* Change directories. */ if(-1 == chdir(&command[3])) { fprintf(stderr, "%s\n", strerror(errno)); } else { ret = TRUE; } /* Set the PWD environment variable, because the current working */ /* directory has probably changed. */ if(setenv("PWD", getcwd(buf, 0), 1) != 0) { fprintf(stderr, "Insufficient space in environment for" \ " PWD. PWD not set.\n"); } else { ret = TRUE; } } /* remove directory */ else if(strncmp(command, "rd ", 3) == 0) { if(-1 == rmdir(&command[3])) { fprintf(stderr, "%s\n", strerror(errno)); } else { ret = TRUE; } } /* make directory */ else if(strncmp(command, "md ", 3) == 0) { if(-1 == mkdir(&command[3], S_IRWXU)) { fprintf(stderr, "%s\n", strerror(errno)); } else { ret = TRUE; } } else { ret = FALSE; } } else { ret = FALSE; } free(buf); return ret; } void Execute(char **argv) { ParsedTokens command; pid_t pid = 0; command.ParseAndAddTokens(argv[0]); std::string token; char * current; char * next; int pipefd[2]; int bufsiz = 30; int argument; int numTokens; numTokens = command.GetNumTokens(); pid = fork(); /* Child Process */ if(pid == 0) { command.StartIterator(); while(command.GetNextToken(token)) { argument=1; strcpy(current, token.c_str()); argv[0] = current; while(command.GetNextToken(token)) { strcpy(next, token.c_str()); if(!strcmp(next, "|\0")) { command.GetNextToken(token); strcpy(next, token.c_str()); write(pipefd[1], current, 1); read(pipefd[0], next , bufsiz); close(pipefd[0]); close(pipefd[1]); } //if next IS NOT A PIPE if(strcmp(next, "|\0")) { argv[argument] = next; argument++; argv[argument] = NULL; } } execvp(argv[0], argv); perror("Error"); /* fprintf(stderr, "%s\n", strerror(errno)); */ /* Exit the process. ENOENT stands for no entry in the directory. * If that is the situation, then the exit status is 127. Otherwise, * it is 126. This is by POSIX convention, as outlined in Robbins P 304. */ _exit(errno == ENOENT ? 127 : 126); } } /* Parent Process */ else if(pid > 0) { if(wait(NULL) == -1) { fprintf(stderr, "%s\n", strerror(errno)); } } /* Fork Failure */ else { fprintf(stderr, "%s\n", strerror(errno)); } }