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 (you should eventually remove this sleep once you are confident your code is working).
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
GitHub repository and push your work by noon Saturday, April 20.
Before starting this homework assignment, you should first perform a git
pull
to retrieve any changes in your remote GitHub repository:
$ cd path/to/repository # Go to assignments repository
$ git switch 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 the following skeleton code:
# Go to homework09 folder
$ cd homework09
# Download Makefile
$ curl -LO https://www3.nd.edu/~pbui/teaching/cse.20289.sp24/static/txt/homework09/Makefile
# Download C skeleton code
$ curl -LO https://www3.nd.edu/~pbui/teaching/cse.20289.sp24/static/txt/homework09/moveit.c
$ curl -LO https://www3.nd.edu/~pbui/teaching/cse.20289.sp24/static/txt/homework09/timeit.c
Once downloaded, you should see the following files in your homework09
directory:
homework09
\_ Makefile # This is the Makefile for building all the project artifacts
\_ moveit.c # This is the moveit utility C source file
\_ timeit.c # This is the timeit utility C source file
Now that the files are downloaded into the homework09
folder, you can
commit them to your git repository:
$ git add Makefile # Mark changes for commit
$ git add *.c
$ git commit -m "Homework 09: Initial Import" # Record changes
After downloading these files, you can run make test
to run the tests.
# Run all tests (will trigger automatic download)
$ make test
You will notice that the Makefile downloads these additional test data and scripts:
homework09
\_ moveit.test.sh # This is the moveit utility test shell script
\_ timeit.test.sh # This is the timeit utility test shell script
You will be using these functional tests to verify the correctness and behavior of your code.
The test
scripts are automatically downloaded by the Makefile
, so any
modifications you do to them will be lost when you run make
again. Likewise,
because they are automatically downloaded, you do not need to add
or commit
them to your git repository.
The Makefile
contains all the rules or recipes for building the
project artifacts (e.g. moveit
, timeit
):
CC= gcc
CFLAGS= -Wall -g -std=gnu99
TARGETS= moveit timeit
all: $(TARGETS)
#------------------------------------------------------------------------------
# TODO: Rules for moveit, timeit
#------------------------------------------------------------------------------
moveit:
timeit:
#------------------------------------------------------------------------------
# DO NOT MODIFY BELOW
#------------------------------------------------------------------------------
...
For this task, you will need to add rules for building moveit
and timeit
executables (you do not need to make any intermediate object files).
You must use the CC
, CFLAGS
variables when appropriate in your
rules. You should also consider using automatic variables such as
$@
and $<
as well.
Once you have a working Makefile
, you should be able to run the following commands:
# Build all TARGETS
$ make
gcc -Wall -g -std=gnu99 -o moveit moveit.c
gcc -Wall -g -std=gnu99 -o timeit timeit.c
# Run all tests
$ make test
Testing moveit...
...
Testing timeit...
...
# Remove generated artifacts
$ make clean
Note: The tests will fail if you haven't implemented all the necessary functions appropriately.
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.
moveit
(5 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:
The functionality of moveit
is demonstrated 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 status Exit status.
**/
void usage(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.
Hint: Use mkstemp to create a temporary file and fdopen to convert the file descriptor into a stream.
/**
* 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.
Hint: Use getenv to read $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
).
Hint: Use rename to move files only if the names are different.
You must check if the system calls you use fail and handle those situations appropriately.
Once you have implemented 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 5.00 / 5.00
Status Success
timeit
(5 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:68:parse_options: Timeout = 10
timeit.c:69: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:68:parse_options: Timeout = 5
timeit.c:69:parse_options: Verbose = 1
timeit.c:85:parse_options: Command = sleep 1
timeit.c:116:main: Registering handlers...
timeit.c:119:main: Grabbing start time...
timeit.c:137:main: Sleeping for 5 seconds...
timeit.c:139:main: Waiting for child 160815...
timeit.c:131:main: Executing child...
timeit.c:145:main: Child exit status: 0
timeit.c:148:main: Grabbing end time...
Time Elapsed: 1.0
$ ./timeit -t 1 -v sleep 2
timeit.c:68:parse_options: Timeout = 1
timeit.c:69:parse_options: Verbose = 1
timeit.c:85:parse_options: Command = sleep 2
timeit.c:116:main: Registering handlers...
timeit.c:119:main: Grabbing start time...
timeit.c:137:main: Sleeping for 1 seconds...
timeit.c:139:main: Waiting for child 160841...
timeit.c:131:main: Executing child...
timeit.c:101:handle_signal: Killing child 160841...
timeit.c:145:main: Child exit status: 9
timeit.c:148: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 status Exit status.
**/
void usage(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 kills the child process whenever aSIGALRM
is invoked.
The main logic for timeit
will be in the main
function. While there are
multiple approaches on how to accomplish the task of running a program with a
time limit, the recommended method is shown below:
In this approach, the main
function first registers the signal_handle
function for SIGALRM
using signal. Then the parent process performs a
fork and the child process executes the specified command
using one of
the exec variants.
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.
To measure the elapsed time, you must use the clock_gettime function with
the CLOCK_MONOTONIC
clock identifier.
Once you have implemented 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
-v find /etc -type f ... Success
-v find /etc -type f (output) ... Success
-v find /etc -type f (valgrind) ... Success
Score 5.00 / 5.00
Status Success
Once you have completed all the activities above, you are to complete the following reflection quiz:
As with Reading 01, you will need to store your answers in a
homework09/answers.json
file. You can use the form above to generate the
contents of this file, or you can write the JSON by hand.
To test your quiz, you can use the check.py
script:
$ ../.scripts/check.py
Checking homework09 quiz ...
Q01 0.80
Q02 0.20
Q03 0.25
Q04 0.25
Q05 0.25
Q06 0.25
Score 2.00 / 2.00
Status Success
For extra credit, you are to use C and system calls to implement your own
version of the TROLL
from Homework 01. Recall, that the TROLL
was a
process that intercepted signals such as SIGINT
and SIGTERM
and taunted
you when you tried to terminate it. Your version of the TROLL
should do
something similar (prevent easy termination)... but its taunts and other
aesthetic details are up to you.
To get credit for this Guru Point, you must show a TA a demonstration of your
TROLL
in action (or attach a video / screenshot to your Pull Request).
You have up until one week after this assignment is due to verify your
Guru Point.
Remember that you can always forgo these Guru Points for two
extra days to do the homework. That is, if you need an extension, you can
simply skip the Guru Points and you will automatically have until Monday to
complete the assignment for full credit.
Just leave a note on your Pull Request of your
intensions.
Note: For this week, we will waive forgoing the Guru Points in order to get two extra days to do the homework. This means, that you can take the self-service extension and still do all of the Guru Points.
To submit your assignment, please commit your work to the homework09
folder
of your homework09
branch in your assignments GitHub repository.
Your homework09
folder should only contain the following files:
Makefile
answers.json
moveit.c
timeit.c
Note: You do not need to commit the test scripts because the Makefile
automatically downloads them.
#-----------------------------------------------------------------------
# Make sure you have already completed Activity 0: Preparation
#-----------------------------------------------------------------------
...
$ git add Makefile # Mark changes for commit
$ git add moveit.c # Mark changes for commit
$ git commit -m "homework09: Activity 1" # Record changes
...
$ git add Makefile # Mark changes for commit
$ git add timeit.c # Mark changes for commit
$ git commit -m "homework09: Activity 2" # Record changes
...
$ git add answers.json # Mark changes for commit
$ git commit -m "homework09: Activity 3" # Record changes
...
$ git push -u origin homework09 # Push branch to GitHub
Remember to create a Pull Request and assign the appropriate TA from the Reading 12 TA List.
DO NOT MERGE your own Pull Request. The TAs use open Pull Requests to keep track of which assignments to grade. Closing them yourself will cause a delay in grading and confuse the TAs.