Due: Mon, Oct 14, 7:00am
In this project, you will implement mcron -- a mock version of the cron utility. cron executes commands based on a user-supplied schedule; for instance, you can schedule a backup job to run every day at midnight.
Our mock utility will be quite a bit simpler: instead of running an actual command, we'll just log that it should be run. Additionally, whereas cron has a sophisticated syntax for specifying a schedule (see crontab), our tool is much more limited: the user can specify an interval in seconds, and the command "runs" at the end of each interval.
Print a usage statement to stdout and exit with status 0.
Use LOG_FILE as the log file. If LOG_FILE already exists, it is truncated and overwritten. If LOG_FILE is a path, the intermediate directories must already exist (mcron does not attempt to create them). If this option is not specified, then the default is to create a file called mcron.log in the current working directory.
Each line of the configuration file has the following format:
CONFIG_LINE := INTERVAL SPACE COMMAND {NEWLINE|EOF}
INTERVAL is an unsigned integer representing the number of seconds, and COMMAND is the command to "run" (in our case, log). COMMAND extends up to but not including the new line character (or, in the case of the last command, the end-of-file). The COMMAND "runs" every INTERVAL number of seconds (that is, mcron waits INTERVAL seconds and logs the COMMAND, then waits INTERVAL seconds and logs the COMMAND again, and so forth).
Each line of the configuration file also corresponds to a job id. The command on the first line has job id 0, the command on the second line has job id 1, and so forth.
The following is sample code to parse a configuration line. You'll want to read the config file line-by-line, and for each line, call job_from_config_line to parse the line to a struct job. You can then add the struct job to a linked list of all of the jobs. As you develop your program, you will most likely want to modify and add fields to the struct job.
#include <ctype.h>
#include "list.h"
#include "mu.h"
struct job {
struct list_head list;
char *cmd;
int secs;
};
static struct job *
job_new(const char *cmd, unsigned int secs)
{
struct job *job = mu_zalloc(sizeof(struct job));
job->cmd = mu_strdup(cmd);
job->secs = secs;
return job;
}
static void
job_free(struct job *job)
{
free(job->cmd);
free(job);
}
/* Return NULL on invalid configuration line */
static struct job *
job_from_config_line(char *line)
{
char *p = line;
char *cmd;
bool found_space = false;
unsigned int secs;
int err;
mu_str_chomp(line);
while (*p) {
if (isspace(*p)) {
found_space = true;
break;
}
p++;
}
if (!found_space)
return NULL;
*p = '\0';
cmd = p+1;
err = mu_str_to_uint(line, 10, &secs);
if (err != 0)
return NULL;
return job_new(cmd, secs);
}
Each log file line has the following format:
LOG_LINE := TIMESTAMP SPACE JOB_ID SPACE COMMAND NEWLINE TIMESTAMP := YYYY/MM/DD HH:MM:SS UTC
Use the function mu_timestamp_utc() from mu.c to create a timestamp with the above format.
On startup, mcron creates a file in its current working directory called mcron.pid that contains the process ID (PID) of the mcron process.
The format of the PID file is:
PID NEWLINE
A PID is an int (though libc typedef's it as a pid_t); a process can retrieve its PID using the getpid system call.
If mcron.pid already exists, then mcron overwrites it.
For Bonus 2, mcron should handle the realtime signal 35 (SIGRTMIN + 1). The integer value that the sender attaches to this signal is the job ID to run. mcron should lookup this job ID and run it (that is, log it) immediately. The job's schedule, however, should remain unchanged. If the job ID does not exist, mcron should log a one line message to the log file that starts with the timestamp and then includes the word error or Error somewhere in the rest of the line.
To send a realtime signal to mcron, you can compile and use the send_sigqueue sample program. This is merely for your testing purposes; you do not need to include send_sigqueue in your submission.
Submit your project as a zip file via gradescope. Your project must include a Makefile that builds an executable called mcron. Please refer to the instructions for submitting an assignment for details on how to login to gradescope and properly zip your project.
You can download a zip file with the starter code:
./mcron --help
Prints a usage statement to stdout. The statement must start with either Usage or usage; you decide the rest of the message. Conventionally, this option either prints the synopsis or a more verbose statement that also includes a description of the options.
./mcron -h
echo $?
0
The exit status is zero.
./mcron
Exits with a nonzero status.
./mcron nonexistent.conf
Exits with a nonzero status.
./mcron -f a.conf
Exits with a nonzero status.
./mcron a.conf
Produces a PID file called mcron.pid.
./mcron a.conf
Produces a PID file called mcron.pid that contains the process's PID followed by a newline (\n).
./mcron a.conf &
kill -TERM $(cat mcron.pid)
SIGTERM kills the process.
./mcron a.conf &
kill -TERM $(cat mcron.pid)
ls mcron.pid
ls: cannot access 'mcron.pid': No such file or directory
SIGTERM causes the process to delete its pid file before it exits.
./mcron a.conf &
kill -INT $(cat mcron.pid)
SIGINT kills the process.
./mcron a.conf &
kill -INT $(cat mcron.pid)
ls mcron.pid
ls: cannot access 'mcron.pid': No such file or directory
SIGINT causes the process to delete its pid file before it exits.
./mcron a.conf
Logs two commands to mcron.log withing first 7.5 seconds.
./mcron a.conf
Logs one command within first 4.5 seconds with correct format (YYYY/MM/DD UTC 0 /bin/ls\n).
./mcron b.conf
After 11 seconds, the log has entries for: /bin/ls, /bin/netstat, /bin/ls, /bin/ls, /bin/netstat.
./mcron --log-file test.log a.conf
Logs one entry to test.log within first 4 seconds.
./mcron a.conf &
# wait 4 seconds
kill -USR1 $(cat mcron.pid)
SIGUSR1 rotates the log to mcron.log-0.
./mcron a.conf &
# wait 4 seconds
kill -USR1 $(cat mcron.pid)
SIGUSR1 rotates the log to mcron.log-0, which has one entry.
./mcron a.conf &
# wait 4 seconds
kill -USR1 $(cat mcron.pid)
# wait 3 seconds
After 4 seconds, SIGUSR1 rotates the log to mcron.log-0 After another 3 seconds, mcron.log should have only one entry.
./mcron a.conf &
# wait 4 seconds
kill -USR1 $(cat mcron.pid)
# wait 4 seconds
kill -USR1 $(cat mcron.pid)
SIGUSR1 rotates the log to mcron.log-0, and then to mcron.log-1.
./mcron a.conf &
# wait 4 seconds
cp a-new.conf a.conf
kill -HUP $(cat mcron.pid)
# wait 7 seconds
After running mcron.conf for 4 seconds, overwrite a.conf with a-new.conf and send a SIGHUP to re-read the configuration. After another 7 seconds, verify that the log as one entry for /bin/ls and one for /bin/netstat.
./mcron -d 3 a.conf
# wait 7 seconds
After 7 seconds have elapsed, mcron.log should have only a single entry for /bin/ls.
./mcron b.conf
# wait 7 seconds
After 7 seconds have elapsed, send realtime signal #35 with a value of 1 and check that mcron.log has four entries: /bin/ls, /bin/netstat, /bin/ls, and /bin/netstat. (In other words, the signal caused the last /bin/netstat entry.) For testing mcron, you can compile and use the send_sigqueue sample program.
./mcron b.conf
# wait 7 seconds
After 7 seconds have elapsed, send realtime signal #35 with a value of 2 and check that mcron.log has four entries: /bin/ls, /bin/netstat, /bin/ls, and error. The error entry is simply a line that starts with a timestamp and then contains the word error or Error somewhere in the rest of the line. The error is due to the fact that b.conf only has two jobs (which correspond to job IDs 0 and 1); there is no job with an ID of 2.