[Solved] CS0449 Project 4-A Shell

$25

File Name: CS0449_Project_4_A_Shell.zip
File Size: 226.08 KB

SKU: [Solved] CS0449 Project 4-A Shell Category: Tag:
5/5 - (1 vote)

In this project, youll be making a simple Unix shell. A shell is what you interact with when you log into thoth its a command-line interface for running programs.

You already did some of this project in lab 7 thats exactly how youll run programs!

Isnt 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 back to step 1

The shell you interact with when you log into thoth is called bash. You can even run bash inside itself:

(13) thoth $ bash(1) thoth $ pstree <yourusername>sshdbashbashpstree(2) thoth $ exit(14) thoth $ _

Youll see the command numbers change, since youre running bash inside of bash. pstree will also show this a bash process nested inside another bash process!

When you write your shell, you can test it like any other program youve written.

(22) thoth $ ./myshellmyshell> lsmyshell myshell.cmyshell> exit(23) thoth $ _

Input tokenization

Use fgets() with a generously-sized input buffer, like 300 characters.

Once you have the input, you can tokenize it (split it into words) with the strtok() function. It behaves oddly, so be sure to read up on it.

Here is a sample program that demonstrates strtok.. Feel free to use it as the basis for your command parsing, but remember

Since strtok operates in place, you cannot return the resulting array from a function. You have to allocate that array in the function that needs it, and pass a char** pointer as an argument.

In the worst case where someone types e.g. a b c d e f g h i, you could have half as many tokens as the size of your character buffer so, 150 tokens.

For strtok()s delim parameter, you can give it this string:

t

Get the string tokenization working first. Test it out well, and try edge cases typing nothing, typing many things, typing several spaces in a row, using tab characters

Commands

Many of the commands youre used to running in the shell are actually builtins commands that the shell understands and executes instead of having another program execute them. This makes the shell somewhat faster, because it doesnt have to start a new process for each command.

Anything that isnt a builtin should be interpreted as a command to run a program.

Following is a list of commands you need to support.

exit and exit number

Functions needed: exit()

The simplest command is exit, as it just exits the shell.

NOTE: In all these examples, myshell> indicates your shell programs prompt, and $ indicates bashs prompt.

$ ./myshellmyshell> 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. Youve used at least one before

cd dirname

Functions needed: chdir()

You know how cd works! You dont have to do anything special for the stuff that comes after the cd. chdir() handles it all for you.

Really, chdir() handles it all for you. You dont 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.

You do not need to support cd ~. This is actually a bash feature, but its kind of complicated, so dont worry about it.

You can see if it works properly using the pwd program, once your shell can run regular programs.

myshell> cd testmyshell> pwd/afs/pitt.edu/home/x/y/xyz00/private/testmyshell> cd ..myshell> pwd/afs/pitt.edu/home/x/y/xyz00/privatemyshell> _

Regular programs

Functions needed: fork(), execvp(), exit(), waitpid(), signal()

If something doesnt look like any built-in command, run it as a regular program. You should support commands with or without arguments.

You basically did this with lab 7! You can use that as a starting point.

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> lsmyshell.c myshell Makefilemyshell> pwd/afs/pitt.edu/home/x/y/xyz00/privatemyshell> echo hellohellomyshell> echo 1 2 3 4 51 2 3 4 5myshell> touch one two threemyshell> ls -lh .total 9K-rw-rr 1 xyz00 UNKNOWN1 2.8K Apr 9 22:04 myshell.c-rwxr-xr-x 1 xyz00 UNKNOWN1 4.4K Apr 9 22:04 myshell-rw-rr 1 xyz00 UNKNOWN1 319 Apr 9 18:51 Makefile-rw-rr 1 xyz00 UNKNOWN1 0 Apr 9 22:05 one-rw-rr 1 xyz00 UNKNOWN1 0 Apr 9 22:05 two-rw-rr 1 xyz00 UNKNOWN1 0 Apr 9 22:05 threemyshell> _

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

$ ./myshellmyshell> cattyping stuff heretyping stuff herecat just copies everything I type.cat just copies everything I type.<ctrl+C>$ _

I tried to exit cat by using Ctrl+C but it exited my shell too!

Making this work right is pretty easy.

  • At the beginning of main, set it to ignore SIGINT.
  • In the child process (after fork but before exec), set its SIGINT behavior to the default.

Once thats done, you can use Ctrl+C with abandon:

$ ./myshellmyshell> catblahblahblahhhhhblahhhhh<ctrl+C>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. (This is just like in lab7.)
  • If the child did not exit normally (WIFEXITED gives false):
    • if the child terminated due to a signal, print out which signal killed it. (Again, lab7!)
    • otherwise, just say it terminated abnormally.

If you get errors about implicit declaration of function strsignal then add #define _GNU_SOURCE to the very top of your code, before any #include lines.

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. DONT FORGET TO EXIT HERE. This is how you forkbomb. If you forkbomb thoth multiple times, even if by accident, you may have your login privileges revoked.

Notes on using execvp:

  • The way execvp detects how many arguments youve given it is by putting a NULL string pointer as the last argument. You must put the NULL in your arguments array yourself, after parsing the user input. (The strtok example above does this.)
  • execvp only returns if it failed. So you dont technically need to check its return value.

Input and Output redirection

Functions needed: freopen()

Any regular program should also support having its stdin, stdout, or both redirected with the < and > symbols.

The redirections can come in either order, like cat < input > output or cat > output < input. Do not hardcode your shell to assume one will come before the other.

Your shell should support using input and output redirection on any non-builtin command with any number of parameters.

This means you should look for the redirections by looking starting at the last tokens. Then you can replace each redirection token (< and >) with NULL to ensure the right arguments get passed to the program.

bash lets you write ls>out without spaces, but you dont have to support that. ls > out is fine for your shell.

myshell> ls > outputmyshell> cat outputmyshell.cmyshellMakefileoutputmyshell> less < Makefile <then less runs and shows the makefile> myshell> cat < Makefile > copymyshell> lsmyshell.c myshell Makefile output copymyshell> less copy <then less runs and shows that copy is identical to the original makefile> myshell> ls -lh . > outputmyshell> cat outputtotal 31K-rw-rr 1 xyz00 UNKNOWN1 2.8K Apr 9 23:18 myshell.c-rwxr-xr-x 1 xyz00 UNKNOWN1 4.4K Apr 9 23:18 myshell-rw-rr 1 xyz00 UNKNOWN1 319 Apr 9 18:51 Makefile-rw-rr 1 xyz00 UNKNOWN1 39 Apr 9 23:20 output-rw-rr 1 xyz00 UNKNOWN1 319 Apr 9 23:21 copymyshell> _

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 after using fork, 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.

Reviews

There are no reviews yet.

Only logged in customers who have purchased this product may leave a review.

Shopping Cart
[Solved] CS0449 Project 4-A Shell[Solved] CS0449 Project 4-A Shell
$25