CSCI 320 - Operating Systems & Concurrency
Spring 2019
Rush - the Rust Shell, part 2
Assignment
In this assignment, we continue implementing an interactive
Unix shell
using the Rust language. Use nix, which is a Rust binding to the basic Unix system calls. Each system call has a corresponding (safe) Rust wrapper.
Implement your shell according to the following steps:
- If the line ends with the
&
symbol, it should run in the background. That is, your shell should not wait for it to terminate; the
command line should immediately return. Your shell should print the PID of
the process, so that the user may later manage it as needed. This is typically used for long-running
programs that perform a lot of computation. It is most often used in conjunction with output redirection, as described in step 2.
- If (prior to the
&
symbol, if present) the line ends with a >
symbol that is immediately followed by a string, the command's output goes to the file named by the string. If the file does not exist, it will be created.
- The line may contain two or more commands connected with the pipe symbol
(
|
). If this happens, start a process for each command, setting
up pipes to send the output of each left-hand command to the input of the
following right-hand command. If the input has been redirected by the <
symbol,
then the leftmost process will receive the input from that file.
- If (prior to the first
|
symbol, or if no pipes are present, prior to the >
symbol and &
symbols, if present) the line ends with a <
symbol that is immediately followed by a string, the command's input comes from the file named by the string. If the file does not exist, the command will be aborted.
Examples
-
ls -l | grep Cargo | wc
: Long-lists files in the current directory, includes only
the ones with the Cargo substring, and word-counts them.
-
big_calculation > results.out &
: Runs the program big_calculation
, storing the results in a file called results.out
and running in the background.
-
cat < input.txt > output.txt
: Copies the contents of input.txt into output.txt.
-
grep log < results.out | wc -l
: Counts the number of lines in the file results.out containing the string log.
Pseudocode for processing a command
The following pseudocode represents one possible algorithm for processing a command. Feel free
to borrow any ideas from this algorithm that you find helpful. Note that redirecting standard output
can make debugging difficult; I recommend using
eprintln!
,
which prints to standard error.
- Fork the shell process.
- Parent (i.e., the shell):
- If this is to be a background process, notify the user of the child PID and return.
- If not, wait for the child process to complete.
- Child (i.e., the command):
- If we are redirecting output, open the output file for writing and redirect standard
output to go to that file.
- If the command contains pipes:
- Create a mutable variable to track the output file descriptor for each process. By
initializing it to 1, by default this will be standard output (or whatever file output
has been redirected to).
- Iterate over all of the commands in the pipe (except the starting command) in reverse
order.
- Create a pipe. Its output will be the input for the current command. Its input will
be the output for the next command.
- Fork:
- Parent (i.e., the current command):
- Close the pipe's input.
- Redirect process output to the output file descriptor mentioned earlier.
- Redirect process input to the pipe's output.
- Execute the command.
- Child (i.e., all preceding commands)
- Close the pipe's output.
- Copy the pipe's input file descriptor in the output file descriptor variable mentioned earlier.
- Once the loop is finished, our current process will be the host for executing the command
at the start of the command line. Redirect its standard output to the output file
descriptor variable, so that it sends its data to the start of the first pipe.
- If we are redirecting input, open the input file for reading and redirect standard input
to come from that file.
- Execute the command at the start of the command line.
Grading
The assignment is worth 30 points:
- Background processes: 5 points
- Output redirection to file: 5 points
- Input redirection from file: 5 points
- Pipelining: 15 points
No credit will be given for code that fails to
compile. Partial credit may be given for incomplete or flawed versions of a
given step.
Acknowledgement
This assignment was adapted from materials developed by David Evans at the University of Virginia.