Insight into something.
//////////////////////////////////////////////////////////////////////////////// /** * @file myshell.c * @brief A C program to implement an interactive shell in which a user * executes commands. * * @title My Shell * @author Nicholas Guthrie * @web http//nickguthrie.com * @created February 26, 2013 * * Compile with: gcc -Wall -g -o ./myshell myshell.c * Flags: -DVERBOSE for verbose child/parent status outputs * -DCMD to print the command struct after it has been parsed * -DPRINTCD to print the current directory after every command * -DTOKEN to print the token parsing * */ //////////////////////////////////////////////////////////////////////////////// /**----------------------------------------------------------------- :Libraries: // _ _ _ _ // | | (_) |__ _ _ __ _ _ _(_)___ ___ // | |__| | '_ \ '_/ _` | '_| / -_|_-< // |____|_|_.__/_| \__,_|_| |_\___/__/ // //----------------------------------------------------------------------------*/ #include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> //for umask #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> /**-------------------------------------------------------------------- :Global: // ___ _ _ _ // / __| |___| |__ __ _| | // | (_ | / _ \ '_ \/ _` | | // \___|_\___/_.__/\__,_|_| // //----------------------------------------------------------------------------*/ #define BUFFER_LENGTH 100 /* maximum buffer length */ #define MAX_ARGS 64 /* maximum number of arguments */ #define MAX_ARG_LEN 100 /* maximum length of a single argument */ #define WHITESPACE " ,\t\n" /* whitespace in commandline args */ #define MAX_HISTORY 999 /* maximum commands to be stored in history */ #define PATH_VAR "MYPATH" /* the environment path variable */ /**------------------------------------------------------------------- :Structs: // ___ _ _ // / __| |_ _ _ _ _ __| |_ ___ // \__ \ _| '_| || / _| _(_-< // |___/\__|_| \_,_\__|\__/__/ // //----------------------------------------------------------------------------*/ struct command_t { char *name; /* redundently stored in argv[0] */ int background; int argc; char *argv[MAX_ARGS]; int hasOutput, hasInput, specialOutput; char *input; char *output; }; /**---------------------------------------------------------------- :Prototypes: // ___ _ _ // | _ \_ _ ___| |_ ___| |_ _ _ _ __ ___ ___ // | _/ '_/ _ \ _/ _ \ _| || | '_ \/ -_|_-< // |_| |_| \___/\__\___/\__|\_, | .__/\___/__/ // |__/|_| // //----------------------------------------------------------------------------*/ int parsePath( char *dirs[] ); int getPath (char *dirs[], int dir_size, char * command, char * path_out); int parseCommand(char *cLine, struct command_t *cmd); void printCommand(struct command_t *cmd); int storeHistory (char * cLine, char history[MAX_HISTORY][BUFFER_LENGTH], int history_size); void printHistory (char history[MAX_HISTORY][BUFFER_LENGTH], int history_size); int execute(char * fullpath, struct command_t * cmd, char history[MAX_HISTORY][BUFFER_LENGTH], int history_size); void printCD(); /**---------------------------------------------------------------------- :Main: // __ __ _ // | \/ |__ _(_)_ _ // | |\/| / _` | | ' \ // |_| |_\__,_|_|_||_| // //----------------------------------------------------------------------------*/ int main( int argc, char *argv[] ) { ////////////////////////////// // Variables ////////////////////////////// char buffer[BUFFER_LENGTH]; char *dirs[10]; char fullpath[512]; char history[MAX_HISTORY][BUFFER_LENGTH]; int history_size = 0; ////////////////////////////// // Shell Loop ////////////////////////////// printf("welcome to myshell\n"); int dir_size = parsePath(dirs); while(1) { #ifdef PRINTCD printCD(); #endif struct command_t cmd; printf("---| "); // Get Command from User fgets(buffer, BUFFER_LENGTH, stdin); // Store History if ( strcmp(buffer, "\n" )) history_size = storeHistory(buffer, history, history_size); ////////////////////////////// // Process Input ////////////////////////////// parseCommand(buffer, &cmd); #ifdef CMD printCommand(&cmd); #endif // Exit the Shell if ( !strcmp(cmd.argv[0], "exit" )) { int pid; //wait for all processes to finish while ((pid = waitpid(-1, NULL, 0))) { if (errno == ECHILD) { break; } } printf("bye\n"); exit(0); } // Bang Search else if ( cmd.argv[0][0] == '!' && strlen(cmd.argv[0]) > 1 ) { //printf("Searching for %s\n", cmd.argv[0]); int k = history_size -1; int found = 0; while(k >= 0) { int max = strlen(cmd.argv[0]) - 1; int j = 0; int matches = 0; for(j=0; j<max; j++) { //printf("\t%i comparing %c to %c\n", k, cmd.argv[0][j+1], history[k][j]); if( cmd.argv[0][j+1] == history[k][j] ) matches++; else break; } if ( matches == max ) { found = 1; strcpy(buffer, history[k]); parseCommand(buffer, &cmd); //Otherwise Execute the Command if Possible if( getPath(dirs, dir_size, cmd.argv[0], fullpath) ) execute(fullpath, &cmd, history, history_size); break; } k--; } // Unable to find in History if(!found) { printf("ERROR: no command with '"); int i; for(i=1; i<strlen(cmd.argv[0]); i++) printf("%c",cmd.argv[0][i]); printf("' prefix in history\n"); } } //Execute the Command else if ( getPath(dirs, dir_size, cmd.argv[0], fullpath) && strcmp(buffer, "" ) ) { execute(fullpath, &cmd, history, history_size); } } } /**----------------------------------------------------------------- :Functions: // ___ _ _ // | __| _ _ _ __| |_(_)___ _ _ ___ // | _| || | ' \/ _| _| / _ \ ' \(_-< // |_| \_,_|_||_\__|\__|_\___/_||_/__/ // // //----------------------------------------------------------------------------*/ //////////////////////////////////////////////////////////////////////////////// /** * @brief Reads the PATH variable for this enviroment, then builds an array, * dirs[], of the directories in PATH * @param[out] dirs[] An array of the directories in PATH. * @return The number of directories stored in dirs. */ //////////////////////////////////////////////////////////////////////////////// int parsePath( char *dirs[] ) { char *pathEnvVar; char *thePath; int i; for(i =0; i<MAX_ARGS; i++) dirs[i] = NULL; pathEnvVar = (char *) getenv( PATH_VAR ); if(pathEnvVar == NULL) { pathEnvVar = "/bin"; } thePath = (char *) malloc(strlen(pathEnvVar) + 1); strcpy(thePath, pathEnvVar); printf("($MYPATH is %s)\n", thePath); /* Loop to parse thePath. Look for a ':' delimiter between each path name. */ int pos = 0; char * pch; #ifdef TOKEN printf ("Splitting \"%s\" into tokens:\n",thePath); #endif pch = strtok (thePath,":"); while (pch != NULL) { #ifdef TOKEN printf ("%s\n",pch); #endif // Parse ~ to $HOME Conversions if( pch[0] == '~' ) { pch++; char fullpath[512]; char * HOME = (char *) getenv( "HOME" ); strcpy (fullpath, HOME); strcat (fullpath, pch); strcpy(pch, fullpath); } dirs[pos] = pch; pch = strtok (NULL, ":"); pos++; } return pos; } //////////////////////////////////////////////////////////////////////////////// /** * @brief Searches for the path of the file in dirs. * @param[in] dirs[] * @param[in] dir_size * @param[in] command * @param[out] path_out * @return 1 if the path was found, 0 otherwise. */ //////////////////////////////////////////////////////////////////////////////// int getPath (char *dirs[], int dir_size, char * command, char * path_out) { char f_name[512]; int i; if( !strcmp(command, "history") || !strcmp(command, "cd") ) return 1; // Test Local Directory if(command[0]);// == '.') { //strcat(f_name, command); if( access( command, F_OK) != -1 ) { strcpy(path_out, command); return 1; } } // Test Directories in dirs[] for(i=0; i<dir_size; i++) { //Concate and Copy Strings strcpy(f_name, dirs[i]); strcat(f_name, "/"); strcat(f_name, command); // Found File if( access( f_name, F_OK ) != -1 ) { //printf("Found %s\n", f_name); strcpy(path_out, f_name); return 1; } } printf("ERROR: command '%s' not found\n", command); return 0; } //////////////////////////////////////////////////////////////////////////////// /** * @brief Parses a commandline string into the command structure. * @param[in] cLine The commandline command to process. * @param[out] cmd The command structure to store the result in. * @return 1 on success. */ //////////////////////////////////////////////////////////////////////////////// int parseCommand(char *cLine, struct command_t *cmd) { int argc; char **clPtr; // Initialization clPtr = &cLine; //cLine is the command line argc = 0; cmd->argc = 0; cmd->input = NULL; cmd->output = NULL; cmd->hasInput = 0; cmd->hasOutput = 0; cmd->specialOutput = 0; cmd->argv[argc] = (char *) malloc(MAX_ARG_LEN); while((cmd->argv[argc] = strsep(clPtr, WHITESPACE)) != NULL) { // Check for ~ to $HOME Conversions if( cmd->argv[argc][0] == '~' ) { cmd->argv[argc]++; char fullpath[512]; char * HOME = (char *) getenv( "HOME" ); strcpy (fullpath, HOME); strcat (fullpath, cmd->argv[argc]); strcpy(cmd->argv[argc], fullpath); } //Check for Redirection. if(!strcmp(cmd->argv[argc], "==>")) { cmd->hasOutput = 1; cmd->output = (char *) malloc(MAX_ARG_LEN); cmd->output = strsep(clPtr, WHITESPACE); argc--; } else if(!strcmp(cmd->argv[argc], "<==")) { cmd->hasInput = 1; cmd->input = (char *) malloc(MAX_ARG_LEN); cmd->input = strsep(clPtr, WHITESPACE); argc--; } else if(!strcmp(cmd->argv[argc], "-->")) { cmd->specialOutput = 1; cmd->output = (char *) malloc(MAX_ARG_LEN); cmd->output = strsep(clPtr, WHITESPACE); argc--; } cmd->argv[++argc] = (char *) malloc(MAX_ARG_LEN); } //Set the command name and argc cmd->argc += argc-1; cmd->name = (char *) malloc(sizeof(cmd->argv[0])); strcpy(cmd->name, cmd->argv[0]); cmd->argv[cmd->argc] = NULL; if(!strcmp(cmd->argv[cmd->argc - 1], "&")) { cmd->background = 1; cmd->argv[cmd->argc - 1] = NULL; cmd->argc = cmd->argc - 1; } else cmd->background = 0; return 1; } //////////////////////////////////////////////////////////////////////////////// /** * @brief Prints all members of the command line structure * @param[in] cmd The commandline structure to print. * @return void */ //////////////////////////////////////////////////////////////////////////////// void printCommand(struct command_t *cmd) { printf("------Printing Command------------------\n"); printf("cmd Name = %s\nnum Args = %i\n", cmd->name, cmd->argc); int i; for(i=0; i<cmd->argc; i++) printf("\t%i. %s\n", i, cmd->argv[i]); printf("Output: %s\n", cmd->output); printf("Input: %s\n", cmd->input); printf("isBackground: %i\n", cmd->background); printf("----------------------------------------\n"); } //////////////////////////////////////////////////////////////////////////////// /** * @brief Stores the command in history. * @param[in] cLine The full command to store in histoyr. * @param[in] history To store the history commands in. * @param[in] history_size The current size of the history. * @return The new size of the history. */ //////////////////////////////////////////////////////////////////////////////// int storeHistory (char * cLine, char history[MAX_HISTORY][BUFFER_LENGTH], int history_size) { if( history_size < MAX_HISTORY ) strcpy(history[history_size++], cLine); else { int i; for(i=1; i < MAX_HISTORY; i++) strcpy(history[i-1], history[i]); strcpy(history[MAX_HISTORY-1], cLine); } return history_size; } //////////////////////////////////////////////////////////////////////////////// /** * @brief Prints stored history. * @param[in] history The history to print. * @param[in] history_size The num of commands to print from history * @return void */ //////////////////////////////////////////////////////////////////////////////// void printHistory (char history[MAX_HISTORY][BUFFER_LENGTH], int history_size) { int i; for(i=0; i<history_size; i++) printf("%03i %s", i, history[i]); } //////////////////////////////////////////////////////////////////////////////// /** * @brief Executes a file with arguments. * @param[in] fullpath the fullpath including filename of the file to execute. * @param[in] cmd The command arguments. * @return EXIT_FAILURE if unsuccessful. */ //////////////////////////////////////////////////////////////////////////////// int execute(char * fullpath, struct command_t * cmd, char history[MAX_HISTORY][BUFFER_LENGTH], int history_size) { // Print History if ( !strcmp(cmd->argv[0], "history") ) { printHistory(history, history_size); return EXIT_SUCCESS; } // CD else if ( !strcmp(cmd->argv[0], "cd" ) ) { chdir(cmd->argv[1]); return EXIT_SUCCESS; } // General Execute Case else { pid_t pid = fork(); // Error Handling Condition if( pid < 0 ) { printf("Unable to fork()\n"); } // In the Child Process else if ( pid == 0 ) { #ifdef VERBOSE printf( "CHILD: happy birthday to me!\n" ); #endif // File Redirection int bak, bak_in, new_in, fd; // ==> Output if(cmd->hasOutput) { fflush(stdout); bak = dup(1); fd = open(cmd->output, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd < 0) { fprintf(stderr, "open error: %d [%s]\n", errno, strerror(errno)); exit(1); } dup2(fd, 1); close(fd); } // --> Output if(cmd->specialOutput) { fflush(stdout); bak = dup(1); fd = open(cmd->output, O_WRONLY | O_APPEND, 0600); if (fd < 0) { fprintf(stderr, "open error: %d [%s]\n", errno, strerror(errno)); exit(1); } dup2(fd, 1); close(fd); } // <== Input if(cmd->hasInput) { fflush(stdin); bak_in = dup(0); new_in = open(cmd->input, O_RDONLY); dup2(new_in, 0); close(new_in); } // Execute the Command execv(fullpath, cmd->argv); // Close File Redirection // @note - not sure this code is ever run if(cmd->hasOutput) { fflush(stdout); dup2(bak, 1); close(bak); } // -------------------- if(cmd->hasInput) { fflush(stdin); dup2(bak_in, 0); close(bak_in); } /* if the execv() sys call works, code should never get here */ perror( "execv() failed" ); return EXIT_FAILURE; } // In the Parent Process else { #ifdef VERBOSE printf( "PARENT: my child's pid is %d\n", pid ); #endif /* wait for the child to terminate */ int status; if( cmd->background ) { waitpid( -1, &status, WNOHANG ); /* NOBLOCKING */ printf( "[process running in background with pid %i]\n", pid ); } else waitpid( -1, &status, 0 ); /* BLOCKING */ #ifdef VERBOSE printf( "PARENT: child %d terminated...", child_pid ); if ( WIFSIGNALED( status ) ) /* core dump or kill or kill -9 */ { printf( "abnormally\n" ); } else if ( WIFEXITED( status ) ) /* child called return or exit() */ { int rc = WEXITSTATUS( status ); printf( "successfully with exit status %d\n", rc ); } #endif } } return EXIT_FAILURE; } //////////////////////////////////////////////////////////////////////////////// /** * @brief Prints the current directory. * @return void */ //////////////////////////////////////////////////////////////////////////////// void printCD() { char cwd[1024]; if (getcwd(cwd, sizeof(cwd)) != NULL) printf("\nCurrent working dir: %s/\n", cwd); else perror("getcwd() error"); }