Here are the partnerships and here's a link to GitHub.
A shell is a program that you use to run other programs. We've become accustomed to using the bash shell in our terminal windows, but there are many other shell programs, each of which tries to make it easy for its users to run programs. bash may now seem to you like the one true way of doing things, but if you stop to think about it I bet you will quickly think of ways you could improve bash. For this lab, we will build our own shell programs. Your shell probably won't be an improvement over bash (at least not yet) but it will be a full-featured programming environment that's actually useable in practice.
The main job of your shell program is to do the following:
Most commands correspond to a program that the user wants to run. The first part of such a command is the name of the binary executable file for the program. Some executables (like ls, vim, or make) can be referenced without needing to specify the directory where they live. With others (like the programs we've written this semester) we must specify the path to the executable, either relative to the current directory (./myprogram) or in absolute terms (/home/mauskop/cs31/myprogram). Running programs is easier than you might think. We just need to fork a new process and call execvp
with the name of or path to the executable. If there are command line arguments we also must pass these to execvp
.
There are a small number of commands that don't correspond to programs, but instead are implemented in the shell itself. Your shell should have four of these built-in commands:
In addition, your shell should allow its users to chose whether a program will run as a foreground process or background process. Running in the foreground is the default behavior. It means that the user can't invoke any additional commands until the process has exited. Running in the background means that the user can continue entering commands while the background process runs. We'll use the same convention as bash, and indicate a background process by ending the command line with &.
Your starting repository contains the following C files:
Read over all these files with your partner, but only modify the code in cs31shell.c and circularqueue.c. You should also modify circularqueue.h by adding comments.
Built-in commands should be impemented right in cs31shell.c. If the user enters a command that isn't a built-in, assume the command is the name of an executable. Fork a child process and use execvp
to run the executable in this child. If it's running in the foreground, the parent process (i.e. the shell) should wait for the child to exit before proceeding. If the user indicates that the executable should run in the background, the parent can proceed immediately, but when the child exits, it must be reaped in a signal handler. You should handle execvp
errors, which usually indicate that the user mistyped the name of the executable. See the lecture slides for more on fork
, execvp
, signal
, and waitpid
.
Here is an example run of a working shell program. Consult this and bash itself to get a sense of how your shell should behave in various circumstances.
waitpid
, except those that are still running in the background when the shell exits. (These become the responsibility of bash, i.e. the grandparent process.)execvp
gets a command that doesn't exist.chdir
function to change the directory.strcmp
function to compare string values.fflush(stdout)
after calls to printf
so that output shows up right away. This is helpful because there might be multiple processes printing simultaneously.strcpy
function to copy command lines into the history queue.make circularqueue.o
and then compile your testing code with the command gcc -g -o queuetester queuetester.c circularqueue.o
.parseCommand
you lose access to previous argv
lists. This is because the parsecommand library stores the argument strings from the most recent call in a single statically-allocated global character array. If you're curious about how this works, try the extra challenge.Implement the parsecommand library yourself in a file called parsecommand.c. Modify the Makefile so it compiles the shell using your source code instead of the publicly available parsecommand.o file. You can implement this library however you like as long as your shell program is still robust.
Our implementation uses a single statically-allocated global character array (of size MAX_COMMAND_SIZE
) to store a copy of the command line. It selectively replaces whitespace characters with null terminating characters (i.e. '\0'
) within this character array. This allows us to store multiple strings in a single array.
If you do the extra challenge please indicate this in the questionnaire.