Princeton University
COS 217:  Introduction to Programming Systems

Assignment 6:  ish - An Interactive UNIX Shell
Implementation Requirements and Recommendations

General

(Required) Write your program so it is modular.  Create small functions, each of which does a single well-defined job.  Define interfaces and implementations, thus splitting your program into multiple files in the standard way.  Define ADTs if appropriate.  Doing so will earn you a better grade.  More fundamentally, doing so will help you to write a better program quicker.

(Recommended) See the web page http://www.opengroup.org/onlinepubs/7908799/xsh/unistd.h.html for excellent documentation on the UNIX system calls.  You may find that documentation more convenient than the man pages.

(Recommended) Consistently use calls to the assert function to validate the parameters of every function in your program.

(Recommended) Use the List and ListIter ADTs (from early precepts).  The source code is available through the course web pages.

(Required) After printing to standard output, immediately flush the standard output stream by calling fflush(stdout).  Doing so eliminates complications with output buffering in the presence of concurrent processes.

(Required) Print all error messages to standard error, not to standard output.

(Recommended) After a failed system call, use the perror standard function to print an appropriate error message to standard error.

(Required) Make sure that your ish interprets commands from the ~/.ishrc file when it is first launched.  We will test your ish by repeatedly copying command sequences to the ~/.ishrc file and launching your ish.  If your program does not interpret commands from that file, then it will fail all of our tests.  In that unfortunate circumstance, the grade would be very poor.

(Required) Your ish should print the commands from ~/.ishrc.  That is, immediately after your ish reads a command from the ~/.ishrc file, it should print that command to standard output.  In that manner ish should generate a transcript that shows each command of ~/.ishrc, followed by the output that results from executing that command.  If your program does not print the commands of ~/.ishrc to standard output, then it will be difficult to interpret the transcript.  In that unfortunate circumstance, the grade would be penalized.

(Required) Your ish should not print the commands from standard input.  That is, when your ish reads a command from standard input, it should not print that command to standard output.

Building

(Required) The source code declarations of the setenv and unsetenv UNIX system calls are not included in any standard header file.  Instead they are available in the header file /u/cs217/Assignment6/setenv.h.  You should copy it to your working directory and #include it into your source code as appropriate.

(Required) The object code definitions of the setenv and unsetenv UNIX system calls are not included in the standard libc.a library.  Instead they are available in the library /u/cs217/Assignment6/libsetenv.a.  To use that library, you should copy it to your working directory, and append "-L. -lsetenv" to the end of each gcc command that links your code to produce an executable file.  (Pages 88-89 of the "GNU Software" textbook describe the meaning of the -L and -l options to gcc.)

Lexical Analysis

(Recommended) Read each input line using the fgets function.

(Recommended) Implement your lexical analysis phase as a deterministic finite state automaton (FSA) in the manner shown in precepts.

(Recommended) Create some temporary code that prints the token list created by the lexical analysis phase.  Do not proceed to subsequent phases until you test your lexical analysis phase thoroughly.

(Recommended) Test your ish's lexical analysis phase by making sure that it handles these example input lines properly:

INPUT LINE TOKEN LIST
one one
123 123
one123 one123
123one 123one
@#$%^&*() @#$%^&*()
' '
one two one
two
one   two one
two
   one two one
two
one > one
>
one> one
>
>one >
one
"one" one
">" > (an ordinary token)
"one two" one two
one"two" onetwo
"one"two onetwo
"one ERROR - unmatched quote
one"two ERROR - unmatched quote

(Required) Make sure that your lexical analysis phase represents tokens so that the difference between quoted and unquoted special characters is adequately captured.  For example, these two commands are very different, and that difference must be captured at the lexical analysis phase:

echo one > two
echo one ">" two

Syntactic Analysis

(Recommended) Do as much validation of the pipeline as you can at the syntactic analysis phase.  The more error checking you do in the syntactic analysis phase, the less must be done during the (more complicated) execution phase.

(Recommended) Create some temporary code that prints the pipeline created by the syntactic analysis phase.  Do not proceed to subsequent phases until you test your syntactic analysis phase thoroughly.

Execution

(Required) Use the _exit (and not the exit) function to terminate child processes.  See the man page for the fork system call for a brief explanation.

(Required) Use the umask system call to determine the permissions of a file that is created via redirection of standard output.  Here's a sequence of statements that indicates how:

/* Fetch umask */
iUmask = umask(0);
/* Reset umask. */
umask(iUmask);

/* Compute the new file's permission as the complement
of the umask, without execute permission. */
iPermission = (~iUmask) & 0666;

(Recommended) Use the standard C function strtok to parse the value of the PATH environment variable.  But beware that strtok destroys the string that it is parsing.  You must make a copy of the string, and apply strtok to that copy.

(Required) Use the setenv UNIX system call to implement the setenv shell built-in command.

(Required) Use the unsetenv UNIX system call to implement the unsetenv shell built-in command.

(Required) Use the chdir UNIX system call to implement the cd shell built-in command.

(Recommended) Use the access UNIX system call to determine if a given file exists, is indeed a file (as opposed to a directory), and is executable.  An alternative is to use the stat UNIX system call.

(Recommended) If you have trouble with the pipeline logic, then temporarily back away from ish and write a simpler test program.  Specifically, write a program that, in a hardcoded manner, executes the pipeline ls | sort | more.  You may find it straightforward to generalize that program to create the pipeline handling code of ish.

History (for extra credit)

(Required) Execute the history command in a child process (alias subshell) so its standard output can be redirected, and so it can be part of a pipeline.