CSCI 340: Operating Systems
Fall 2007
Program 3: Command Shell
Code due: Midnight Thursday November 8 (see below)
Grading Weight: 4 (programs will have a weight between 1 - 5)
NOTE: if you turn the assignment in by November 1 (the original deadline) you get 15% extra credit.
Overview:
Write a command shell like the Bash shell. Your program will display a prompt and execute commands the user types.
It must implement pipelines (the | operator in the Bash shell). For example, your program will display a prompt
("$ ") and the user can enter a command of this form:
$ ls
-l | sort -k 4
In this example, your shell
will create two processes, one to execute ls and one to execute sort. The output of ls will be piped to the input of sort.
The shell must also handle file
redirections (the < and > operators in the Bash shell). For
example,
$ sort < input_filename | head -5 >
output_filename
You may work with one other
student in the class. However, you must work together (sit right
next to each other) at least 90% of the time. To do otherwise
would be considered cheating.
Requirements:
The shell must continue to accept
command lines until the user enters exit.
At this point the shell must terminate.
The shell must fork a new process
for each command. It must not fork any more processes than are
needed. The shell must call wait() on all the processes it creates (you can run into problems if you fail to do this).
The shell must be able to handle up
to 100 commands piped together on a single line:
$ cmd1 | cmd2
| ... | cmd100
Each command must be able to accept
up to 100 arguments:
$ cmd1
arg1 arg2 ... arg100 | cmd2
arg1 arg2 ... arg100 | ... | cmd100 arg1
arg2 ... arg100
Files can only be redirected
into the first command. Only output from the last command can be
redirected to a file.
If no file is redirected into the
first command, it should read from
standard input. If the output of the last command is not
redirected to a file, it should be written to standard output.
Your shell must handle commands with
full pathnames (e.g. /bin/ls) and commands without a full pathname (e.g. ls). For
the commands without a path look in every directory in the PATH environment
variable until you find a match (just the bash shell). You must do this
yourself, you may not call execvp() which does this for you.
Command names and arguments must be separated by a space on the command
line. However, the special characters (<, >, |) do not have
to be separated by spaces. The following two inputs should work
the same:
$ sort<input_filename|head -5>output_filename
$ sort < input_filename | head -5 >
output_filename
You do not have to recover from user
errors. If the user enters a command line that contains an error
(such as a command that does not exist, or a file that cannot be
opened) print an error message and exit your program.
You may implement this using either
C or C++. Use the .c extension for C files and .cpp for C++ files. The shell must compile using gcc if
you use C or g++ if you use C++.
You must provide a Makefile that creates an executable named shell.
That is, when I type make in
the directory that contains your files, an executable named shell will be
created.
You must write this program from scratch. Unlike the chat
program, you may not borrow any code.
Using anyone else's code
(including copying the code out of a book) will be considered
cheating. If you are working with someone and
he or she cheats, you will also be charged with cheating.
Hints:
Check the value returned from ALL system calls.
The file pipes.cpp
contains examples of most
of the system functions you will have to call. You will also need
to call fopen() and fileno() (to
implement redirection).
The system call getenv("PATH") will return a string containing the
value of the PATH environment variable. This string is a colon
separated list of directories. You can use the strsep() function
to break it into separate tokens. See strsep.c for an example of how to use it.
Be careful with fork(). If you
have a bug in your code and fork()
a large number of process, you will crash the machine. If you are
working on tiglon or jaguar everyone will be
mad at you. The following code illustrates a common mistake:
child_pid = fork();
if (child_pid == 0)
{
cout << "hello from child" << endl;
// should call exit() here
}
child2_pid = fork();
if (child2_pid == 0)
{
cout << "hello from child 2" << endl;
// should call exit() here
}
In the above code, both the parent and the first child call the fork()
for child2. Make sure that all children call exit() when they are
done executing their code.
Since the special characters (<,>, |) do not have to have spaces
before or after them, you need to preprocess the input line to add
these spaces. Once you add the spaces you can use strsep() to
break it into tokens (see strsep.c).
The Unix command ps -lf -u username (where username is
your username) will tell you all the process you have running.
You can kill one (or more) of these processes using the command kill -9 pid (where pid is one or more of the numbers in the PID
column of the output of ps).
If you are desperate (processes forking faster then you can stop them),
do the following (username is your username):
$ pkill -24 -u username
log in again because the above will have stopped the shell process you
were using
$ pkill -9 -u username
log in again because the above will have killed the shell process you
were using
Most students find this assignment very difficult. It is
surprisingly difficult to get everything straight, and it is a very
very hard program to debug, when it does not work there are very few
clues as to what is going wrong.
You must make sure you close all the pipes after the calls to dup2().
In other words, if you have 3 commands and create two pipes, all
three processes have to close both sides of both pipes, and the parent
must close both sides of both pipes (that is 12 calls to close).
If you forget a single call to close it can cause your program to
hang.
Sample
Test Cases:
Unix Filters
All the commands I use to test your
program will work like this:
while
(read(stdin, buf) == true)
{
process buf
write(stdout, buf)
}
-or-
while
(read(stdin, buf) == true)
;
process all the input
write all the output to stdout
Many Unix commands use one of these two forms. Examples of the
first are: grep sed awk. Examples of the second are:
sort wc
Since all the commands will work the same, you don't need to think
about what command I will use to test your program.
I will test the following aspects of your program:
connecting several commands
together: ls | sort
handling arguments: /bin/ls -l | sort -r -k 7
file redirection: ls | sort -r -k 7 | cat > foo
Knowing your program is correct:
You will know your program is
correct if it has the same behavior as the bash shell, that
is, type the same command to bash and your program. If you see the exact same results, your program is correct.
How to
turn in code:
Turn in all the files necessary to make your program
work including a Makefile. My solution contains the files:
shell.cpp
Makefile
Copy your files to the turn in directory for this
assignment.
See How
to turn in assignments for details on turning in assignments.
If you are working with someone, only turn in one copy of the
assignment. It is very helpful (but not mandatory) if people working together always
turn their assignments in under the same name. Make sure you list
both names at the top of all files.
Late
assignments:
You will lose 10% if your program is 1-24 hours late.
You will lose 20% if your program is 24-48 hours late.
Programs will not be accepted 48 hours past the deadline.
e-mail late assignments to me (avoid .zip files because they are usually removed by virus scanners).
It may take me longer to grade late assignments (if I have
already graded the other assignments, grading late assignments gets a
low priority).
I use the time I
receive your e-mail, not the time you send your e-mail as the turn in
time (sometimes e-mail from an ISP takes a day or two to be delivered). If you
want to be absolutely sure your assignment gets to me immediately,
e-mail it from your csuchico or ecst account.