Lab: A Simple Shell

CSC 213 - Operating Systems and Parallel Algorithms - Weinman



Summary:
You will create your own interactive shell program.
Assigned:
Tuesday 9 September
Due:
11:30 PM Monday 15 September
Objectives:
 
Collaboration:
You will complete this lab in teams as assigned by the instructor.

Background

The Unix shell is a familiar mechanism for interacting with a computer. While modern Unix shells include extensive capabilities, a simple shell facilitates two types of interactions:
  1. setting or modifying environment variables, such as the current working directory or the search path, and
  2. locating and executing programs.
You should be familiar with the user's view of the shell from section 5.4 in your text, and from your experience using it in CSC 161 and this class. In this lab, you will get a taste of the system programmer's view of the shell.

Exercises

Preliminaries

Do this laboratory on any MathLAN workstation. Copy the starter files to somewhere in your home directory.
cp -R ~weinman/public_html/courses/CSC213/2014F/labs/code/shell ~/somewhere/
You will use these files in the exercises below.

Part A: Exploring the launcher

launch.c is a program that, given the name of a file, executes all of the commands listed in that file. This program does many of the things your shell will need to do, but not all of them.
Examine the commands listed in the launch_set file, and try executing them yourself (i.e., one by one in bash) to see what the results should be.
Then compile and run the program from the shell:
make
./launch launch_set
Kind of confusing, isn't it?
Read the source code and answer the following questions, which indicates one bug for you to fix in launch.c. For the following questions, keep your answers concise and to the point; 2-3 sentences should suffice.
  1. MAX_LINE_LEN and MAX_ARGS are defined, but there is no comparable definition for the maximum length of a single argument string. Is that ok? Why or why not? (Hint: See the manpage for strsep(3).)
  2. Try adding a blank line to the launch_set file and running the launcher. Something bad will happen. Explain and then fix this bug. (When fixed, all of the commands in the file should be executed without error.)
    You can test your bug fix using the command
    ./launch -test all
  3. Explain what the return value of fork() means and how this program uses it.
  4. What would happen if this program did not use the fork function, but just called execv directly? (You can test your hypothesis: Try changing line 100 from 'pid = fork();' to 'pid = 0;')
  5. What is the role of the status variable?
  6. Run
    ./launch launch_set
    several times. You should see some variation in the output. What is happening? Why?
  7. Try adding a nonexistent command (e.g., fasdfjaskf or argelfraster) to the launch set. What happens? Why?
  8. Try adding the command 'cd ..' to the launch_set file. What happens? Why? (Hint: Learn what the which(1) command does; then try typing 'which cd' in your bash shell at the terminal.)
  9. Given what you learned in A.6, is the behavior you saw in A.8 appropriate? Is the cd command useful in the launch program? Why or why not?

Part B: Implementing the simplest shell

Based on what you have learned from reading launch.c, implement a simple shell. The file shell.c provides a skeleton file in which to write your program. Your program should: Here are some more detailed requirements.
Command-line arguments:
The simplest version of the shell should only accept the optional -test command-line argument, as given in the skeleton file and shown below.
The prompt:
The prompt should be of the form 'Shell(pid=XXXX)> ', where XXXX is the shell's process id.
Parsing and executing the command:
Parse the command and execute it in a child process, as in your modified launch.c. Your program should not crash no matter what the user types (including a blank line).
Waiting:
The shell should not print a new prompt or accept any more commands until the last command has finished executing. Commands should execute strictly in the order they are given.
Exiting:
The shell should exit when the user types the 'exit' command or when the user presses Control-D to signify the end of the input (EOF).
You can test your code using
./shell -test shell-b
Here is an example run:
nelson$ ./shell
Shell(pid=8604)> /bin/ls
Acknowledgments.txt launch_set Makefile shell_tests.h testrunner.o
launch_seta shell shell_tests.o
launch launch_tests.c shell.c smp1.in
launch.c launch_tests.h shell.o testrunner.c
launch.o launch_tests.o shell_tests.c testrunner.h
Shell(pid=8604)> /bin/echo hello world!
hello world!
Shell(pid=8604)> /bin/pwd
/home/davisjan/csc/213/sandbox/simple_shell
Shell(pid=8604)> ./shell
Shell(pid=8609)> /bin/pwd
/home/davisjan/csc/213/sandbox/simple_shell
Shell(pid=8609)> exit
Shell(pid=8604)> 
Shell(pid=8604)> 
Shell(pid=8604)> exit
nelson$ 

Part C: Enhancing the shell

Once you have implemented the simplest shell, enhance it in the following ways.
  1. The launch program requires users to give the full path for programs to execute as commands, such as '/bin/ls'. If you've followed the approach suggested above, then so does your shell. Modify your shell so that users can type shorter command names, such as 'ls', for programs that can be found through the PATH environment variable. (Hint: Read the manpage for execv(3) to learn about other variants on this system call.)
    (Test: ./shell -test path)
  2. Implement the cd command, so that the user can type commands such as 'cd ..' to change the shell's working directory. Be sure to print an error message to stderr if changing the directory fails. (Hint: This must be implemented as a built-in command evaluated in the parent process, rather than forking a child process. Do you see why?)
    Handle the arguments to cd as follows:
  3. Modify the shell so that it uses waitpid rather than wait to wait for the most recent child process to exit. If the child process terminates with a status other than EXIT_SUCCESS, the shell should print to stderr a message of the form 'shell: Process %1 exited with status %2', where %1 is the pid and %2 is the status. (Test: ./shell -test error)
Here is an example run with these enhancements:
nelson$ ./shell
Shell(pid=13365)> pwd
/home/davisjan/csc/213/2010F
Shell(pid=13365)> whoami
davisjan
Shell(pid=13365)> which whoami
/usr/bin/whoami
Shell(pid=13365)> cd /usr/bin
Shell(pid=13365)> pwd
/usr/bin
Shell(pid=13365)> cd 
Shell(pid=13365)> pwd
/home/davisjan
Shell(pid=13365)> pwd -q 
pwd: invalid option -- q
Try `pwd --help' for more information.
shell: Process 13372 exited with status 256
Shell(pid=13365)> exit
nelson$
Note: As in Lab 1, you should remove or disable (e.g., with #ifdef) any additional debugging output you may have created before running the tests or submitting your lab.

What to turn in

Extra Credit

Acknowledgment:

This lab was adapted by Janet Davis with portions from several sources: