The directory
/homes/park/pub/cs536/lab1
(symbolic link /homes/park points to /u/riker/u3/park)
accessible from our lab machines in HAAS G050
contains mysh.c which implements a prototypical concurrent server:
a simple, minimalist shell. A concurrent server receives and parses a
client request -- in this case, from stdin (by default the keyboard
attached to a PC in the lab) --
then delegates the actual execution of a requested
task to a worker process or thread.
Many apps in the real-world, including network software, follow the
general structure of concurrent client/server code which are multithreaded programs.
They constitute a baseline for implementing network software.
Create a directory, lab1/, somewhere under your home directory, and a subdirectory v1/ therein. Copy mysh.c to v1/, compile, run and check that you understand how it works. Run the shell with command "ls -l -a" and explain why it does not work. Describe the logical steps of a solution that allow command-line arguments to be supported. Put your answer in lab1.pdf and place the pdf file under lab1/. Modify mysh.c, call it mysh1.c, that calls execvp() instead of execlp() so that it accepts command-line arguments. Your modified code, mysh1.c, will handle command-line arguments by parsing "ls -l -a" into string tokens "ls", "-l", "-a" and passing them to execvp() as its second argument which is an array of pointers to the parsed tokens.
Introduce two built-in shell commands, newprompt and byebye,
where byebye causes your shell to terminate. The command newprompt is
followed by a string where the specified string becomes the new prompt.
For example,
% newprompt hi:
changes the prompt from the default '% ' to 'hi: '.
Add an asynchronous event handling feature to your code so that entering CTRL-C at the prompt does not terminate the shell process. Perform this task by coding a signal handler, void myinthandler(int), for the signal SIGINT which executes when CTRL-C is entered (i.e., catches the SIGINT event), and outputs the message "To quit please enter 'byebye' at the prompt." Your code should ensure that a fresh prompt is printed on stdout on a new line.
Make your code modular by coding functions to carry out subtasks and place each function into its own file of the same name. For example, if a function, int abc(float), is called by your code, place abc() in v1/abc.c. Provide a Makefile in v1/ that compiles your code and generates a binary executable mysh1. As an intermediate step running command make should generate separate .o object files for each .c file. Running 'make clean' should clean up v1/ so that only the source code remains (i.e., .c and .h files besides Makefile). Compile, test, and verify that your code works correctly. Annotate your code so that a competent C programmer can understand what your code is doing.
Create a subdirectory v2/ under lab1. Using mysh1 from Problem 1 as baseline code, implement a FIFO command server, fifocmds, that accepts command requests not from stdin but a client process, fifocmdc. The command server sets up a FIFO called ".myreqfifo" used to accept client requests where the name ".myreqfifo" is hardcoded in both server and client. When fifocmds is executed it creates FIFO .myreqfifo. When fifocmds terminates upon catching SIGINT it deletes .myreqfifo. While debugging please check manually that the file .myreqfifo has been deleted when fifocmds is terminated by entering CTRL-C. The normal procedure for client/server startup is for the server fifocmds to run first, followed by one (default) or more clients (as separate processes). The server and client processes execute on the same machine in the lab. Thus their interaction is through IPC (inter-process communication), not sockets (network communication across different machines). In the event that a client runs before the server and the client tries to submit a request through .myreqfifo resulting in failure, the client should output a suitable error message to stdout then terminate by calling exit(1). When testing, run server and client processes in separate windows for ease of observation of app behavior.
The client uses system call open() to open the server's request FIFO and sends its request using write(). The client's request is provided by a human through stdin. Hence, the client itself acts as a server who prints a prompt 'enter command: ' to stdout and accepts a command request from stdin. To simplify, the input string provided on stdin does not contain command-line arguments. Hence 'ls' and 'ps' are valid, 'ls -a' and 'ps -g -u' is not. If command-line arguments are entered, output a suitable error message followed by a fresh prompt 'enter command: '. Total length of the input string may not exceed 25 characters including end-of-string character '\0'. If it does, the client outputs an error message to stdout and outputs a fresh prompt. To support multiple client processes, we will use a message format where a request ends with the newline character. That is, we are using '\n' as a delimiter which overwrites the end-of-string character '\0'. When using FIFO to receive multiple client requests, the potential for interleaving of characters belonging to two or more requests must be considered. For our Linux lab machines check up to what message size when calling write() on a FIFO atomicity is guaranteed (i.e., no risk of characters belonging to two or messages being interleaved). Although your client will reject command strings exceeding 25 characters, the server fifocmds will not rely on correctness of clients. Instead, when fifocmds reads a command from .myreqfifo it will check that a command terminated by '\n' does not exceed 25 characters exclusive newline. If a request exceeds maximum allowed length, it ignores the command by discarding characters in .myreqfifo until '\n' is reached. Then the next command is parsed.
To facilitate ease of testing, have the server sleep for 1 second before forking a child to execute a command. When the server terminates it will close .myreqfifo (followed by deletion). A client, upon detecting the server's request FIFO has closed, should output a suitable message to stdout and terminate. Detecting when a connection between two communication parties has closed (in this case, at the server side) is, in general, a nontrivial problem. Make sure to understand what system support is provided to detect closure of a FIFO by the server (who is the reader, a client a writer).
As in Problem 1, implement your client/server app in a modular fashion. Provide a Makefile in v2/ to generate fifocmds and fifocmdc. Create README under v2/ that specifies the files and functions of your code, and a brief description of their roles. Please follow a default convention we will use in CS 536 of placing a function in its own file (as noted in Problem 1). Test your client/server app with multiple client processes under various conditions and verify correctness. Note that testing is the most time consuming part of system development, not coding and basic debugging. Because you are not implementing code to be released for public consumption -- all lab assignments are, at the end of the day, exercises aimed to impart learning -- scope of testing is drastically reduced. However, you need to consider the different run-time scenarios that are alluded to in the problem description.
Linux supports system call clone() that allows more refined multithreading implementation than fork(). Without coding and testing a solution, describe in lab1.pdf how you would modify mysh1 to use clone() in place of fork(). Note the main differences between the two implementations.
The Bonus Problem is completely optional. It serves to provide additional exercises to understand material. Bonus problems help more readily reach the 40% contributed by lab component to the course grade.
Electronic turn-in instructions:
i) For problems that require answering/explaining questions, submit a write-up as a pdf file called lab1.pdf. Place lab1.pdf in your directory lab1/. You can use your favorite editor subject to that it is able to export pdf files which several freeware editors do. Files submitted in any other format will not be graded.
ii) We will use turnin to manage lab assignment submissions. Please check that the relevance source code including Makefile are included in the v1/ and v2/ subdirectories of lab1. In the parent directory of lab1, run the command
turnin -c cs536 -p lab1 lab1
You can check/list the submitted files using
turnin -c cs536 -p lab1 -v