CS 485 Systems Programming
Fall 2015
Project 3: The User-defined Pluggin Shell (upsh)

Pluggins are due November 14, 2015
Shell code is due November 18, 2015

In this assignment, you will write a shell that allows the user to interactively execute Unix programs. Your shell, called the User-Defined Plugging Shell (upsh), will read commands typed by the user and then execute them. It will also allow users to enhance the functionality of upsh by writing pluggins that can be loaded into the shell (as a shared library).

To get you started, we will provide you with a modified version of the shell code shown in your textbook. You can download a copy of the code from

http://www.netlab.uky.edu/%7egriff/classes/cs485/handouts/projects/p3-shell/upsh.tar

This code is a working shell capable of reading in commands, parsing them, calling fork() and exec() to run the command, and waiting for the process to complete before issuing another command. Typing 'quit' will get you out of the shell. The code is identical to the code in your textbook except that we have enhanced it to call a new version of the parseline() function which we call p3parseline(). The p3parseline() function is specifically designed to parse the command lines we will use in project 3. Your job is to further enhance this simple shell to support the features described below.

In this project you may work individually or in groups of two (i.e., the maximum group size is two). If you decide to work individually, you will still be responsible for completing all the work. In other words, there is no reduction in work if you decide to do the project by yourself. We expect that most students will work in groups of two, and will likely divide up the work between the two group members. Note that if you do divide up the work, you must educate your teamate about the code that you wrote. During the interactive grading session there is a good chance that you will be asked to explain code that you did not write. You need to be able to describe what is happening in all the code, even if you did not write it. Your description of the code will be an individual grade and counts for 30% of the overall grade. The other 70% of your grade will be the group grade based on how well your group implemented the upsh shell.

You must write the upsh shell in C and/or C++, and your shell must run on your VM. If you are working in pairs, you only need to demo the code running on one of the team member's VMs (and as noted below, only one of the members of the group needs to submit the project).


The User-defined Pluggin Shell (upsh)

You will write a new Unix shell that has similarities to existing shells such as csh, bash, ksh, tcsh, etc. However, it will lack the advanced functionality found in ksh, bash, csh, tcsh, and other shell, and it will have a slightly different command syntax and a different command set. What it lacks in functionality compared to other shells will be made up for in its ability to be extended and enhanced. In particular, your shell will support loading of pluggins that extend the functionality supported by the shell. The details of pluggin extensions are described in Section [*].

Like other shells, your shell will accept two types of commands: ``Built-in'' commands and ``User-Progam'' commands:

Built-in Commands:

% anytext

The % command is a comment command. It tells your shell that this is a comment line. Any text after the % will be ignored. The % must occur as the first token on the line.

setprompt prompt

Set the shell prompt to prompt. The default prompt should be ``upsh> ''.

cd directory_name

This command changes the current directory to directory_name. You do not need to handle ``cd'' with no arguments - (a shell like bash would have taken you to the home directory when given no arguments). See the getwd(3) and chdir(2) system calls.

bgjobs

List all jobs running in the background. Each job listed should be displayed with a job_number that can be used in the fg command. For example, the output might look like
upsh > bgjobs
[1] sleep 10
[2] sleep 50

fg job_number

Move job specified by job_number to the foreground.

loadpluggin file_name.so

This built-in command loads a plugging stored in the shared library file with the name file_name.so.

culater

Exit the upsh shell. You should also accept control-D (end-of-file) on the input stream and treat it as if the user had typed ``culater''. That is, if a user types in cntl-D at the command line, your shell will do exactly the same thing it does when it receives the built-in command ``culater''.



User-Program Commands:

cmd [arg]* [ $<$ infilename ] [ $>$ outfilename ] [ & ]

This will execute a program (i.e., an executable file). It has the following parameters (where some are optional):

Example Commands:

        setprompt "my shell > "
        cd /usr/bin
        cd ./bin
        cd ../misc/oldstuff
        cd /etc
        /bin/ls
        /bin/ls -l
        /bin/echo "Hello World"
        /bin/cat hosts > /tmp/output
        /usr/bin/wc -l /tmp/output
        /usr/bin/wc -l < /tmp/output
        /bin/sleep 10 &
        /bin/mkdir /tmp/mypluggins
        /bin/copy uklogo.so /tmp/mypluggins/uklogo.so
        /bin/ls -l -F -g -s /tmp/mypluggins
        loadpluggin /tmp/mypluggins/uklogo.so
        uklogo
        culater

