Project 1: Command Line Shell

Update: Make sure that you read the frequently asked questions about this project.

Project Goals

In this project, you will:
  1. refresh your knowledge of plain C structures and functions.
  2. understand and use basic Unix process management interfaces.
  3. learn the relationship between user programs, system tools, and the kernel.

Introduction

In this assigment, you will build a simple command line shell similar to the everyday UNIX tools bash or tcsh. The job of a shell is to transform typed instructions into running programs. When you type in a plain command, the shell will create a new process, invoke the external command, and then wait for its completion. Although a real shell has hundreds of bells and whistles, your shell will only have a few extra features.

Here is an example of how your shell should work: (italics indicate typed commands)

% ./myshell
shell> echo hello world!
hello world!
shell> emacs test.txt &
process 3456 started
shell> mozilla &
process 3457 started
shell> cp test.txt newfile.txt
shell> cd /tmp
current directory is /tmp
shell> wait
process 3457 ended
process 3456 ended
shell> exit
%

This project isn't as complicated as it sounds. The operating system will take care of most of the hard parts, you just have to understand and use the underlying facilities. You don't have to implement all of the complicated features found in real shells, such as pipes, redirection, and so forth. However, your program does need to be robust: when errors of various kinds are found, a sensible error message should be printed, and the shell should continue on normally.

Technical Requirements

Your code must be written in C (not C++) and contained in a single file called myshell.c. The main function should be an infinite loop that reads one line, splits the line into words, and then takes the necessary actions. One or more spaces marks the boundary between words on a line. The first word indicates the name of an external process or an internal command to run. (To keep things simple, you may assume a fixed maximum of 1024 characters on a line.) Your shell should work with any arbitrary external commands, but you should be sure to test the following:

ls, who, date, cat, cp, emacs, echo, grep.

If the command is typed normally, your shell should create a new process by calling fork() and then run the named program with its arguments by calling execvp(). The parent process should then wait for the newly-created process by calling waitpid(). If the command ends with the character & (ampersand), then the shell should not call waitpid() but simply allow the child process to run in the background while the user is allowed to execute more commands. In addition to external commands, your shell should also check for the following commands and handle them internally:

wait - This internal command should cause the shell to wait for all previously-started background processes to complete.
cd - This internal command should use the system call chdir() to change the working directory of the shell to the directory named by the first argument.
exit - This internal command should cause the shell to exit after all background processes have completed.

In addition to running interactively, your shell should also be able to run using a file as input. For example, if I place commands into the file foo:

echo hello world!
Then I should be able to run the shell, using that file as an input to the shell with the < (less-than) redirection character:
% ./myshell < foo
shell> hello world!
Note that your shell does not need to open a new file to handle this case; simply use fgets() as if you were reading from the console. The trick is that you must be careful to check for an end-of-file (EOF) condition on the input. EOF is indicated when fgets() returning null. If your shell receives an EOF, then it has come to the end of the input file and should exit cleanly.

You must be very careful to check for errors in every aspect of the shell. Note that most Unix system calls use a return value of -1 to indicate failure. If a system call fails, the global variable errno is set to an integer error code, and strerror(errno) points to a string describing that error code. If an error occurs, you should be sure to detect it and print out a reasonable error message.

For example, if the user attempts to run a program that does not exist, your shell should print an error similar to the following:

shell> badprog
badprog: No such file or directory.
There are many other places that errors could occur in the shell. In systems programming, almost any function can return an error. You must code carefully, checking for errors at every stage. When something goes wrong, your shell should produce a suitable message and continue on if possible. We will hold you to this by testing your shell with all manner of bad inputs.

Technical Hints

In order to carry out this project, you will need to refresh your knowledge of C. For this purpose, you should have acquired a book on C by now. I strongly recommend "The C Programming Language", which is mentioned in the syllabus. It may also be helpful to read and understand various Unix manual pages regarding process management, string manipulation, and so forth. Note that the manual pages are good for references purposes, but are no substitute for a book.

Here are some hints to get you started.

For reading input and printing output, you can use:

  • fgets(3)
  • printf(3)
  • For splitting input lines into words, you can use:
  • strtok(3)
  • strcmp(3)
  • strlen(3)
  • For creating and managing Unix processes, you can use:
  • fork(2)
  • execvp(3)
  • wait(2)
  • For dealing with error messages, you can use:
  • errno(3)
  • strerror(3)
  • Note that, by custom, a Unix call is appended with a number indicating what section of the online manual it is contained in. Manual section 1 contains shell commands, section 2 contains system calls, section 3 contains library functions, and there are a few more sections. For example, fork(2) means that fork() is a system call described in section 2 of the manual. You can read the manual page for any command or function by running the command:

    % man [section] <name>
    
    The section number is optional. If left out, man will search section 1, then section 2, and so forth. This can cause problems if you are looking for a system call with the same name as a shell command. For example:
    % man printf
    
    will yield printf(1), a shell command, which is not what you want. However:
    % man 3 printf
    
    will show you the printf(3) family of standard library calls, which is what you really want to see. Usually, the man pages you want are found in sections 2 and 3.

    Handing In

    This project is due at 5PM on Feb 3, 2005. Late assignments will not be accepted!.

    Your entire shell program should be contained in a single file myshell.c. To hand in this file, simply copy the source file and nothing else into /afs/nd.edu/coursesp.05/cse/cse341.01/dropbox/YOURNAME/p1. We will compile and test your code on the Fitzpatrick cluster, so be sure to build and test your code on those machines.

    The grade on this project will take into account the correct functioning of your shell (50%), the ability to handle errors cleanly (30%), and good coding style (20%). Be sure to test your shell carefully using a variety of inputs.


    Notre Dame - CSE Dept - CSE 341