CSCI 341: Lab 3

Shell
Due: 11:59 PM on Monday, April 5th

Repository

Check out the Lab 3 repository at https://classroom.github.com/g/6gYlCwua. Note that there is no starter code for this assignment - you will need to add your own lab3.c file.

Partners

You can work on this lab with a partner if you choose. If you decide to work with a partner, you and your partner should check out a single lab3 repository. The first partner will create a team name, and the second partner should choose that team name. Please be careful choosing a team, as this cannot be undone. Please name your team something that makes it clear who you are.

If you choose to work with a partner, you and your partner must complete the entire lab together. Dividing the lab up into pieces and having each partner complete a part of it on their own will be considered a violation of the honor code. Both you and your partner are expected to fully understand all of the code you submit.

Background

One of the fundamental functionalities of Linux (and Linux-like OSs) is the ability to pipe commands. When the | character is used in a shell between two commands, the shell takes the output from the first command and “pipes” it to the second command as the input. You can try it yourself! If you run the command ls -la / | grep tmp on cs341.cs.oberlin.edu, you should be able to see only the tmp directory output from ls -la. This is because you gave grep, a common search tool, the output of ls -la directly. There are many more applications of piping but that is for you to discover as necessary in your adventures/projects/jobs/etc.

The programming part

For this assignment you are to write a simple program that will act as a shell. The program should:

  • Display a command prompt and read in a command line from the user (the prompt must be CS341 >, otherwise it cannot be detected by the autograder)
  • Your shell must support basic piping like the unix shell. Specifically, it must support the following inputs, where command represents any command to the linux shell, such as /bin/ls -l, ./a.out, etc. You may assume that all commands will include their full file path - i.e. we will test "/bin/ls" rather than "ls".
    1. command Run command, with stdin and stdout connected to their usual files. When command finishes, check if it exited normally or was killed by the OS. If it exited normally, the shell should output pid:%d exit value:%d\n (with the proper relevant values inserted into the format string). If the process exited abnormally, the shell should output pid:%d signal:%d\n, again with relevant values inserted. The shell should then print the prompt again and wait for more input.
    2. command1 | command2 Run command1 as in #1, but redirects the output of command1 as input to command2. Note that there should be two instances of pid:%d status:%d\n output.
    3. command1 ; command2 Run command1 as in #1, wait for it to finish, and then run command 2 as in #1.
    4. exit Exit the shell.
    5. Ctrl-D will close standard in for a process. You should detect if this has happened by checking the return value of fgets and exiting the shell if it has.

Your shell should handle the following signals:

  • SIGINT - Generated by Ctrl-C. This signal allows a user to terminate a running program. Your shell should not exit when user presses Ctrl-C or the process receives SIGINT but simply report that the SIGINT signal has been received by the shell. If the process receives SIGINT, you must print the string “caught sigint” on a line by itself, and then show the prompt again.
  • SIGTSTP - Generated by Ctrl-Z. This signal allows a user to terminate a running program. Your shell should not exit when user presses Ctrl-Z or the process receives SIGTSTP but simply report that the SIGTSTP signal has been received by the shell. If your shell receives SIGTSTP, you must print the string “caught sigtstp” on a line by itself, and then show the prompt again.

I strongly recommend you implement this after you have the rest of the shell working, as you may need to use ctrl-c/ctrl-z to kill your shell before you have perfected it.

A sample shell session might look like the following:


    CS341 >/bin/echo hello
    hello
    pid:22393 exit value:0
    CS341 >/bin/ls /bin | /bin/grep less
    bzless
    less
    lessecho
    lessfile
    lesskey
    lesspipe
    zless
    pid:22395 exit value:0
    pid:22401 exit value:0
    CS341 >/bin/echo hello ; /bin/echo goodbye
    hello
    goodbye
    pid:22403 exit value:0
    pid:22405 exit value:0
    CS341 >/bin/cat
    ^C
    pid:22406 signal:2
    caught sigint
    CS341 >^C
    caught sigint
    CS341 >exit
    

(Note that ^C represents the user entering ctrl-C)

The shell does NOT need to support background processes, running more than one child at a time, or handling multiple chained pipes. Essentially, if anything not matching the use cases/requirements above is in question, you probably do not have to do it. Please ask in office hours or on Piazza if you have questions or need clarification.

Tips for primary components

  • Parse the command line into arguments, creating an array of character pointers, where array[0] points to the actual command and rest of the array elements point to the arguments to the command (Similar to main()’s argv[])
  • Fork off a child and have the child load the requested program by passing the argument vector created in step 2 to exec() family of system calls.
  • Wait for the child to complete executing and then print its pid and exit status.
  • Repeat the first step forever until the user enters the command exit

    Requirements

    See above, but here is a summary:

    1. Display a command prompt (CS341 >) and read in a command line from the user
    2. Handle command (run command, with stdin and stdout connected to their usual files, must additionally output pid:%d status:%d\n, then continue)
    3. Handle command1 | command2 (run command1 as in #2, but redirect the output of command1 as input to command2, then continue) Handle command1 ; command2 (run command1 as in #2, wait for it to finish, and then run command2 as in #2, then continue)
    4. Handle SIGINT - Generated by Ctrl-C (print “caught sigint” and then continue)
    5. Handle SIGTSTP - Generated by Ctrl-Z (print “caught sigtstp” and then continue)
    6. Exit the program when the user enters "exit".
    7. Exit the program if the user closes stdin.
    8. It is highly recommended you develop and test your code on cs341.cs.oberlin.edu. This is to ensure standardization of comparisons between our and your tests whilst you are developing your code.
    9. When you turn in the assignment, we should be able to compile your program by running gcc -Wall -Werror -o lab3 lab3.c in the hw3 directory (case sensitive). You are free to include any files e.g. lab3.h, it just has to compile using the above command.

      References

      All of the following will be very helpful for you:
      • Worksheet 3, for fork/exec/wait.
      • Worksheet 5, for signal handling and dup2.
      • Worksheet 7, for parsing input.
      • Chapter 8 of the book. (Note: the book uses their own weird wrapper functions for a lot of libc functions - you are expected to use the real functions and not the book's versions of them.)

      Grading

      Grading will happen using an autograder on Gradescope. Please test your output against the autograder early and often so that you can tell if it matches the expected output.

      C. Taylor, C. Kanich