Background Commands

The upsh shell code that we have provided you is able to determine if a command should be run in the foreground or background. It will wait() for commands executed in the foreground, but will not wait for commands executed in the background.

You must add code to keep track of background commands so that you can list them when the users types the bgjobs built-in command. If the user uses the fg command to move a job to the foreground, your must add code to wait for the process to terminate before issuing a prompt and reading a new command.

In addition to handling the bgjobs and fg built-in commands, your shell needs to reap children processes as they terminate using the SIGCHLD signal and a signal handler. You will need to use the unix signal() system call to catch and detect children that terminate. Your upsh shell must not accumulate zombie processes. When a background process terminates, your shell should print a message to the terminal saying the background process terminate. For example, if the second background process terminated it might print.

[2] sleep 50 - Finished

File Redirection

You will need to add file redirection to your shell. You can do this using using the unix file system calls open(), read(), write(), and close(), and you can use the dup() system call to ``dup'' a file descriptor onto standard input or standard output. (Be careful to close unused file descriptors or file descriptors that are no longer needed). When redirecting stdout to an output file, the output file should start out as an empty file. If the output file does not exist, you should open it as a new file. If the file already exists, you will need to truncate it to length 0, effectively overwriting whatever was in the file.

Pluggins

Your user-defined pluggin shell (upsh)1 must allow users to define new built-in functions and dynamically load them using the loadpluggin command. The loadpluggin command takes the name of a (.so) shared library file (i.e., the pluggin) as its argument. This allows users to define new functionality (built-in commands) and dynamically add the new functionality into the shell. As a result, the functionality available in your upsh can evolve over time. Users can share pluggins with one another so that all users benefit from the new functionality being developed by other members of the upsh community. In this case, the community is our class and you must write two pluggins to contribute to the community (more on this below).

Your shell will use the Unix system calls dlopen(), dlsym(), and dlclose() to dynamically link the pluggin into your shell. Every pluggin will have offer one or more new built-in commands. In order for your shell to know what built-in commands are offered by a pluggin, all pluggins include a global variable called pluggin_method which is a structure that contains the name of the new built-in command, the name of the function that implements the command, and the name of function that should be executed on every command the user types. Some of the names may be ``blank'' (i.e., have a strlen() of 0). The format of the structure and the definition of pluggin_method is defined as follows:

 struct NewBuiltIn {
     char CommandName[64];   /* Name of the Built-in command users can type */
     char FunctionName[64];  /* Name of the function in the code */
     char AnalyzerName[64];  /* Name of an analyzer function to call on every command */
 };

 struct NewBuiltIn pluggin_method; /* Description of a pluggin's built-in command */
The NewBuiltIn structure includes a character string representing the name of the built-in command that can be used/invoked by users. The structure also includes the name of the function in the .so file that should be invoked when a user tries to use the new built-in command. Lastly, it includes the name of a command-analyzer function (in the .so file) that should be invoked every time a user types any command. The analyzer function should be passed the entire command line entered by the user, and the analyzer must be called before calling p3parseline(). The analyzer will return a pointer to a character string that should be passed to p3parseline(). Invoking a pluggin's analyzer function on every command allows a pluggin to ``see'' all the commands being typed and modify (or record) the command before it is passed to p3parseline(). As noted above, some of the names in the structure may be ``blank'' (i.e., have a strlen() of 0). For example, if the pluggin is designed to record every command typed by the user, it may only have a AnalyzerName.

Note that you can load multiple pluggins, and each of the pluggins may define a ``command analyzer'' function. As a result, your shell may need to execute several ``command analyzer'' functions before calling p3parseline(). In this case, you should start by calling the command analyzer function that was loaded first. You should then pass the character string returned by that analyzer function to the analyzer function that was loaded second. The character string produced by the second analyzer function should then be passed to the analyzer function that was loaded third, and so on, until you have called all the anyalzer functions.

