The purpose of this assignment is to help you learn about Unix processes, low-level input/output, and signals. It also will give you ample opportunity to define software modules; in that sense the assignment is a capstone for the course.
This assignment is an individual assignment, not a team assignment.
Signal handling (as described below) is the "on your own" part of this assignment. That part is worth 8% of this assignment.
A Unix shell is a program that makes the facilities of the operating system available to interactive users. There are several popular Unix shells:
sh (the Bourne shell),
csh (the C shell), and
bash (the Bourne Again shell) are a few.
Your task in this assignment is to create a program named
ish. Your program should be a minimal but realistic interactive Unix shell. A Supplementary Information page lists detailed requirements and recommendations.
When first started, your program should check to see if the user provided a command-line argument. If so, then your program should treat the command-line argument as a file name, and should read and interpret lines from that file. Thus that mechanism provides a convenient way for you to automate the testing of your program.
To facilitate your debugging and our testing, your program should print each line that it reads from the file immediately after reading it. Your program should print a percent sign and a space (% ) before each such line.
Your program should terminate when the user types Ctrl-d or issues the
exit command. (See also the section below entitled "Signal Handling.")
After start-up processing, your program repeatedly should perform these actions:
stdout a prompt consisting of a percent sign followed by a space.
Read a line from
Lexically analyze the line to form an array of tokens.
Syntactically analyze (i.e. parse) the token array to form a command.
Execute the command.
From the user's point of view, a token should be a word. (Your program may represent a token using a richer data structure.) More formally, from the user's point of view a token should consist of a sequence of non-white-space characters that is separated from other tokens by white-space characters. There should be two exceptions:
The special characters '<' and '>' should form separate tokens.
Strings enclosed in double quotes (") should form part or all of a single token. Special characters inside of strings should not form separate tokens.
Your program should assume that no line of the given file or
stdin contains more than 2047 characters; the terminating newline character is included in that count. In other words, your program should assume that a string composed from a line of input can fit in an array of characters of length 2048. If a line of input is longer than 2047 characters, then your program need not handle it properly; but it should not corrupt memory.
From the user's point of view, a command should be a sequence of tokens, the first of which specifies the command name. (Your program may represent a command using a richer data structure.)
The '<' token should indicate that the following token is the name of a file. Your program should redirect the command's
stdin to that file. It should be an error to redirect a command's
stdin more than once.
The '>' token should indicate that the following token is the name of a file. Your program should redirect the command's
stdout to that file. It should be an error to redirect a command's
stdout more than once.
Your program should interpret four shell built-in commands:
||If environment variable
||Your program should destroy the environment variable
||Your program should change its working directory to
||Your program should exit with exit status 0. Note:
Note that those built-in commands should neither read from
stdin nor write to
stdout. So your program should ignore file redirection with those built-in commands. Your program should still recognize invalid commands, however.
If the command is not a built-in command, then your program should consider the command name to be the name of a file that contains code to be executed. Your program should
fork a child process and pass the file name, along with its arguments, to the
execvp system call. If the attempt to execute the file fails, then your program should print an error message indicating the reason for the failure.
stdin is redirected to a file that does not exist, then your program should print an appropriate error message.
stdout is redirected to a file that does not exist, then your program should create it. If the
stdout is redirected to a file that already exists, then your program should destroy the file's contents and rewrite the file from scratch. Your program should set the permissions of the file to 0600.
All child processes forked by your program should run in the foreground. Your program need not support background processes.
When the user types Ctrl-c, Linux sends a SIGINT signal to the parent process and its children. Upon receiving a SIGINT signal:
The parent process should ignore the SIGINT signal.
A child process should not necessarily ignore the SIGINT signal. That is, unless the child process itself (beyond the control of parent process) has installed a handler for SIGINT signals, the child process should terminate.
When the user types Ctrl-\, Linux sends a SIGQUIT signal to the parent process and its children. Upon receiving a SIGQUIT signal:
The parent process should print the message "Type Ctrl-\ again within 5 seconds to exit." to the standard output stream. If and only if the user indeed types Ctrl-\ again within 5 seconds of wall-clock time, then the parent process should terminate.
A child process should not necessarily ignore the SIGQUIT signal. That is, unless the child process itself (beyond the control of the parent process) has installed a handler for SIGQUIT signals, the child process should terminate.
Your program should handle an erroneous line gracefully by rejecting the line and writing a descriptive error message to the standard error stream. An error message written by your program should begin with "
programName: " where
argv, that is, the name of your program's executable binary file. Note that
argv typically will be
ish, but need not be so.
The error messages written by your program need not be identical to those written by the given
sampleish program. However, the error messages written by your program should be should be at least as descriptive as those written by
Your program should handle all user errors. It should be impossible for the user's input to cause your program to crash.
Your program should contain no memory leaks. For every call of
calloc, eventually there should be a corresponding call of
free. More specifically, your program should produce a clean Meminfo report when the user terminates your program by typing Ctrl-d. It need not produce a clean Meminfo report when the user terminates your program by issuing the
exit command or by typing Ctrl-\ twice within 5 seconds.
Test your program by creating multiple files containing lines which your program should interpret, and by executing your program with each file as the lone command-line argument. The file
/u/cos217/Assignment7/ishconfig contains a sequence of commands that can serve as a minimal test case. You should develop many more test files.
Of course you also should test your program manually by typing commands at its prompt.
Develop on hats. Use
emacs to create source code. Use
make to automate the build process. Use
gdb to debug.
An executable version of the assignment solution is available in
/u/cos217/Assignment7/sampleish. Use it to resolve any issues concerning the desired functionality of your program. The
/u/cos217/Assignment7 directory also contains the interface and implementation of the
DynArray ADT that we discussed in precepts. You are welcome to use that ADT in your program.
You should submit:
Your source code files.
makefile. The first dependency rule should build your entire program. The
makefile should maintain object (.o) files to allow for partial builds, and encode the dependencies among the files that comprise your program. As always, use the
gcc217 command to build.
readme file should contain:
A description of whatever help (if any) you received from others while doing the assignment, and the names of any individuals with whom you collaborated, as prescribed by the course "Policies" web page.
(Optionally) An indication of how much time you spent doing the assignment.
(Optionally) Your assessment of the assignment.
(Optionally) Any information that will help us to grade your work in the most favorable light. In particular you should describe all known bugs.
Submit your work electronically via the command:
submit 7 readme makefile allsourcecodefiles
If you use the
DynArray ADT from precepts, then submit the
We will grade your work on quality from the user's and programmer's points of view. From the user's point of view, your program has quality if it behaves as it should. The correct behavior of your program is defined by the previous sections of this assignment specification and by the given
sampleish program. Remember to use
meminfo to help you make sure that all dynamically allocated memory is freed.
From the programmer's point of view, your code has quality if it is well styled and thereby easy to maintain. In part, good style is defined by the
critTer tools, and by the rules given in The Practice of Programming (Kernighan and Pike) as summarized by the Rules of Programming Style document. The more course-specific style rules listed in the previous assignment specifications also apply. Proper function-level and file-level modularity will be a prominent part of your grade. To encourage good coding practices, we will deduct points if
gcc217 generates warning messages.
If your program works only through the lexical analysis phase, then leave code in your program that prints the token array that the lexical analysis phase creates. Doing so will enable us to assign partial credit for your successful implementation of lexical analysis.
Similarly, if your program works only through the syntactic analysis phase, then leave code in your program that prints the command that the syntactic analysis phase creates. Doing so will enable us to assign partial credit for your successful implementation of syntactic analysis.
Remember that the Supplementary Information page lists detailed implementation requirements and recommendations.
This assignment was written by Robert M. Dondero, Jr. with contributions by many other faculty members.