The pdf has steps and descriptions, as well as examples.
shell.pdf

Unformatted Attachment Preview

You’ll be making a simple Unix shell written in C.
A big part of this project is learning to read API
documentation. Get comfortable with the man pages.
Compile with -Wall and -Werror
Code Style
You must split your program up into functions. At the very
least, have a separate function for each kind of command, but
splitting it up more is even better
Isn’t a shell a special kind of program?
Nope! A shell is just a user-mode process that lets you interact
with the operating system. The basic operation of a shell can be
summed up as:
1 Read a command from the user
2 Parse that command
3 If the command is valid, run it
4 Go to 1
Input
The way a shell gets input is one line at a time. fgets() is the
obvious choice here. You can limit input to a certain size, but
make it generous – 3~500 characters would be good.
Once you have the input, you can tokenize it (split it up) with the
strtok() function. It behaves oddly, so be sure to read up on it.
Example:
#include
#include
// This example shows how to use the strtok function to
split a string
// into pieces.
int main()
{
// strtok MODIFIES THE STRING TO PARSE. So you can’t
give it a string
// literal.
char string_to_parse[] = “this is a
string:to::split.:::”;
const char* delim = ” :”;
int i;
// Uncomment this line and the for loop at the end to
see what strtok
// actually did to string_to_parse’s memory.
// int orig_len = strlen(string_to_parse);
char* token = strtok(string_to_parse, delim);
for(i = 0; token != NULL; i++)
{
printf(“token %d: ‘%s’n”, i, token);
token = strtok(NULL, delim);
}
// for(i = 0; i < orig_len; i++) // printf("%d: %c (%d)n", i, string_to_parse[i], string_to_parse[i]); return 0; } You can limit the number of tokens in the input, but be generous - assume the worst case where someone types e.g. a b c d e f g h i.... So you'd basically need half as many tokens as the size of your character buffer. For strtok()'s "delim" parameter, you can give it this string: " tn()|&;" You won't be using all these symbols in your assignment, but it will make the command parsing consistent with the way a real shell works. Note: You don't need to do any kind of input parsing beyond tokenizing the input and looking at the first token to see what command to run. Don't e.g. pick apart paths or executable names or anything like that. It's really straightforward! Commands Many of the commands you're used to running in the shell are actually builtins - commands that the shell understands and executes instead of having another program execute them. Anything that isn't a builtin should be interpreted as a command to run a program. Following is a list of commands you need to support. exit System calls needed: exit() The simplest command is exit, as it just... exits the shell. NOTE: In all these examples, myshell> indicates your shell
program’s prompt, and $ indicates bash’s prompt.
$ ./myshell
myshell> exit
$ _
You also need to support giving an argument to exit. It should
be a number, and it will be returned to bash. You can check it like
so:
myshell> exit 45
$ echo $?
45
$ _
The echo $? command in bash will show the exit code from the
last program.
If no argument is given to exit, it should return 0:
myshell> exit
$ echo $?
0
$ _
Hint: there are a few functions in the C standard library you can
use to parse integers from strings.
cd
System calls needed: chdir()
You know how cd works! You don’t have to do anything special
for the stuff that comes after the cd. chdir() handles it all for
you.
Note: really, chdir() handles it all for you. You don’t have to
parse the path, or look for ‘..’, or make sure paths are
relative/absolute etc. chdir() is like cd in function form.
You do not need to support cd without an argument. Just
regular old cd.
pushd and popd
System calls needed: getcwd(), chdir()
pushd and popd implement a stack of directory changes. pushd
works like cd, but pushes the previous directory onto an internal
stack. popd will pop the value off that stack and cd back to it.
In other words, it acts almost like a browser history. popd is like
hitting the back button.
NOTE: you do not have to support ~ in your shell. This is just an
example of how it works in bash.
Your shell should support 4 directory stack entries.
• If pushd is run, and the stack already has 4 entries, print an
error message and do not do anything else.
• If pushd is run, and changing the directory failed, do not
change the directory stack.
• If popd is run, and the stack has 0 entries, print an error
message and do not do anything else.
• If popd is run, and changing the directory failed, do remove
the invalid old directory from the stack (pop it).
Note: READ THE FOLLOWING. Don’t use the form of
getcwd() where you give it a buffer. It will only cause you
trouble since you’ll be using it multiple times.
Watch out: read the man pages for the getcwd() function. It has
a handy feature where you don’t need to give it a buffer, but you
do need to do something with the string it returns…
Regular programs
System calls needed: fork(), execvp(), exit(), waitpid(),
signal()
If something doesn’t look like any built-in command, run it as a
regular program. You should support commands with or without
arguments.
Your shell should support ANY number of arguments to
programs, not just zero or one.
For example, and these are just examples: ANY program
should be able to be run like this:
myshell> ls
myshell.c
myshell
Makefile
myshell> echo “hello”
hello
myshell> echo 1 2 3 4 5
1 2 3 4 5
myshell> touch one two three
myshell> ls -lh .
total 9K
-rw-r–r– 1 xyz00 UNKNOWN1 2.8K
-rwxr-xr-x 1 xyz00 UNKNOWN1 4.4K
-rw-r–r– 1 xyz00 UNKNOWN1 319
-rw-r–r– 1 xyz00 UNKNOWN1
0
-rw-r–r– 1 xyz00 UNKNOWN1
0
-rw-r–r– 1 xyz00 UNKNOWN1
0
myshell> _
Apr
Apr
Apr
Apr
Apr
Apr
9
9
9
9
9
9
22:04
22:04
18:51
22:05
22:05
22:05
myshell.c
myshell
Makefile
one
two
three
**Note: ** in the above ls -lh . command, the date and time
indicate that you have started this project way too late. When
writing your project, please ensure that the date and time is much
earlier than 2 hours before midnight on April 9th.
Catching Ctrl+C
Ctrl+C is a useful way to stop a running process. However by
default, if you Ctrl+C while a child process is running, the parent
will terminate too. So if you try to use it while running a program
in your shell…
$ ./myshell
myshell> cat
typing stuff here…
typing stuff here…
cat just copies everything I type.
cat just copies everything I type.

$ _
I tried to exit cat by using Ctrl+C but it exited my shell too!
Making this work right is pretty easy.
• When your shell first starts up, set it to ignore SIGINT.
• In the child process, set its SIGINT behavior to the default.
Once that’s done, you can use Ctrl+C with abandon:
$ ./myshell
myshell> cat
blah
blah
blahhhhh
blahhhhh

myshell> exit
$ _
The Parent Process
After using fork(), the parent process should wait for its child to
complete. Things to make sure to implement:
• Make sure to check the return value from waitpid to see if it
failed.
If the child did not exit normally (WIFEXITED gives false):
◦ if the child terminated due to a signal, print out which signal
killed it.
otherwise, just say it terminated abnormally.
The Child Process
After using fork(), the child process is responsible for running
the program. Things to make sure to implement:
• Set the SIGINT behavior to the default (as explained above
in the Ctrl+C section).
• Use execvp to run the program.
• Print an error if execvp failed.
AND THEN…. exit() after you
print the error. DON’T FORGET
TO EXIT HERE. This is how
you forkbomb.
Notes on using execvp:
• The way execvp detects how many arguments you’ve given
it is by putting a NULL string pointer as the “last” argument.

Example below shows a hard-coded version, but you must
put the NULL in your arguments array yourself, after parsing
the user input.
• execvp only returns if it failed. So you don’t technically need
to check its return value.
#include
#include
int main()
{
printf(“Gonna list the current directory.
n—————–nn”);
int child_pid = fork();
if(child_pid == 0)
{
// After we fork, we can make the child process
transform into
// ls by using execvp().
char* args[3] = { “ls”, “-al”, NULL };
execvp(args[0], args);
}
else
{
// This waitpid() forces the parent to wait (or
“block”) until
// the child process terminates.
waitpid(child_pid, NULL, 0);
printf(“n—————–nDone listing!n”);
}
return 0;
}
Input and Output redirection
Functions needed: freopen()
Note: As an extra credit opportunity (10%), you can
implement redirection using the open(), dup2(), and close()
syscalls instead.
Any regular program should also support having its stdin, stdout,
or both redirected with the < and > symbols.
Your shell should support
using input and output
redirection on any non-builtin
command with any number of
parameters.
bash lets you write ls>out but you don’t have to support that. It’s
okay for your shell to only support > and < with spaces around them as shown here. myshell> ls > output
myshell> cat output
myshell.c
myshell
Makefile
myshell> less < Makefile output myshell> cat < Makefile > copy
myshell> ls
myshell.c
myshell
Makefile
myshell> less copy
output
copy

