# mush.shar # Shell archive of mush, the minimally usable shell # made by dwjones on Tue Feb 27 11:35:20 CST 2018 # To install this software on a UNIX system: # 1) create a directory (e.g. with the shell command mkdir mush) # 2) change to that directory (e.g. with the command cd mush), # 3) direct the remainder of this text to sh (e.g. sh < ../mush.shar). # This will make sh create files in the new directory; it will do # nothing else (if you're paranoid, you should scan the following text # to verify this before you follow these directions). Then read README # in the new directory for additional instructions. cat > README <<\xxxxxxxxxx README by Douglas Jones, Feb. 27, 2018 This is a distribution of the version of mush (the minimally usable shell) written to meet the requirements of MP3 in the Spring 2018 offering of CS:3620 The component files are: README -- this file globals.h -- the global declarations main.c -- the main program getcommand.c -- read a command from stdin splitargv.c -- divide the command into arguments launch.c -- launch the application (the only part students turned in) Makefile -- build mush from the above Read the Makefile for more information xxxxxxxxxx cat > globals.h <<\xxxxxxxxxx /* globals.h * Global variables and definitions for a Minimally Usable SHell (MUSH) * extracted from mush.c, Feb 25, 2018 * rewritten by Douglas Jones for MP2 in CS:3620, Feb 13, 2018 * originally by Douglas Jones */ /* the input line */ #define LINE_LENGTH 73 char command[LINE_LENGTH]; /* the argument vector extracted from the input line */ #define MAX_ARGS ((LINE_LENGTH/2)+1) char *argv[MAX_ARGS]; /* the definition of MAX_ARGS means we never need to do any bounds checking on argv; each arg is at least 1 character followed by a NUL, and we allow a trailing NULL at the end of the argument list. */ /* interface specs for the components of mush */ void getcommand(); void splitargv(); void launch(); xxxxxxxxxx cat > main.c <<\xxxxxxxxxx /* main.c * Main program for a Minimally Usable SHell (MUSH) * extracted from mush.c, Feb 25, 2018. * rewritten by Douglas Jones for MP2 in CS:3620, Feb 13, 2018 * originally by Douglas Jones */ #include /* needed for feof(stdin) */ #include "globals.h" /* global variables of mush */ int main() /* repeatedly reads commands, splits them up, and executes them */ { while (!feof( stdin )) { getcommand(); splitargv(); launch(); } } xxxxxxxxxx cat > getcommand.c <<\xxxxxxxxxx /* getcommand.c * Get one command from stdin for a Minimally Usable SHell (MUSH) * extracted from mush.c, Feb 25, 2018 * rewritten by Douglas Jones for MP2 in CS:3620, Feb 13, 2018 * originally by Douglas Jones */ #include /* needed for getchar, putchar, EOF */ #include "globals.h" /* global variables of mush */ void getcommand() /* Read a command line from stdin, the standard input file global: command, filled with the null-terminated text of the line note: this is never called when feof(stdin) is true */ { int ch; /* type is int because EOF is outside the char range */ int i = 0; putchar( '>' ); /* the prompt */ /* get as much of the line as possible */ do { /* get the line */ ch = getchar(); command[i] = ch; i++; } while ((ch != '\n') && (ch != EOF) && (i < LINE_LENGTH)); /* deal with nonstandard end of line conditions */ if ((ch == '\n') || (ch == EOF)) { /* normal case */ command[i - 1] = '\0'; } else { /* error case */ command[LINE_LENGTH - 1] = '\0'; fputs( "overlength line: ", stderr ); while ((ch != '\n') && (ch != EOF)) { putchar( ch ); ch = getchar(); } putchar( '\n' ); } } xxxxxxxxxx cat > splitargv.c <<\xxxxxxxxxx /* splitargv.c * Split the command line into argv for a Minimally Usable SHell (MUSH) * extracted from mush.c, Feb 25, 2018 * rewritten by Douglas Jones for MP2 in CS:3620, Feb 13, 2018 * originally by Douglas Jones */ #include /* needed for NULL */ #include "globals.h" /* global variables of mush */ void splitargv() /* Split the command line into its constituent arguments global: command, the line, a null terminated string that is broken into many argv, filled with pointers to the successive arguments in command note: NUL chars replace the delimiter after each argument in command See the definition of MAX_ARGS for why no array bounds checking here */ { int i = 0; int j = 0; for (;;) { while (command[j] == ' ') j++; if (command[j] == '\0') break; argv[i] = &command[j]; i++; while ((command[j] != ' ') && (command[j] != '\0')) { j++; } if (command[j] == '\0') break; command[j] = '\0'; j++; } argv[i] = NULL; } xxxxxxxxxx cat > launch.c <<\xxxxxxxxxx /* launch.c * Application launcher for a Minimally Usable SHell (MUSH) * extracted from mush.c, Feb 25, 2018 * rewritten by Douglas Jones for MP2 in CS:3620, Feb 13, 2018 * originally by Douglas Jones */ #include /* needed for getchar, putchar, NULL */ #include /* needed for exit(), getenv() */ #include /* needed for fork() */ #include /* needed for wait() */ #include /* needed for strcmp() */ #include "globals.h" /* global variables of mush */ extern char** environ; /* environment passed here by whoever exec'd this */ void substitutions() /* do any $environment substitutions on the command line in argv global: argv, an array of strings, any starting with $ will be substituted environ, the values of variables */ { int i; for (i = 0; argv[i] != NULL; i++) { /* for each argument */ if (argv[i][0] == '$') { /* argument subject to substitution */ char * variable = argv[i] + 1; /* what was after $ */ char * value = getenv( variable ); if (value != NULL) argv[i] = value; /* substitution only done if named variable had def! */ } } } void trypath() /* try to launch the command using each element of the search path global: argv, argv[0] names a command to which the rest of argv is passed environ, the environment; here, we look at path note: launch() calls this, and it calls it only in the child fork. */ { char * path = getenv( "PATH" ); int argv0len = strlen( argv[0] ); /* get this just once in advance */ if (path != NULL) { /* if there's no path, don't even try */ /* make a copy of the path for strtok to scribble on */ path = strcpy( malloc( strlen( path ) + 1 ), path ); /* find the first path element and setup for future strtoks */ char * element = strtok( path, ":" ); /* for each path element */ while (element != NULL) { /* construct a trial file name */ int len = strlen( element ) + argv0len + 2; char * filename = strcpy( malloc( len ), element ); strcat( filename, "/" ); strcat( filename, argv[0] ); /* try this path element */ execve( filename, argv, environ ); /* get next element (this is how strtok works!) */ element = strtok( NULL, ":" ); } } } void launch() /* Execute the command global: argv, the command (in argv[0]) and its arguments (argv[1] and up) command, not explicitly referenced, but holds the argument text note: Except for built-in commands, uses fork-exec to launch applications */ { /* fix any references to environment in the command */ substitutions(); /* built-in commands */ if (argv[0] == NULL) { /* we need this to prevent strcmp() from segmentation fault */ return; } if (!strcmp( argv[0], "exit" )) { /* this could get exit succes/failure code from argv[1] */ exit( EXIT_SUCCESS ); } /* control only reaches here if the command is not built-in */ if (fork() == 0) { /*child*/ /* try each option on the search path first */ trypath(); /* last chance after path exhausted */ execve( argv[0], argv, environ ); /* we could put code to walk down the search path here */ printf( "no such command\n" ); exit( EXIT_FAILURE ); } else { /*parent*/ wait( NULL ); } } xxxxxxxxxx cat > Makefile <<\xxxxxxxxxx # Makefile # make mush -- makes the minimally usable shell # make clean -- deletes all files created by make # # by Douglas Jones for MP3 in CS:3620, Feb. 25, 2018 # primary make target mush: main.o getcommand.o splitargv.o launch.o cc -o mush main.o getcommand.o splitargv.o launch.o # subsidiary make targets main.o: main.c globals.h cc -c main.c getcommand.o: getcommand.c globals.h cc -c getcommand.c splitargv.o: splitargv.c globals.h cc -c splitargv.c launch.o: launch.c globals.h cc -c launch.c # utility make targets clean: rm -f *.o mush xxxxxxxxxx