Your shell will need to use dlsym() to find the address of the pluggin_methods array and will then need to read the list of names in the structure, adding the CommandName to the list of built-in commands accepted by the shell, and calling dlsym() to find the address of the FunctionName and AnalyzerName functions so they can be called by your shell. If the user types the name of a new built-in command, you will invoke the function specified by FunctionName. The only parameter to FunctionName is argv (the list of tokens returned by p3parseline(). The only parameter to AnalyzerName is the command line typed by the user.

Not only will you add support for pluggins to your upsh shell, but you will write two pluggins to contribute to the community (i.e., to our class). The first pluggin you write must be a simple pluggin that is basically useless, but is fun. For example, you might write a plugging that prints out the UK logo in ASCII on a terminal. The second pluggin you write must be something that could be useful to a user. Example useful pluggins might include:

As an example, consider the following ``uklogo'' pluggin which can be found at
http://www.netlab.uky.edu/%7egriff/classes/cs485/handouts/projects/p3-shell/pluggins/uklogo.tar
It defines a file called uklogo.c

#include <stdio.h>

struct NewBuiltIn {
    char CommandName[64];   /* Name of the Built-in command users can type */
    char FunctionName[64];  /* Name of the function in the code */
    char AnalyzerName[64];  /* Name of an analyzer function to call on every command */
};

/* Description of a pluggin's built-in command functions and command to type */
struct NewBuiltIn pluggin_method = { "uklogo", "uklogo", "" }; 

int uklogo(char **argv) {
  printf("\n");
  printf("U     U     K    K   \n");
  printf("U     U     K  K     \n");
  printf("U     U     K K      \n");
  printf(" U   U      K  K     \n");
  printf("  UUU       K    K   \n");
  printf("\n");
  return(0);
}

It can be compiled into a .so using the command

gcc -Wall -g -shared -fPIC -I. uklogo.c -o uklogo.so

You must upload your two pluggins to the csportal by Nov 14, 2015. Your pluggins will then be vetted and posted on a class web page where you and the other members of the class can go to download and try your shell with a wide range of pluggings. To submit your pluggin, you must tar up two files:

  1. The C/C++ source code for your pluggin.
  2. A readme file that describes what the pluggin does and how a user can invoke it from the upsh shell.
After tar'ing up these to files, you should upload it to the csportal. We will create a .so file and post it on the class web page.

You can test your pluggin to make sure it is working by running the program
http://www.netlab.uky.edu/%7egriff/classes/cs485/handouts/projects/p3-shell/upsh-pluggin-tester
This is a binary executable that supports the loadpluggin command.

Testing Your Program

Clearly, you can test your program by typing commands interactively at the prompt. However, an easier way to test your program is to have it read a file of commands and redirected it into standard input. For example, if you have a file named ``testfile'' that contained:

        cd directory_1
        /bin/cat file1 file2 file3
        ls -lt
        /bin/wc file1
you could test all of these commands simply by typing:
        upsh < testfile
Your program must be able to execute commands fed to it this way. This is how we will test your program.

Helpful Manual Pages

The following unix routines may be helpful in the assignment: fork(2), execv(2), execve(2), chdir(2), getpwnam(3c), malloc(3c), perror(3c), exit(2), wait(2), waitpid(2), kill(2), getcwd(3c), and string(3c) You can view these manual pages with the Unix man command. Use the -s option to specify the section of the Unix manual (shown in paranthesis above). For example, man fork or man -s 2 chdir.

What to Submit

*** Your code must compile and run on the Ubuntu VM that you have been assigned or you will receive no credit.

Only one person from your group should submit the code. Consequently it is important the you remember to list both people from your group in the README file.

First, you must submit your two pluggins by November 14. You will submit two different files to the csportal, one for each of the pluggins. Each pluggin submission should be a tar file with the two files mentioned above (C/C++ code and a readme file for that plugging).

Second, you will submit all your shell code plus a documentation file. You should not submit .o files or other binary files. To create your submission, tar and compress all files that you are submitting (e.g., tar czf proj3.tgz projectdirectory).

You should submit the following:

Once you create the text file, go to https://www.cs.uky.edu/csportal to upload your file. Browse to, or type in, the name of your .tgz file and click the Submit button to upload your .tgz file. If the upload succeeds you will be given a confirmation number.

Note, you may upload your .tgz file as many times as you like. Each submission will overwrite the previous submission, so we will only have a copy of your last submission. Also note that the system timestamps your submission with the date/time of the last submission. The system does allow late submissions (which will be charged a late penalty).



Footnotes

... (upsh)1
Inspired by G. Back at vt.edu.


James Griffioen 2015-11-14