myshell> ls -lh . > output
myshell> cat output
total 31K
-rw-r–r– 1 xyz00 UNKNOWN1 2.8K Apr
-rwxr-xr-x 1 xyz00 UNKNOWN1 4.4K Apr
-rw-r–r– 1 xyz00 UNKNOWN1 319 Apr
9 23:18 myshell.c
9 23:18 myshell
9 18:51 Makefile
-rw-r–r– 1 xyz00 UNKNOWN1
-rw-r–r– 1 xyz00 UNKNOWN1
myshell>
39 Apr
319 Apr
9 23:20 output
9 23:21 copy
Input and output redirection should detect and report the
following errors:
• If the user tried to redirect stdin or stdout more than once
(e.g. ls > out1 > out2 or cat < file1 < file2) • If the file to read or write could not be opened Opening the redirection files You should open the redirection files in the child process, but before using execvp(). In order to redirect stdin and stdout, you have to open new files to take their place. freopen() is the right choice for this. • When opening a file for redirecting stdin, you want to open the file as read-only. • When opening a file for redirecting stdout, you want to open the file as write-only. For extra credit, you can implement redirection using the open(), dup2(), and close() syscalls instead. It's a bit more work but gives you more practice with syscalls. Using open() Open the redirected files with the open() system call. Make sure you do the following: • When opening a file for redirecting stdin, you want to open the file as read-only. • When opening a file for redirecting stdout, you want to open the file as write-only. In addition, you want the following behaviors: ◦ You want the output to create the file if none exists. ◦ You want the output to replace any file that is already there. ◦ You want the output to be readable and writable to the current user, at least. Have a look at open()'s mode parameter for that last one. Then try doing ls -l in the directory and look at the dashes and letters on the left side. You want your output file's permissions to look something like -rw------- or -rw-r—r--. Using dup2() Just opening a file isn't enough. You then have to reassign stdin/ out to the file(s) you just opened. Remember we learned in class that the file descriptors are small integers because they're actually indices into an array that the OS keeps of all your open functions. You can copy things from one array slot to another, just like any other array. But the function to do it is a little weird. If you have a variable fd, and you want to replace stdin with fd, you write this: dup2(fd, 0); The first argument is the file descriptor to copy, and the second argument is the file descriptor to overwrite. Remember, stdin is 0. What's stdout? But you're not done! After you use dup2, you have to close(fd). Why?? You just opened it! Well, file descriptors use something called reference counting. When you did the dup2(), there are now two references to the file, one in file descriptor fd, and one in file descriptor 0. When you write close(fd), that removes the unnecessary second reference, leaving you with just one reference in file descriptor 0. You should check both dup2 and close for errors like all the other system calls. Command Syntax If you want a more formalized version of the syntax, because you're a dork like me, here you go: Command: SimpleCommand SimpleCommand && SimpleCommand SimpleCommand || SimpleCommand SimpleCommand: Builtin Program Program Input Program Output Program Input Output Program Output Input Builtin: 'exit' 'exit' Number 'cd' Token 'pushd' Token 'popd' Program: Token+ Input: '<' Token Output: '>‘ Token
You can earn more extra credit by implementing the && and ||
operators.
Note: bash lets you chain multiple && and || together. You don’t
have to do this. Just one per line is enough.
The && operator
The && operator takes two commands. If the first command
succeeded (that is, it exited normally with an exit code of 0), then
it runs the second command. If the first command failed, the
second command is skipped.
The || operator
The || operator takes two commands. If the first command failed
(that is, it didn’t exit normally, or it exited normally with a nonzero exit code), then it runs the second command. If the first
command succeeded, the second command is skipped.
Support for redirection
Both commands should independently support input and
output redirection. Since you are only running one command at
a time, this shouldn’t be too bad. You’ll just have to change your
command parsing a bit.
Examples
myshell> ls . && ls ..
myshell
myshell.c
proj1
proj2
proj3
proj4
myshell> ls ajosfjaog && ls
ls: cannot access ajosfjaog: No such file or directory
myshell> ls ajosfjaog || ls
ls: cannot access ajosfjaog: No such file or directory
myshell
myshell.c
myshell> ls . || ls ..
myshell
myshell.c
myshell> ls > out && ls .. > out2
myshell> cat out
myshell
myshell.c
myshell> cat out2
proj1
proj2
proj3
proj4
myshell> _

Purchase answer to see full
attachment