Unix Systems Programming: Lab 5

Due:           Wednesday, November 10, 2010 @ 5:00 pm.  



 PURPOSE AND RATIONALE

The purpose of this lab is to allow students to become comfortable with the following fundamental Unix system calls:
as well with the utilization of Unix pipes, named and unnamed.

PRIMARY RESOURCES:

FAQ (submission instructions and other useful stuff)

You should refer to the required reading sections of the assigned texts in order to accomplish this lab.

If possible, you should ssh into the cluster to perform all lab activities.   

README

  1. If you are not in our course email list, please subscribe to the cspp51081 email list here: http://mailman.cs.uchicago.edu/mailman/listinfo/cspp51081 ;
  2. Before starting, please reviews lecture5 note carefully, for each part of lab, there is also specified reading assignment;
  3. Please pay attention to "DELIVERABLES" instruction in each part;
  4. Turn the lab assignment in by email to the grader by the due date.

LAB 5

  1. Your own simple shell -- gosh (Grand Ole SHell)

    The purpose of this part is to write your own shell in C.  This simple project is designed to give the programmer a working knowledge of input string processing, fork/exec procedures.

    We have provided you with a simple shell template that you can choose to work from.  You can compile the shell using the following command assuming you've untarred the package and entered the gosh_template subdirectory:

    gcc -I./ gosh_template.c -o gosh
    The project is divided into discrete steps that you can follow.  Each step will define a test that you can run and expected behaviour.  Please make your shell follow this behaviour.

    Deliverables: A tar file that contains only the following:

    1. all source programs (*.h and *.c)
    2. a makefile that compiles and builds your shell program
    3. a text file that shows the output of your shell for each problem, in the way done in the examples. It can be a transcript generated with the "script" command.

    Step 1: Input Processing Part 1
    Every good shell project starts with a prompt.  A prompt is just a string printed on each line on which you can enter a command.  Make your shell print the prompt and take input from user in an infinite loop. Once you have received a line of input, split it into one array of character pointers by spaces.  If the user enters the special string 'exit', exit the program.  If the user just enters '\n', the shell should do nothing but print the prompt on a new line.  In the example bellow, c1 stands for command 1 and 0, 1, 2, ...i, stand for the ith group of characters arguments). NOTE: we do not need consider the situation about pipe.

    gosh> ls -a -l -d /etc
    c1 0: ls
    c1 1: -a
    c1 2: -l
    c1 3: -d
    c1 4: /etc
    gosh>
    gosh>
    gosh>
    gosh>
    gosh>
    gosh> exit
    hangao@gawaine:~/cspp51081/LAB4$
     

    Step 2: Fork/Exec
    Write a simple fork/execvp procedure to execute command c1 in a child process.  If the command inputted does not exist, the child should print an error message and exit. The relevant  examples (C code) from the book are here: create a child process with fork and wait for its termination, replace a process image (details: BLP, chapter 10).

    gosh> ls -l /etc/passwd
    c1 0: ls
    c1 1: -l
    c1 2: /etc/passwd
    Running Command
    ---------------
    lrwxrwxrwx 1 root root 14 Jul 6 1999 /etc/passwd -> ./local/passwd
    ---------------
    Command Returned Exit Code 0
    gosh>
     

    Step 3: Internal Commands
    Add support for internal shell commands: cd and pwd.

    gosh> cd /usr/lib
    c1 0: cd
    c1 1: /usr/lib
    Running Command
    ---------------
    change dir: success
    ---------------
    Command Returned Exit Code 0

    gosh> pwd
    c1 0: pwd
    Running Command
    ---------------
    /usr/lib
    ---------------
    Command Returned Exit Code 0
    gosh>
     

    Step 4: Environment Variables
    Add support for environment variables.

    gosh> MYVAR='All work and no play makes Jack a dull boy'
    c1 0: MYVAR='All work and no play makes Jack a dull boy'
    Running Command
    ---------------
    define new variable: success
    ---------------
    Command Returned Exit Code 0

    gosh> echo $MYVAR
    c1 0: echo
    c1 1: $MYVAR
    Running Command
    ---------------
    All work and no play makes Jack a dull boy
    ---------------
    Command Returned Exit Code 0
    gosh>
     

  2. Unnamed Pipes

    Step 1: background

    A pair of related processes can use an 'unnamed pipe' to pass information between them. When a pipe is created, the operating system sets up two file descriptors: one that can be read from and one that can be written to. Any data put into the pipe (by writing to the 'write' side of the file descriptor) by one process can be grabbed by another (by reading from the 'read' file descriptor). One common use of this function/structure is to create a pipe and write the stdout from one process to the pipe's write file descriptor and read from the pipe's read file descriptor to another program's stdin. We use this system all of the time:

    cat /etc/passwd | grep root

    We are 'piping' the stdout from 'cat /etc/passwd' to the stdin of 'grep root'. The program that usually sets up this pipe for you is the user shell. The program flow is as follows:

    program has two commands to run
    program sets up a pipe using 'pipe()'
    program forks
    the parent closes the pipe writer file descriptor
    the parent runs 'dup2()' to duplicate it's stdin to the pipe's reader
    the child closes the pipe reader file descriptor
    the child runs 'dup2()' to duplicate it's stdout to the pipe's writer
    both parent and child run 'execvp()' or similar to run the commands

    Step 2: my_upipe

    In this portion of the lab project, you will write a simple program that creates a pipe between two processes given on the command line. The syntax for this program is as follows:

    ./my_upipe "cat /etc/passwd" "grep root"

    You may want to borrow code from your gosh project to handle the tokenizing of the two commands given. We will test the following commands on CS linux boxes:

    gawaine:$ ./my_upipe "cat /etc/services" "more"
    # /etc/services:
    # $Id: services,v 1.4 1997/05/20 19:41:21 tobias Exp $
    #
    # Network services, Internet style
    ...
    ...
    --More--
    ...
    ...

    gawaine:$ ./my_upipe "cat /etc/passwd" "grep root"
    root:x:0:0:Super-User:/:/bin/bash

    gawaine:$ ./my_upipe "ls -a -l /usr/bin" "wc"
    501 4668 32853

    Step 3: help

    There are example programs that use 'pipe' in the BLP. Also, you can look in /home/mark/pub/51081/pipes for some example code.

    Step 4: Deliverables

    A tar file that contains all code: *.c and *.h (if necessary) and a script file shows how your codes work.
  3. Named Pipes

    Step 1: background

    A pair of unrelated processes can use a 'named pipe' to pass information between them. Unlike 'unnamed pipes', however, 'named pipes' are accessed as a file on the file system. This allows a situation where two processes started in separate shells can communicate with eachother through a 'named pipe' on the file system. A named pipe, or FIFO, can be created using the 'mkfifo()' function. It can be removed (like any other file on the file system) using the 'unlink()' function. Once a named pipe file exists, programs can open it like they would other files and then use the file descriptor obtained to perform regualar file IO operations on the (read, write, close...).

    Step 2: my_npipe

    You will write two simple programs 'my_npipe_reader.c' and 'my_npipe_writer.c' that use a named pipe to communicate. The 'my_npipe_reader' program will set up a named pipe using 'mkfifo()', open it read only, and read strings from it until it receives the string 'exit'. The writer will open the named pipe file, read strings from the user and write them to the named pipe. When the user enters 'exit', the program will write the string to the pipe and then exit. Execution should look something like this (note that you must start the reader first):
    reader:
    gawaine:$ ./my_npipe_reader
    Creating named pipe: /tmp/mypipe
    Waiting for input...Got it: 'hello world'
    Waiting for input...Got it: 'foober goober'
    Waiting for input...Got it: 'exit'
    Exiting


    writer:
    gawaine:$ ./my_npipe_writer
    Opening named pipe: /tmp/mypipe
    Enter Input: hello world
    Writing buffer to pipe...done
    Enter Input: foober goober
    Writing buffer to pipe...done
    Enter Input: exit
    Writing buffer to pipe...done
    Exiting

    Note that the 'my_npipe_reader' and 'my_npipe_writer' need to be executed in separate shells at the same time. The reader stops at 'Waiting for input...' until it receives data from the pipe (the read completes).

    Step 3: Help

    Use BLP as a reference, there are many good code examples for named pipe usage. Also, see /home/mark/pub/51081/pipes for example code.

    Step 4: Deliverables

    A tarball that contains all code: *.c and *.h (if necessary), makefiles, and a script file shows how your code works.
  4. MARKS DISTRIBUTION

    1. Excercise 1:

    2. Excercise 2:

    3. Excercise 3:

    4. Total Marks: 25


    Atilla Soner Balkir