UP | HOME

mysh Project
COP-3402

Table of Contents

Overview

In this project, you will develop a simple version of an sh-style shell.

Execution

USAGE: ./mysh

The program should be called mysh. It takes no arguments.

Command-line input

While mysh is running, it should prompt the user with "$ " (a single dollar-sign followed by a space with no newline character) and wait for input from stdin.

Maximum length of the whole command-line string entered by the user: 1024 (including the null-terminating character). Anything longer that will become part of the next command (which is the behavior of using fgets()). Maximum required arguments per command to support is 32 (not including the program name and the NULL-terminator for argv).

The command should be tokenized by space, e.g., with strtok(), with the first token being the name of a program and the remaining tokens being the arguments passed to an exec syscall.

The user may also use a pipe | to redirect output of the command to the left of the pipe to the input of the command to the right. bash supports arbitrary pipes, but for this project, only a maximum of two pipes is required to be supported. (More than two pipes may be part of a bonus project proposal.)

No quote processing is required: the program name and arguments can be split using just strtok(). More advanced command-line processing can be part of a bonus project.

Command processing

exit

If the user types exit and hits ENTER, the mysh should terminate successfully (EXIT_SUCCESS);

$ exit

Test with echo

echo "exit" | ./mysh

End-of-input

If the user ends the input, e.g., by typing Ctrl-D, then mysh should terminate successfully, e.g.,

if (!fgets(buffer, BUFSIZE, stdin)) {
  exit(EXIT_SUCCESS);
}

cd

If the user types cd dirname, the shell should change its working directory (chdir()) to the directory given by dirname.

Test this with

echo "cd ../" | ./mysh

program

For all other non-empty input, tokenize the string by space (strtok()) and attempt to run the program named by the first token (with execvp). Pass the remaining tokens in as the list of arguments to execvp.

For example

$ uname

Output:

Linux

For example

$ ls -l /usr/include/arpa

Output:

total 52
-rw-r--r-- 1 root root  3432 May  6 16:34 ftp.h
-rw-r--r-- 1 root root  4334 May  6 16:34 inet.h
-rw-r--r-- 1 root root  7041 May  6 16:34 nameser_compat.h
-rw-r--r-- 1 root root 14510 May  6 16:34 nameser.h
-rw-r--r-- 1 root root 10263 May  6 16:34 telnet.h
-rw-r--r-- 1 root root  3051 May  6 16:34 tftp.h

Waiting for input

Some programs read from standard input, e.g., wc

$ wc

The program will appear to hang, but it is waiting for input on stdin. Type on on the console, then hit Ctrl-D on an empty line, i.e., after hitting enter, in order to close the input and allow the program to finish execution.

Invalid programs

If the program name given doesn't exist, you can simply kill the child process and let the parent continue processing command-line input.

$ foobar123 | wc

pipes

Pipes create separate processes and connect their inputs and outputs. Tokenize the string by the pipe symbol | before tokenize each individual program and arguments on either sides of the pipes. Pipes communicate output from left to right. For instance, with ls | head | wc, two pipes first_pipe and second_pipe) are created and the following I/O occurs:

process input output
ls stdin first_pipe
head first_pipe second_pipe
wc second_pipe stdout

Take the following example:

$ uname | wc

uname prints to stdout the kernel name, i.e., "Linux". wc prints to stdout the number of lines, words, and characters given to stdin. With the pipe, uname's stdout is replaced with input of the pipe and wc's stdin is replaced with the output of the pipe. The string "Linux" is not visible in the console but is instead passed via the pipe to wc, which counts a single line, a single word, and six characters.

Output:

1       1       6

Additional examples

$ ls | wc
$ ls -l /usr/include/ | grep ^d | sort -rk9

Try running myls with mysh

$ ../myls/myls /usr/include/ | grep DIR | sort -hk4

Empty input

If the user hits ENTER without typing anything, a new prompt should appear, e.g.,

$ 
$ 

Test this with echo, which adds a newline character by default.

echo | ./mysh

Similarly, if one side of a pipe is empty, there is no need to continue executing the programs or processing the pipes, e.g.,

$   | wc

Error handling

Use the perror() / exit() pattern on all system calls, including pipe(), fork(), dup2(), and close(). Use perror() / _exit() instead for execvp(). See the manpages and references for these system calls.

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.

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
close() man 2 close LPI 4.1
chdir() man 2 open LPI 18
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.

Tips

  • Use fgets with a buffer size of 1024 to read commands
  • Remove the newline from the input command with strcspn(), e.g.,

    buffer[strcspn(buffer, "\n")] = '\0';
    
  • Here is a a quick-and-dirty way to wait for all child processes to finish (requires #include <errno.h>):

    do {
      while (wait(NULL) > 0);
    } while (errno != ECHILD);
    

Submitting your project

Setup the repo

You only need to do these steps once. If you try to create a new repo and push it to the remote, you may get unexpected conflicts.

  1. ssh into eustis, replacing NID with your UCF NID.

    ssh NID@eustis.eecs.ucf.edu
    
  2. Create a new git repo

    mkdir mysh
    cd mysh/
    git init
    echo "mysh" > README
    git add README
    git commit -m "initial commit" README
    
  3. Add the URL of your personal remote repository, replacing NID with your UCF NID.

    git remote add submission gitolite3@eustis3.eecs.ucf.edu:cop3402/NID/mysh
    

    If you make a mistake typing the URL, remove the remote with git remote rm submission and try adding it again.

  4. Synchronize your local repo with the remote eustis3 repo.

    git push --set-upstream submission master
    

    You only need to do this once. See below for commands to keep the remote repo up to date.

Keeping your repository up to date

Use git commit and git push regularly to keep the remote repo up to date.

Building and running the tool

Your project should be buildable with make and runnable with ./mysh, i.e.,

make
./mysh

Create a Makefile that will build your project and give the resulting program the name mysh.

Self-check

cd ~
git clone gitolite3@eustis3.eecs.ucf.edu:cop3402/NID/mysh mysh_new
cd mysh_new
make
echo "ls | wc" | ./mysh

Self-grading

cd ~/
git clone https://www.cs.ucf.edu/~gazzillo/teaching/cop3402fall24/repos/mysh-grading.git
cd mysh-grading
wget https://www.cs.ucf.edu/~gazzillo/teaching/cop3402fall24/files/mysh-tests.tar
tar -xvf mysh-tests.tar

Look at README.md for usage instructions.

Grading schema

Criterion Points
The git repo exists 1
The program is written, builds, and runs as described here 1
Prorated correct public test cases 2
Prorated correct private test cases 2
TOTAL 6

Author: Paul Gazzillo

Created: 2024-11-07 Thu 14:54

Validate