This assignment provides practice in writing systems programs that make
Unix system calls.
Problem Specification
In this assignment, you will develop a micro shell. Like a normal shell,
the micro shell will 1) display a prompt, 2) accept user commands, and
3) execute them. The shell will be like a real Unix shell (e.g., Csh),
but will be much simpler.
The overview of the shell functioning is as follows:
At startup, the micro shell will display the prompt string: >
After displaying the prompt, it will read a single command line,
parse it, and execute the command.
A command line has the following syntax: command [arguments]
When a valid command is entered by the user, the shell will execute
the command. During the execution, the shell will not accept any more
commands (i.e., all commands are run in the foreground).
The shell will return an appropriate error message when the command
specified is invalid, or when there are problems with either the
arguments or the execution of the command.
Once the command has completed (i.e., it has executed or the appropriate
error message has been reported to the user), the shell will re-display
the prompt, and the whole cycle repeats.
The shell will handle a variety of commands, which are similar to Unix shell
commands. The code that you write should also be similar to that in Unix
shells. The commands to be implemented are given below:
Basic Commands
The basic commands require little programming effort since there are system
calls to do the work. You must "bullet-proof" the commands to handle error
checking. You are not allowed to use system() in your implementation.
display filename
prints the contents of a file on the screen
copy source destination source is the name of a pre-existing file; destination is a
filename different from source. This creates a new file with the name
given in destination and the same contents as the source file.
fileinf filename
displays the essential information about the file specified,
or of the current directory if invoked without argument;
it must list at least the following:
the full path of the file
the size of the file
creation and last modification dates
the first 10 lines of the contents of the file
dirinf directoryname
displays the essential information of the directory specified,
or of the current directory if invoked without argument;
it must list at least the following:
the full path of the directory
all subdirectories and files contained in the directory
the total space occupied by the directory and its contents
deldir directoryname
deletes the directory and all its contents; since this is a
potentially disastrous operation, the user should be asked
to confirm the operation
quit
this will quit the shell.
Implementation Notes
Each of the shell commands should be implemented as a procedure (function).
The main program is a loop: wait for the next command, parse it, and execute
it. You must implement error handling. Note that the error-handling aspect
of the problem is not precisely specified, i.e., the specification is left open.
Nonetheless, it is expected that your code will consider all error conditions
and take take appropriate action. There are two key considerations here:
1) the shell should not crash when the input is invalid, i.e., your shell
program must be bullet proof, and 2) the shell's response to errors must be
user friendly and intuitive. You may use the perror call for reporting
errors, but may want to catch and handle certain kind of errors directly.
Directory Listing
The next command you will implement will be the directory-listing command,
which has the following syntax:
dir [-l]
The command works like the ls command in Unix, and has a similar output.
The output should be formatted to look like the output of /bin/ls without
the optional -l, and like /bin/ls -gF with the optional -l. For the directory
listing, you need only worry about regular files and directories, not special
files. This command will require you to make use of various system and
library calls. You will need the stat system call to get information about
a file. You will also need to consult the include file
/usr/include/sys/stat.h, which has some useful macros for this command. You
will also need the getpwuid library call to find information about a user.
Executing Programs
So far, our microshell can only execute "built-in" commands. One of the
tasks of a shell is also to execute user programs. This is also a useful
mechanism for extending the functionality of the shell, since you can write
programs that execute specific commands, and these programs extend the
capability of the shell.
In order to execute user programs, the shell will use the fork and
execlp (or execvp) system calls. When the command line does not
represent one of the built-in commands of the shell, then the shell should
attempt to see if the command is a user program. If so, it should execute
the command in a separate child process, using a combination of fork() and
exec(). The shell, which is the parent process after fork(), should wait
for the child process to terminate before displaying the prompt again. The
shell should also check for errors in finding and executing the child program
and report them appropriately to the user.
Process Synchronization
This is a small example (separate from the shell above) that helps you understand
the coordination of the activities performed by two processes.
Two processes P1 and P2 are running concurrently on the same machine.
P1 and P2 communicate through a shared memory. First P1, sends P2 a file
name (of an ASCII file) that P2 opens and reads. As P2 reads the file, it
sends its content to P1 10 characters at a time. Since the two processes
have different execution speeds, they should be synchronized (in your
implementation, insert some artificial delays in P1 or P2 to make make sure
that their speeds differ substantially). To synchronize them, use a semaphore.
When the transfer is over, P1 or P2 stops the communication (you should know
which process stops it).
Last modified: Thu Jan 20 12:45:39 EST 2000