Princeton University
COS 217: Introduction to Programming Systems

Assignment 7: A Unix Shell


Purpose

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.


Background

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

Your task in this assignment is to create a program named ish. ish should be a minimal but realistic interactive Unix shell. A Supplementary Information page lists detailed implementation requirements and recommendations.


Initialization and Termination

When first started, ish should read and interpret lines from the file .ishrc in your HOME directory, provided that the file exists and is readable. Note that the file name is .ishrc (not ishrc), and that it resides in the user's HOME directory (not the current/working directory).

To facilitate your debugging and our testing, ish should print each line that it reads from .ishrc immediately after reading it. ish should print a percent sign and a space (% ) before each such line.

ish should terminate when the user types Ctrl-d or issues the exit command.


Interactive Operation

After startup processing, ish repeatedly should perform these actions:


Lexical Analysis

Informally, a token should be a word. More formally, a token should consist of a sequence of non-whitespace characters that is separated from other tokens by whitespace characters. There should be two exceptions:

ish should assume that no line of standard input contains more than 1023 characters; the terminating newline character is included in that count. In other words, ish should assume that a string composed from a line of input can fit in an array of characters of length 1024. If a line of standard input is longer than 1023 characters, then ish need not handle it properly; but it should not corrupt memory.


Syntactic Analysis

A command should be a sequence of tokens, the first of which specifies the command name.

The '<' token should indicate that the subsequent token is the name of a file. ish should redirect the command's standard input from that file. It should be an error to redirect a command's standard input more than once.

The '>' token should indicate that the subsequent token is the name of a file. ish should redirect the command's standard output to that file. It should be an error to redirect a command's standard output more than once.


Execution

If the command is an ish built-in, then ish should execute it directly (i.e. without forking a child process). ish should interpret four shell built-in commands:

setenv var [value] If environment variable var does not exist, then ish should create it. ish should set the value of var to value, or to the empty string if value is omitted. Note: Initially, ish inherits environment variables from its parent. ish should be able to modify the value of an existing environment variable or create a new environment variable via the setenv command. ish should be able to set the value of any environment variable; but the only environment variable that it explicitly uses is HOME.
unsetenv var ish should destroy the environment variable var.
cd [dir] ish should change its working directory to dir, or to the HOME directory if dir is omitted.
exit [arg] ish should exit with arg as the exit status, or with 0 as the exit status if arg is omitted. ish should make sure that arg is numeric.

Note that those built-in commands should neither read from standard input nor write to standard output. ish should ignore file redirection with those built-in commands.

If the command is not an ish built-in, then ish should consider the command name to be the name of a file that contains executable binary code. ish should fork a child process and pass the filename, along with its arguments, to the execvp() system call. If the attempt to execute the file fails, then ish should print an error message indicating the reason for the failure.

If standard input is redirected to a file that does not exist, then ish should print an appropriate error message.

If standard output is redirected to a file that does not exist, then ish should create it. If standard output is redirected to a file that already exists, then ish should destroy the file's contents and rewrite the file from scratch. ish should set the permissions of the file to 0600.


Process Control

All child processes forked by ish should run in the foreground; ish need not support background process control. However, the user must be able to kill the current child processes by sending it a SIGINT signal, typically by typing Ctrl-c. A SIGINT signal should not kill ish itself.


Error Handling

ish 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 ish should begin with "programName: " where programName is argv[0], that is, the name of the ish executable binary file. Note that argv[0] typically will be "ish", but need not be so.

The error messages written by ish need not be identical to those written by the given sampleish program. However, the error messages written by ish should be should be at least as descriptive as those written by sampleish.

ish should handle all user errors. It should be impossible for the user's input to cause ish to crash.


Memory Management

ish should contain no memory leaks. For every call of malloc() or calloc(), eventually there should be a corresponding call of free(). More specifically, your ish should produce a clean meminfo report when the user terminates ish by typing Ctrl-d. It need not produce a clean meminfo report when the user terminates ish by issuing the exit command.


History Mechanism (extra credit, up to 10 points)

ish should support a history mechanism that includes:

ish need not support editing of the previously issued command.

ish need not retain the history of previously issued commands between sessions. That is, ish need not maintain the history of previously issued command in a file.


Testing

Test ish by creating multiple files containing lines which ish should interpret, repeatedly copying each to the .ishrc file in your HOME directory, and restarting ish. The file /u/cos217/Assignment7/.ishrc 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 ish manually by typing commands at its prompt.


Logistics

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 ish.

You should submit:

Your readme file should contain:

Submit your work electronically via the command:

submit 7 makefile readme allsourcecodefiles

If you use the DynArray ADT from precepts, then you should submit the dynarray.h and dynarray.c files.


Grading

We will grade your work on quality from the user's point of view and from the programmer's point of view. From the user's point of view, your program has quality if it behaves as it should. The correct behavior of the program is defined by the previous sections of this assignment specification and by the given sampleish program. From the programmer's point of view, your program has quality if it is well styled and thereby simple to maintain. See the specifications of previous assignments for guidelines concerning style. 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. Remember that the Supplementary Information page lists detailed implementation requirements and recommendations.