CS 3733 Operating Systems, Fall 2004 Assignment 3


Due Thursday, October 28

In this assignment you will write a very simple make utility and then extend it to concurrent operation using multiple processes using fork. In later assignments you will:

You have 3 weeks to do this assignment. There is much to do and there may be some complications which will not be obvious until you make some progress. Start the assignment early. If this assignment is beyond your capabilities as a programmer, you need to find this out before the drop date which is 2 days before the assignment is due.

Start this assignment by reading the discussion of make starting on page 807 of USP.

Motivation
Writing a complete make utility is difficult because it allows for very general types of dependencies. We will simplify the rules and syntax but retain the ability to manage the types of projects that are most common.

You used make in Part 2 of Assignment 1. In this part of the assignment, the program in test_check_older used utility functions in get_modify_time.c, compare_modify_times.c, and check_older.c. The makefile here could have been used for that compilation. It creates files get_modify_time.o, compare_modify_times.o and check_older.o if any of the required source files are newer than the corresponding object files. It also creates a new executable, test_check_older if this file is older than any of the object files it is made from.

In this assignment you will be writing a program to manage similar simple compilations using multiple processes to compile the individual pieces. We will modify the syntax used by make to simplify the programming.

The make utility uses a makefile description file containing dependency information. Your program, mymake, will use a mymakefile dependency file containing simplified dependencies. The mymakefile consists of pairs of lines separated by an optional blank line (a line containing no tokens). The first line of each pair (the target line) will contain the dependencies of one of the utilities that make up the project. The first token is the name of the target and the rest of the tokens are the components of that target. If any of the components are newer than the target, the target needs to be recreated. The second line of the pair (the rule line) gives the command to recreate the target. Note that unlike the full make utility, we assume that all of the components exist. The syntax does not require a colon between the target and the components, it does not require a tab at the start of a rule line and we allow only one rule line per target.

The significance of the optional blanks lines will be discussed later.

You can find a simple example of a mymakefile for this assignment here.

Each part of this assignment will be put in a separate directory. Once a part is working, do not change the contents of that directory. Read each part of the assignment completely before starting to write code for that part.

Part 0:
Copy all of the files from your assign1/part2 directory into a new directory, assign3/part0. You should be able to run the program as before. If your utilities were not working for Assignment 1, you need to correct them now. Make sure you have removed all of the lint warnings that need to be removed. See the discussion of Assignment 1.

Create a file called filetimes.c and put in it the three functions: get_modify_time, compare_modify_times and check_older. Only the function check_older should have external linkage. You can now delete all of the .c files except for this one and test_check_older.c. Modify the makefile to use filetimes to compile and lint test_check_older.

Part 1:
Copy everything from your part0 directory into a new part1 directory. Write a function called compare_target that takes a single string parameter in the form of a target line (see the motivation above). The function returns true (1) if the target needs to be recompiled and false (0) otherwise. This function should be in the file makeutil.c. Use the check_older of Assignment 1. You can make the needed parameters using makeargv. Get a copy of makeargv.c here. Make sure that your compare_target function does not have a memory leak.

Write a main program called test_compare_target to test compare_target. Modify the makefile to compile and lint this program. The program should take one command line argument which is a target line in the form given in the discussion above. Run this program by putting the target line as a single command line argument surrounded by double quotes.

Part 2:
Copy everything from your part1 directory into a new part2 directory. Write a program called mymake.c that takes a single command line argument, the name of a file. Assume that the file to be read in has the format of the mymakefile dependency file. That is, it contains pairs of lines with a single optional empty line in between. Your program should do the following in a loop:

The functions execute_line, do_not_execute_line and wait_for_completion should be in the file makeutil.c. Create a file called makeutil.h that contains the prototypes of the public functions in makeutil.c. Include this in mymake.c. Get a copies of restart.c and restart.h and use the functions there so that your programs will be safe to use with signals. Use r_open2 to open the file and readline to read in a line. Read the description of readline starting on page 818 of USP. You may consider it an error if the input file has a line longer than 1024 bytes. If this error occurs, print an informative message and exit. Be sure you understand item 16 in the Programming Style guidelines. Modify your makefile to compile and lint this program. Create your own mymakefile file for testing this.

Part 3:
Copy everything from your part2 directory into a new part3 directory. Modify execute_line so that it executes the line passed as a parameter. You will need to call makeargv to make an argument list and fork a child which will execute execvp. Using this form will allow the exec to use the path environment to find the executable. Make sure there are no memory leaks. Do not wait for the child process to complete. The function execute_line will return 0 if the fork is successful and 1 otherwise. Have mymake check the return value. If it is 1, print an appropriate message and exit.

Since the individual compilations are done by separate children, they may not complete in the order they appear in the mymakefile. In the example file given in the discussion at the start of this assignment, we do not want to compile test_check_older until the other compilations have completed. We have inserted a blank line in this file before the test_check_older target. This indicates that all other compilations must complete before we continue. This is an example of a synchronization point. The synchronization will be handled by the wait_for_completion function.

Rewrite wait_for_completion so that it waits for all children before returning. Use r_wait which handles restarting when a signal occurs. Call this in a loop until it returns an error. The only possible error is ECHILD indicating that no unwaited for children remain. Each other time r_wait returns, check for normal termination. Be sure to use the appropriate macros to test the exit status as in Example 3.22 of USP. If all children have completed successfully, return 0. Otherwise return 1, but do not return until all children have finished. Modify mymake so that it checks the return value of wait_for_completion. It should print an informative message and exit if wait_for_completion returns 1.

Testing your program.
You are responsible for creating tests cases for testing your program. After you have done this, copy your mymake executable into a clean directory and untar this file in that directory. Execute each of the following and save the results for turning in:
./mymake mymakefile1
./mymake mymakefile2
./mymake mymakefile3
./mymake mymakefile4

Handing in your assignment
Use this cover sheet. Consecutively number all of the other pages you turn in. If a part of this assignment is not completely working, you must describe in detail exactly how far you got in trying to get it to work, what you tested, and what results you got from these tests. This is necessary if you want to receive partial credit for the work you did.