The goal of this homework assignment is to allow you to practice using
system calls involving processes and signals in C by
implementing two new Unix utilities: moveit and timeit.
moveit: The first utility allows users to interactively rename specified
files using their favorite text $EDITOR.
timeit: The second utility allows users to compute the elapsed time of
an application while also enforcing a timeout (or cut off time).
Be careful with the fork system call. To prevent a fork bomb, start off by always putting a sleep after a fork, that way you have time to kill the processes.
If you still manage to create a fork bomb, do not simply go to another machine and run the same program. Notify the csehelp@nd.edu and explain what happened.
For this assignment, record your source code and any responses to the
following activities in the homework09 folder of your assignments
GitLab repository and push your work by noon Saturday, April 21.
Before starting this homework assignment, you should first perform a git
pull to retrieve any changes in your remote GitLab repository:
$ cd path/to/repository # Go to assignments repository $ git checkout master # Make sure we are in master branch $ git pull --rebase # Get any remote changes not present locally
Next, create a new branch for this assignment:
$ git checkout -b homework09 # Create homework09 branch and check it out
To help you get started, the instructor has provided you with some starter code.
# Download starter code tarball $ curl -LO https://www3.nd.edu/~pbui/teaching/cse.20289.sp18/static/tar/homework09.tar.gz # Extract starter code tarball $ tar xzvf homework09.tar.gz # Add and commit starter code $ git add homework09 $ git commit -m "homework09: starter code"
Once downloaded and extracted, you should see the following files in your
homework09 directory:
homework09
\_ Makefile # This is the Makefile for building all the project artifacts
\_ README.md # This is the README file for recording your responses
\_ moveit.c # This is the C99 source file for the moveit utility
\_ timeit.c # This is the C99 source file for the timeit utility
\_ test_moveit.sh # This is the Shell test script for the moveit utility
\_ test_timeit.sh # This is the Shell test script for the timeit utility
MakefileBefore you implement any of the utilities, you should first complete the
provided Makefile. Remember, the Makefile contains all the rules or
recipes for building the project artifacts (e.g. moveit and timeit).
Although the provided Makefile contains most of the variable definitions
and test recipes, you must add the appropriate rules for moveit, and
timeit.
You must use the CC, CFLAGS, LD, LDFLAGS, AR, and ARFLAGS
variables when appropriate in your rules.
You can complete the Makefile with a pattern rule by compiling from the
.c source directly into an executable.
Once you have a working Makefile, you should be able to run the following
commands:
$ make # Build all targets Compiling and Linking moveit... Compiling and Linking timeit... $ make clean # Clean targets and intermediate files Cleaning... $ make -n # Simulate build with tracing output echo Compiling and Linking moveit... gcc -g -gdwarf-2 -Wall -Werror -std=gnu99 -fPIC -o moveit moveit.c echo Compiling and Linking timeit... gcc -g -gdwarf-2 -Wall -Werror -std=gnu99 -fPIC -o timeit timeit.c $ make test # Run all tests ...
Depending on your compiler, you may see some warnings with the initial starter code. Likewise, the test programs will all fail in some fashion.
You must include the -Wall flag in your CFLAGS when you compile. This
also means that your code must compile without any warnings, otherwise points
will be deducted.
The details on what you need to implement are described in the following sections.
moveit (7 Points)For the first activity, write a program, moveit, that reads in filenames
from the command line arguments, stores them to a temporary file, opens an
$EDITOR on the temporary file, allows the user to modify the filenames, and
then renames the files based on the information in the temporary file as
shown below:
Note: Your program must remove the temporary file with the unlink system call.
moveit.cTo implement the moveit utility, you are to complete the provided
moveit.c source file which contains the following macros and functions:
#define streq(a, b) (strcmp(a, b) == 0)
##define strchomp(s) (s)[strlen(s) - 1] = 0
/** * Display usage message and exit. * @param progname Program name. * @param status Exit status. */ void usage(const char *progname, int status);
This provided
usagefunction displays the help message and terminates the process with the specifiedstatuscode.
/** * Save list of file paths to temporary file. * @param files Array of path strings. * @param n Number of path strings. * @return Newly allocated path to temporary file. */ char * save_files(char **files, size_t n);
This
save_filesfunction writes each of the files specified in thefilesarray to a temporary file (one file per line) and returns the path of this temporary file.
/** * Run $EDITOR on specified path. * @param path Path to file to edit. * @return Whether or not the $EDITOR process terminated successfully. */ bool edit_files(const char *path);
This
edit_filesfunction executes the users$EDITORon the specified file and returns the exit status of the$EDITORprocess.
$EDITOR from your process environment (fall back
to vim, nano, or emacs if the environment variable does not exist).For this assignment, you must use low-level system calls for processes such as fork, exec, wait, and kill.
/** * Rename files as specified in contents of path. * @param files Array of old path names. * @param n Number of old path names. * @param path Path to file with new names. * @return Whether or not all rename operations were successful. */ bool move_files(char **files, size_t n, const char *path);
This
move_filesfunction renames old files specified in thefilesarray with the new names contained in thepathfile (ie.files[0]corresponds to the first line inpath).
You must check if the system calls you use fail and handle those situations appropriately.
test_moveit.shAs you implement moveit, you can test it by running the test-moveit
target:
$ make test-moveit Testing moveit... system calls ... Success usage ... Success usage (no arguments) ... Success usage (valgrind) ... Success deadpool spidey rogue -> deadpool spidey rogue ... Success deadpool spidey rogue -> deadpool spidey rogue (valgrind) ... Success deadpool spidey rogue -> deadpool spidey rogue (vim) ... Success deadpool spidey rogue -> deadpool spidey rogue (vim, valgrind) ... Success deadpool spidey rogue -> deadpool spidey rogue (nano) ... Success deadpool spidey rogue -> deadpool spidey rogue (nano, valgrind) ... Success deadpool spidey rogue -> deadpool spidey rogue (emacs) ... Success deadpool spidey rogue -> deadpool spidey rogue (emacs, valgrind) ... Success deadpool spidey rogue -> deadpool spidey rogue (NOPE) ... Success deadpool spidey rogue -> deadpool spidey rogue (NOPE, valgrind) ... Success deadpool spidey rogue -> batman superman wonderwoman ... Success deadpool spidey rogue -> batman superman wonderwoman (valgrind) ... Success deadpool spidey rogue -> batman superman wonderwoman (NOPE) ... Success deadpool spidey rogue -> batman superman wonderwoman (NOPE, valgrind) ... Success batman superman wonderwoman -> deadpool batman rogue ... Success batman superman wonderwoman -> deadpool batman rogue (valgrind) ... Success batman superman wonderwoman -> deadpool batman rogue (NOPE) ... Success batman superman wonderwoman -> deadpool batman rogue (NOPE, valgrind) ... Success batman superman -> deadpool spidey rogue ... Success batman superman -> deadpool spidey rogue (valgrind) ... Success batman superman -> deadpool spidey rogue (NOPE) ... Success batman superman -> deadpool spidey rogue (NOPE, valgrind) ... Success deadpool spidey rogue -> batman superman ... Success deadpool spidey rogue -> batman superman (valgrind) ... Success deadpool spidey rogue -> batman superman (NOPE) ... Success deadpool spidey rogue -> batman superman (NOPE, valgrind) ... Success doom -> thing ... Success doom -> thing (valgrind) ... Success doom -> thing (rm) ... Success doom -> thing (rm, valgrind) ... Success doom -> thing (false) ... Success doom -> thing (false, valgrind) ... Success Score 7.00
timeit (7 Points)For the second activity, write a program, timeit, that executes the given
command until a timeout is reached or the program terminates as show below:
If the verbose mode is enabled, your timeit should display the following
sort of debugging messages:
$ ./timeit -v
timeit.c:65:parse_options: Timeout = 10
timeit.c:66:parse_options: Verbose = 1
Usage: ./timeit [options] command...
Options:
-t SECONDS Timeout duration before killing command (default is 10)
-v Display verbose debugging output
$ ./timeit -t 5 -v sleep 1
timeit.c:65:parse_options: Timeout = 5
timeit.c:66:parse_options: Verbose = 1
timeit.c:80:parse_options: Command = sleep 1
timeit.c:107:main: Registering handlers...
timeit.c:110:main: Grabbing start time...
timeit.c:125:main: Sleeping for 5 seconds...
timeit.c:119:main: Executing child...
timeit.c:95:handle_signal: Received interrupt: 17
timeit.c:134:main: Waiting for child 32151...
timeit.c:139:main: Child exit status: 0
timeit.c:141:main: Grabbing end time...
Time Elapsed: 1.0
$ ./timeit -t 1 -v sleep 2
timeit.c:65:parse_options: Timeout = 1
timeit.c:66:parse_options: Verbose = 1
timeit.c:80:parse_options: Command = sleep 2
timeit.c:107:main: Registering handlers...
timeit.c:110:main: Grabbing start time...
timeit.c:125:main: Sleeping for 1 seconds...
timeit.c:119:main: Executing child...
timeit.c:127:main: Killing child 32162...
timeit.c:134:main: Waiting for child 32162...
timeit.c:95:handle_signal: Received interrupt: 17
timeit.c:139:main: Child exit status: 9
timeit.c:141:main: Grabbing end time...
Time Elapsed: 1.0
Note: If the time limit is exceed, the parent should kill the child and wait for it.
timeit.cTo implement the timeit utility, you are to complete the provided
timeit.c source file which contains the following macros and functions:
#define debug(M, ...) \ if (Verbose) { \ fprintf(stderr, "%s:%d:%s: " M, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ }
This
debugmacro displays the specified formatted message only if theVerboseglobal variable istrue. The message includes the name of the file, the line number, and the function at which the macro is called.
/** * Display usage message and exit. * @param progname Program name. * @param status Exit status. */ void usage(const char *progname, int status);
This provided
usagefunction displays the help message and terminates the process with the specifiedstatuscode.
/** * Parse command line options. * @param argc Number of command line arguments. * @param argv Array of command line argument strings. * @return Array of strings representing command to execute. */ char ** parse_options(int argc, char **argv);
This
parse_optionsfunction processes the command line arguments by setting theTimeoutandVerboseglobal variables and by constructing an array of strings to represent the specifiedcommand.
/** * Handle signal. * @param signum Signal number. */ void handle_signal(int signum);
This provided
handle_signalfunction simply displays a message about thesignumwhenever a signal is triggered.
The main logic for timeit will be in the main function. There are
multiple approaches on how to accomplish the task of running a program with a
time limit and two such methods are shown below:
In both approaches, the main function registers some signals using either
signal or sigaction. Then the parent process performs a fork and the
child process executes the specified command using one of the exec
variants.
In the first approach, after the fork, the parent process simply
performs a nanosleep on the specified Timeout. It will then either
complete and thus must kill the child process, or it will be interrupted by
the SIGCHLD signal. Afterwards, it will wait to get the child's exit
status.
In the second approach, after the fork, the parent process sets an
alarm based on the specified Timeout. It then simply calls wait. If
the alarm triggers, then the handler should kill the child process.
Otherwise, if the child terminates before the Timeout then the parent
cancels the alarm and completes its wait to retrieve the child's exit
status.
Either approach is acceptable, although the first is recommended as it is simpler and less error-prone.
To measure the elapsed time, you must use the clock_gettime function with
the CLOCK_MONOTONIC clock identifier (ie. older documentation may suggest
gettimeofday, but use of this function is now discouraged).
If you get an "undefined reference to clock_gettime" linker error when
compiling timeit, then you need to append the -lrt library flag to your
compiler command as noted in the manual page for clock_gettime.
test_timeit.shAs you implement timeit, you can test it by running the test-timeit
target:
$ make test-timeit Testing timeit... system calls ... Success usage (-h) ... Success usage (-h, valgrind) ... Success usage (no arguments) ... Success usage (no arguments, valgrind) ... Success usage (-v, no command) ... Success usage (-v, no command, valgrind) ... Success usage (-t 5 -v, no command) ... Success usage (-t 5 -v, no command, valgrind) ... Success sleep ... Success sleep (valgrind) ... Success sleep 1 ... Success sleep 1 (output) ... Success sleep 1 (valgrind) ... Success -v sleep 1 ... Success -v sleep 1 (output) ... Success -v sleep 1 (valgrind) ... Success -t 5 -v sleep 1 ... Success -t 5 -v sleep 1 (output) ... Success -t 5 -v sleep 1 (valgrind) ... Success sleep 5 ... Success sleep 5 (output) ... Success sleep 5 (valgrind) ... Success -v sleep 5 ... Success -v sleep 5 (output) ... Success -v sleep 5 (valgrind) ... Success -t 1 sleep 5 ... Success -t 1 sleep 5 (output) ... Success -t 1 sleep 5 (valgrind) ... Success -t 1 -v sleep 5 ... Success -t 1 -v sleep 5 (output) ... Success -t 1 -v sleep 5 (valgrind) ... Success Score 7.00
In your README.md, answer the following questions:
As discussed in class, working with system calls can be tricky because
unlike most normal functions, they can fail. For each of the following
scenarios involving moveit, identify which system calls would fail (if any)
and described you handled that situation:
No arguments passed to moveit.
$EDITOR environmental variable is not set.
User has run out of processes.
$EDITOR program is not found.
Temporary file is deleted before moving files.
Destination path is not writable.
As described in the project write-up, the parent is doing most of the
work in timeit since it forks, times, and waits for the child
(and possibly kills it), while the child simply calls exec. To distribute
the work more evenly, Bill proposes the following change:
Have the child set an
alarmthat goes off after the specifiedtimeout. In the signal handler for thealarm, simply callexit, to terminate the child. This way, the parent just needs towaitand doesn't need to perform akill(since the child will terminate itself after thetimeout).
Is this a good idea? Explain why or why not.
Professor Tim Weninger, Notre Dame's resident Reddit expert, lamblasted the instructor for failing to introduce students to cron in previous course sections. To rectify this, you are to write a simple shell or Python script does something interesting and runs periodically at some time interval using cron. Here are some resources regarding cron:
To receive credit, simply demonstrate the cronjob triggering to the instructor or a TA.
To submit your assignment, please commit your work to the homework09 folder
of your homework09 branch in your assignments GitLab repository.
Your homework09 folder should only contain the following files:
MakefileREADME.mdmoveit.ctimeit.ctest_moveit.shtest_timeit.shNote: Don't include any object files or executables.
#-------------------------------------------------- # BE SURE TO DO THE PREPARATION STEPS IN ACTIVITY 0 #-------------------------------------------------- $ cd homework09 # Go to Homework 07 directory ... $ git add Makefile # Mark changes for commit $ git add moveit.c # Mark changes for commit $ git commit -m "homework09: moveit" # Record changes ... $ git add Makefile # Mark changes for commit $ git add timeit.c # Mark changes for commit $ git commit -m "homework09: timeit" # Record changes ... $ git add README.md # Mark changes for commit $ git commit -m "homework09: README" # Record changes ... $ git push -u origin homework09 # Push branch to GitLab
Remember to create a merge request and assign the appropriate TA from the Reading 11 TA List.