Processes (proc) Project
COP-3402
Table of Contents
1. Overview
In this project, you will develop a simple version of a command-line shell that can invoke programs and perform redirection and piping.
2. Input
USAGE: ./mysh
The program should be called mysh
. It takes no arguments. When running, it reads a command (or commands) from stdin. It executes the command(s) and then exits.
mysh
should support up to one pipe between two commands as well as redirection in and out (including both simultaneously) gfor any command.
2.1. Grammar
start ::= pipes "\n" pipes ::= command pipe* pipe ::= "|" command command ::= program in? out? program :: STRING STRING* in ::= "<" STRING out ::= ">" STRING
This grammar allows for any number of pipes, but only one pipe (or no pipe) is required to be supported. Supporting unbounded pipes is a bonus feature.
This grammar requires that a redirect in comes before a redirect out, i.e., ls <infile >outfile
is allowed, but ls >outfile <infile
is not. This makes parsing the command input simpler.
2.2. Example
Redirection
$ ls>out.txt
Notice the lack of a space betwen >
and out.txt
. The lexer will pick out these tokens for you regardless of whitespace.
Piping
ls / | wc
Notice that the programs can still take arguments even with a pipe (or redirect)
Piping and redirection
cat < file.txt | wc > wc.out
The commands in pipes can still take redirects. We will only test redirect in for the first program and redirect out for the last command. bash does permit other redirects, but these end up taking precedence over the pipe and we will not test for this.
3. Output
The output of command should match the output of running the command with bash, making it easy to create ground truth for testing.
4. Starter code
This only every needs to be run once.
mkdir ~/cop3402fall25/proc cd ~/cop3402fall25/proc curl https://www.cs.ucf.edu/~gazzillo/teaching/cop3402fall25/files/proc.tar | tar -xvf - mv -i mysh.template.c mysh.c # do not overwrite your existing code accidentally
5. Implementation
5.1. Lexer
The lexer identifies the individal tokens of the command input and handles whitespace for you. The lexer is given to you (lex.l
). The lexer is called with yylex()
and returns the type of token, while setting yytext
to the actual text (lexem) of the token. Below are the names of the tokens as defined in the token type enum (tokens.h
):
typedef enum { STRING, PIPE, REDIRECT_IN, REDIRECT_OUT, END_OF_LINE, } token_t;
The token enum names correspond to the actual text of the tokens as given in the below table. Whitespace is ignored
Grammar Symbol (Terminal) | Token enum |
---|---|
string | STRING |
"|" | PIPE |
"<" | REDIRECT_IN |
">" | REDIRECT_OUT |
"\n" | END_OF_LINE |
The code template provides a consume()
function which will read the next token from the input and save the lexeme (text) of the token into the globla variables lookahead
and lexeme
respectively. Use consume()
and inspect lookahead
and lexeme
to parse the input into a data structure.
5.2. Parser
Write a parser that takes tokens from the user input (via consume()
) and both checks that the input is valid (according to the grammar) and creates an in-memory data structure to store the commands.
See the lecture notes on parsing for an example to get you started.
You are also free to interpret the commands (run programs, do redirection and piping) during parsing, although this may be more challenging.
The code template provides an empty parse()
function for you to implement your parser in.
The result of parsing should be a linked list of command_t
structs. Use the provided add_command()
function (util.h
) to create and add a new, empty command_t
struct to the list. add_command()
will returns a pointer to the new struct that you can then populate with the program name, args, and redirect file names.
Use the provided print_commands()
function (util.h
) to help debug your parser.
5.3. Command structure
The code template provides you with a data structure, command_t
in util.h
, to store the results of parsing the commands from the input.
typedef struct command_s { char *argv[MAX_ARGS]; // NULL-terminated list, i.e., the next element after the last should be NULL. char *in; // NULL if there is no redirect char *out; // NULL if there is no redirect struct command_s *next; } command_t;
The data structure has one field for the argv array. This stores both the name of the program and the arguments, i.e., if the command is ls path1 path2
, argv will be
[ "ls", "path1", "path2", NULL ]
The last element in the argv
list must be succeeded by NULL. This is requirement of the exec
series of commands (man 2 execv
, "The array of pointers must be terminated by a null pointer.).
There are fields for the name of file to redirect stdin from (in
) and the name of the file to redirect standard out to (out
). If redirects are not present for the command in the user input, these fields should be NULL.
The next
field points to the next element in the linked list or NULL when it is the last element.
add_command()
(util.h
) allocates a new command_t
struct and appends it to the list for you.
The beginning of the list is pointed to by the global variable first_command
(util.h
) and the end by last_command
(util.h
), each of which will be NULL if the list is empty.
You can check for list sizes with the following conditions:
Condition | Description |
---|---|
first_command == last_command == NULL |
Empty list |
first_command == last_command && first_command != NULL |
Single-element list |
first_command != NULL && first-command->next == last_command |
Two-element |
first_command != NULL && first_command != last_command |
More than one element |
You are also free to define your own data structure(s) if you prefer.
5.4. Executing commands
fork()
creates a new process, dup2()
redirects I/O, and pipe()
creates a pipe.
For program execution, you can use (execvp
instead of execve
) which does path lookups for you.
When performing redirection, be sure to close the pipe file descriptors in the parent or the program reading the pipe may hang waiting for the file to close.
You can use the following in the parent to wait until there are no more child processes running:
do { while (wait(NULL) > 0); } while (errno != ECHILD);
5.5. Error checking
syscall errors should always be checked.
Report errors via exit codes passed to the exit
library call, i.e., exit(EXIT_FAILURE)
.exit code 5
.
Error messages are optional, but should only be written to stderr, not to stdout, which will be used for program output, i.e., use perror()
or fprintf(stderr, ...)
instead of printf
.
No erroneous commands will be given for testing.
6. System call and library references
Do not use helper libraries or other simplified calls to achieve similar results as functions below. Just use the syscalls below for these aspects of the project. The template code has all the header files you will need for the project. If you believe you need other library calls, please double-check with the instructor.
Symbol | Reference | Reading |
---|---|---|
fork() | man 3 fork |
LPI 24 |
wait() | man 2 wait |
LPI 24 |
execvp() | man 3 execvp |
LPI 27 |
pipe() | man 2 pipe |
LPI 44.2 |
dup2() | man 2 close |
LPI 44.4 |
open() | man 2 open |
LPI 4.1 |
close() | man 2 close |
LPI 4.1 |
perror() | man 3 perror |
LPI 3.4 |
exit() | man 3 exit |
LPI 25 |
_exit() | man 2 exit |
LPI 25 |
man
is the command-line manual.- LPS is The Linux Program Interface book.
- Knowledge of memory management and string processing is assumed.
6.1. Headers to include
#include <stdio.h> #include <stdbool.h> #include <malloc.h> #include <unistd.h> // fork() #include <stdlib.h> // exit() #include <inttypes.h> // intmax_t #include <sys/wait.h> // wait() #include <string.h> // strcspn(), strtok #include <errno.h> // ECHILD #include <fcntl.h> // O_RDONLY, open #include "tokens.h" #include "util.h" #include "lex.c"
Note that lex.c
isn't a header, but the generated code from the lexer.
7. Building and running the tool
Create a Makefile that will build your project and give the resulting program the name mysh
. See the hello project for an example Makefile.
Your project must be buildable with make
and runnable with ./mysh
, both from the root of your repo, i.e.,
cd ~/cop3402fall25/proc make ./mysh
Automated grading will build and run your tool this way and only this way.
8. Submitting your project
8.1. git
setup
Be sure to complete the vc exercise before attempting this project.
Create a new local repository, following the directions in the vc exercise (including the git --set-upstream
last step) and set the local and remote repository URLs to be the following locations:
Local repository | ~/cop3402fall25/proc |
Remote repository | gitolite3@eustis3.eecs.ucf.edu:cop3402/$USER/proc |
Commands to setup your repo
These steps only setup the repo, do not submit your code, and assume you have already completed the vc exercise. Consult that exercise for specifics on validating each step and submitting your code.
mkdir -p ~/cop3402fall25/proc cd ~/cop3402fall25/proc git init echo "proc project" > README.md git add README.md git commit README.md # Enter a commit message in the editor that pops up git remote add submission gitolite3@eustis3.eecs.ucf.edu:cop3402/$USER/proc git push --set-upstream submission master
8.2. Self-check
See the hello project for instructions on cloning a project from the grading server.
9. Grading
The test cases used to grade the project are listed below. Scripts to run the the odd-numbered test cases are provided publicly, while the even-numbered test cases are private.
To use the automated tests, first download them, e.g.,
cd ~/cop3402fall25/proc wget https://www.cs.ucf.edu/~gazzillo/teaching/cop3402fall25/files/proc_public_tests.tar tar -xvf proc_public_tests.tar
To run a test case, e.g., tests/03.sh
, use the following bash command from the root of your local repository directory:
bash proc-tests/03.sh $(realpath mysh) echo $?
This runs the bash script, passing in the absolute path to your program, which is what $(realpath mysh)
finds for you. The result of echo $?
should be 0
which indicates the test was succesful.
To see what commands the test case is running exactly, use bash -x
instead of just bash
:
bash -x proc-tests/03.sh $(realpath mysh) echo $?
9.1. Test cases
# | Description | Expected Output |
---|---|---|
1 | Run a program with no args. | It should print to stdout. |
2 | Run a program by name without giving the full path and with no args. | It should print to stdout. |
3 | Run a program that takes an argument. | It should print to stdout |
4 | Run a program, reading its input from stdin. | It should print to stdout. |
5 | Run a program, redirecting its output to /dev/null | It should print nothing to stdout. |
6 | Run echo. | It should print to stdout. |
7 | Run echo, redirecting to a file. | It should print to the file. |
8 | Run cat, redirecting both stdin and stdout from and to files. | It should copy the contents of the input file to the output file. |
9 | Pipe the output of ls to cat. | It should print the output of ls to stdout. |
10 | Pipe a program to word count. | It should print to stdout the word count of the first program's output. |
11 | Pipe ls to wc -l | It should print only the number of lines from ls. |
12 | Pipe a program with arguments to another program with arguments. | It should correctly run both programs, piping between them, printing to stdout. |
13 | Pipe find to grep. | It should correctly filter the results of find to stdout. |
14 | Pipe find to grep, but redirect the output. | It should correctly filter the results of find to the given file. |
15 | Pipe cat to grep, redirecting cat's stdin from a file. | It should correctly filter the contents of the input file to stdout. |
16 | Redirect in to one program, pipe to another, redirect output. | The file should contain the correct output. |
17 | Same as 05, but alternative spacing around redirect symbol | Same as 05 |
18 | Same as 07, but alternative spacing around redirect symbol | Same as 07 |
19 | Same as 09, but alternative spacing around pipe symbol | Same as 09 |
20 | Same as 12, but alternative spacing around pipe symbol | Same as 12 |
21 | Same as 13, but alternative spacing around pipe symbol | Same as 13 |
22 | Same as 15, but alternative spacing around pipe and redirect symbols | Same as 15 |
23 | Same as 15, but alternative spacing around pipe and redirect symbols | Same as 15 |
24 | Same as 16, but alternative spacing around pipe and redirect symbols | Same as 16 |
10. Bonus feature
Support more than one pipe between executables. The bonus feature test cases will test 2, 3, and more pipes in a single command with and without redirection and with differing spacing between the pipe and redirect symbols.
11. Grading schema
Criterion | Points |
---|---|
The git repo exists | 1 |
The program is written, builds, and runs as described here | 1 |
Tests pass, prorated by the number of test cases passing | 6 |
TOTAL | 8 |