# mush.shar # Shell archive of mush, the minimally usable shell # made by dwjones on Fri Mar 2 14:29:31 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, Mar. 2, 2018 This is a distribution of the version of mush (the minimally usable shell) written to launch MP4 (but not solve) 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 or do built-in commands 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) * modified by Douglas Jones for MP4, Mar 2, 2018 * 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 file (usually the same as stdin) */ FILE * infile; /* 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) * modified by Douglas Jones to start MP4, Mar 2, 2018 * 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( int argc, char * argv[] ) /* repeatedly reads commands, splits them up, and executes them */ { infile = stdin; while (!feof( infile )) { getcommand(); splitargv(); launch(); } } xxxxxxxxxx cat > getcommand.c <<\xxxxxxxxxx /* getcommand.c * Get one command from stdin for a Minimally Usable SHell (MUSH) * modified by Douglas Jones to start MP4, Mar 2, 2018 * 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 getc, 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 infile, the input text stream 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; /* where on the line are we */ /* only prompt if reading from stdin */ if (infile == stdin) putc( '>', stdout ); /* get as much of the line as possible */ do { /* get the line */ ch = getc( infile ); 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)) { putc( ch, stderr ); ch = getc( infile ); } putc( '\n', stderr ); } } 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 and built-ins for a Minimally Usable SHell (MUSH) * modified by Douglas Jones to start MP4, Mar 2, 2018 * 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; } else { argv[i] = ""; /* undefined variables become empty strings */ } } } } 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) return; /* blank command line */ if (argv[0][0] == '#') return; /* comment */ 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*/ /* first try the literal file name given */ execve( argv[0], argv, environ ); /* if that doesn't work, try prefixes from the search path */ trypath(); /* if that doesn't work, give up */ fputs( "no such command\n", stderr ); 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