Assigned: Tuesday, Feb 2, 2016
Due: Monday, Feb 8, 2016 at 10:30pm
Collaboration: Work with your assigned partner for this lab. You may use your classmates and their code as a resource, but please cite them. Sharing of complete or nearly-complete answers is not permitted.
In this lab, you will implement a simple “shell” program. A shell is an interactive program that allows you to start and manage other programs, serving as a sort of intermediary between you, the OS, and the running processes. When you run a program in the terminal, you are actually interacting with the shell.
The shell you write for this program (called mysh) will support basic functionality that allows you to run programs in the foreground and background, and to move around the filesystem.
The starter code for this lab is available on GitHub at grinnell-cs/213-shell. To start out, fork this repository and add your lab partner as a collaborator.
As you work through this lab, make sure you understand and handle all of the error conditions for the POSIX functions you call. Testing some error conditions is very difficult, so this will mostly depend on a careful reading of the relevant manpages.
The starter code includes a simple command prompt loop that reads in a line of text and prints it back out.
The first step in actually executing these commands is to break them into pieces.
Take a look at the manpage for execvp, which is the variant of exec we will use for this lab.
This function takes in a command name or path and an array of arguments than ends with a NULL argument.
Write code to break the command line string into an array of char*s ending with a NULL entry.
You can do this with repeated calls to strtok, strtok_r, or strsep.
You do not need to support quoted arguments in your shell.
Now that you can read in and break apart commands, use fork to create a child process, execvp to launch the command in the child process, and wait to have the shell wait for the child process to complete.
The reason we are using the execvp function for this lab is so we do not need to implement path resolution, the process of searching through directories in the PATH environment variable to find an executable that matches the given command name.
The convention for exec is that the first argument passed to the program is the name of the program.
For example, the command ls /home/curtsinger runs the program /bin/ls with the arguments ls and /home/curtsinger.
When the child process completes, print the exit code for the child process before showing the command prompt again. For example:
> ls
Makefile README.md common.mk mysh mysh.c obj
Child process 12802 exited with status 0
> grep
usage: grep [-abcDEFGHhIiJLlmnOoqRSsUVvwxZ] [-A num] [-B num] [-C[num]]
[-e pattern] [-f file] [--binary-files=value] [--color=when]
[--context[=num]] [--directories=action] [--label] [--line-buffered]
[--null] [pattern] [file ...]
Child process 12804 exited with status 2
While the command cd is actually an executable in /bin, this won’t work for our shell;
when you run the command you do change directories, but this doesn’t change the working directory of the parent process (your shell).
Instead, your shell will need to check if the current command is cd.
If it is, your shell should call the chdir function to change directories instead of calling fork and exec to run /bin/cd in a child process.
You should also add special handling for blank lines (which should do nothing) and the exit command.
Most shells also allow you to invoke multiple commands in sequence using a semicolon.
For example, cd ..; pwd; ls will move up a directory, print the full path of that directory, then print the files in that directory.
Modify your shell to support multiple commands chained together with a semicolon.
Hint: You will need to revisit your command parsing code from part A to break a command line string into commands separated by semicolons.
Add support for background commands, which are launched with the & symbol.
The most common use case for background commands is to launch a single executable without blocking the current shell.
For example, the command sleep 5 will block the shell for 5 seconds before a prompt is printed again, while sleep 5 & will run the sleep command in the background and allow you to run additional commands immediately.
It is also possible to run multiple commands with a single invocation separated by an ampersand, much like a semicolon. Unlike the semicolon separator, the ampersand will run the joined commands simultaneously instead of in sequence. You can also combine semicolon and ampersand delimited commands, with the following rules.
Hint: Don’t pass the ampersand or semicolon separator as an argument to the child process or built-in command.
When you have one or more background commands running, the shell should print the exit status of any command that quits between command invocations. For example:
> sleep 1 & sleep 1; sleep 1
(shell pauses for 1 second here)
Child process 12805 exited with status 0
Child process 12806 exited with status 0
(shell pauses for 1 second here)
Child process 12807 exited with status 0
>
You can check for this behavior with a simple test in a real shell, as well as your mysh implementation.
Type the command sleep 1 & and hit enter.
Then, sit on the blank command prompt for well over one second.
You should not see any indication that the one second sleep has finished.
Then, hit enter to run a blank command.
Before the next prompt appears, you should see the exit status of the sleep command.
You can use the wait function to block until a child process quits (a semicolon command), but you will need to use waitpid with the WNOHANG flag to check for any completed background processes.
With this flag, the function will return immediately and indicate whether there are any additional zombie processes to collect.
Do this repeatedly until there are no more zombies, then move on to the next command or prompt for a new one.
Another important feature of a shell is the ability to compose commands to perform more complex operations. You will receive extra credit for the following features:
`) to use the output of that command as part of another command line.<, or the output of a command to a file with >.|.You do not have to implement all of these features to earn extra credit.
To implement these features, you may need the functions dup2, open, close, and pipe.
These use some file-related functionality that we haven’t talked about in class.
I am happy to discuss these with you during office hours.