UP | HOME

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

Author: Paul Gazzillo

Created: 2025-09-30 Tue 11:01

Validate