Lab 4: Redirecting I/O
Overview
The purpose of this lab is to develop a working knowledge of UNIX system-level I/O and IPC via unnamed pipes. You will do this by extending the capabilities of the tiny shell you developed in the last lab.
Preliminaries
In this lab you'll be working in the labs/4_iolab directory.
As before, don't forget to commit your previous work and pull the latest changes from the central repository before starting!
You will be building on top of the tsh.c file you completed for the previous
lab, so the first thing you'll need to do is copy that file over the tsh.c
file present in the 4_iolab directory (which is just an empty
placeholder). The following command will accomplish this:
cp ../3_shlab/tsh.c .
If your previous shell implementation was buggy or incomplete, however, you
might want to start with a fresh version of the original tsh.c file given to
you for the shell lab. You can do that by checking out the appropriate version
from Git and copying it over — ask a TA or myself if you need help doing
this.
Note that we will not, except where necessary, be checking for functionality that should've been completed for the previous shell lab.
Extending the Shell
You will be adding input and output redirection with the ">", and "<" shell operators, and basic interprocess communication via unnamed pipes with the "|" operator.
To accomplish this, your implementation must be modified to:
- Detect when a command is entered that requires I/O redirection or piped IPC
- Open the required file for I/O or create an unnamed pipe for IPC
- Use
dup2to copy the newly opened file descriptor to the appropriate index number - Cleanup / Close all descriptors as necessary
To make your life easier (and reduce the amount of code you have to write), there are a number of simplifying assumptions you can make:
- You can assume that a given command uses no more than one of the
>,<, or|operators. E.g., you needn't support commands requiring both input and output redirection, or multiple pipes. - You don't need to worry about quoted command line arguments/characters -- this will make parsing the command line for the redirection/pipe characters much easier. (See the hint that follows for more information.)
We will not be checking backgrounded jobs this time around.
Your implementation does not need to gracefully handle malformed or illogical command line incantations. We will not, for instance, throw either of the following at your shell:
ls |file.txt | wc
Testing
As before, we provide trace files to test your I/O redirection and pipe
implementations. Use make test{01-04} to run the trace files on your shell. A
reference shell is not provided for this lab, but you can still use make
rtest{01-04} to run the trace files on the system shell to see the desired
output.
Grading
You can receive a maximum of 30 points on this lab:
- 10 points for input redirection
- 10 points for output redirection
- 10 points for pipes
Besides basic functionality, we will be checking for the following:
Commands with additional command line arguments (e.g.,
ls -a -l) can be used correctly to perform both I/O redirection and pipingAll open file descriptors are closed (by all processes involved) when they are no longer needed.
Proper synchronization semantics are observed — e.g., when two processes are connected with a pipe, the shell prompt should be printed only after both processes have completed.
Hints
This section gives you a few hints to get started with the lab. Skip it if you're feeling confident!
Opening and Creating Files
It's also important that you specify the correct flags to the "open" system call (which you'll need to open files for I/O redirection). When opening a file for input, you'll probably want to use the following:
open(filename, O_RDONLY);
but for output redirection, where you'll likely have to create a file and delete its existing contents, use:
open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
Where the second argument specifies that the file should be opened for writing
(O_WRONLY), that it should be created if it doesn't already exist (O_CREAT),
and that it should be truncated to size 0 before writing (O_TRUNC). The last
argument specifies that if the file is being newly created it should have its
read (S_IRUSR) and write (S_IWUSR) permissions set for the user, so that you
can examine the file after it's been created.
Look up the "open" manpage for
more details.
Remember to close the files when you're done! (In all necessary processes.)
Parsing multiple commands
When dealing with two commands that are separated by the pipe (|) character,
you're going to have to do more than just call parseline once, as you did
before.
The strchr function can be used to determine if a string contains a given
character, and the strsep function can be used to "split" the string (with a
"\0") on a specified token.
The following snippet of code demonstrates their combined use for parsing two separate commands joined with a pipe:
char *argv[MAXARGS]; char *p = cmdline; if (strchr(cmdline, '|')) { strsep(&p, "|"); // The '>' character in cmdline is replaced with // a '\0', and p points to subsequent character. // Now use parseline to parse the two halves of the command line parseline(cmdline, argv); printf("%s\n", argv[0]); // fork and exec the first process parseline(p, argv); printf("%s\n", argv[0]); // fork and exec the second process }
Note that you shouldn't need to call parseline a second time to handle input
and output redirection (the second half of cmdline will simply consist of a
file name, which you can manually remove whitespace from).