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.
ssh into eustis, replacing
NID
with your UCF NID.ssh NID@eustis.eecs.ucf.edu
Create a new git repo
mkdir mysh cd mysh/ git init echo "mysh" > README git add README git commit -m "initial commit" README
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.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 |