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 13.
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 -LOk https://gitlab.com/nd-cse-20289-sp19/cse-20289-sp19-assignments/raw/master/homework09/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
Makefile
Before 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
and CFLAGS
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 -Wall -Werror -std=gnu99 -fPIC -o moveit moveit.c echo Compiling and Linking timeit... gcc -g -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
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.c
To 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
usage
function displays the help message and terminates the process with the specifiedstatus
code.
/** * 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_files
function writes each of the files specified in thefiles
array 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_files
function executes the users$EDITOR
on the specified file and returns the exit status of the$EDITOR
process.
$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_files
function renames old files specified in thefiles
array with the new names contained in thepath
file (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.sh
As 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 6.00
timeit
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 exceeded, the parent should kill the child and wait for it. Moreover, the parent should always return the child's exit status as its own exit status.
timeit.c
To 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
debug
macro displays the specified formatted message only if theVerbose
global 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
usage
function displays the help message and terminates the process with the specifiedstatus
code.
/** * 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_options
function processes the command line arguments by setting theTimeout
andVerbose
global 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_signal
function simply displays a message about thesignum
whenever 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.sh
As 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 6.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, Logan proposes the following change:
Have the child set an
alarm
that goes off after the specifiedtimeout
. In the signal handler for thealarm
, simply callexit
, to terminate the child. This way, the parent just needs towait
and 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:
Makefile
README.md
moveit.c
timeit.c
test_moveit.sh
test_timeit.sh
Note: 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.