crond: support @daily etc

function                                             old     new   delta
start_jobs                                             -     348    +348
load_crontab                                         766     936    +170
static.SpecAry                                         -      96     +96
crond_main                                          1424    1134    -290
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 1/1 up/down: 614/-290)          Total: 324 bytes

Based on patch by Jonathan Kolb <kolbyjack@gmail.com>

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2017-07-08 20:53:11 +02:00
parent 1572f520cc
commit 75fbea3879

View File

@ -35,6 +35,22 @@
//config: help
//config: Command output will be sent to corresponding user via email.
//config:
//config:config FEATURE_CROND_SPECIAL_TIMES
//config: bool "Support special times (@reboot, @daily, etc) in crontabs"
//config: default y
//config: depends on CROND
//config: help
//config: string meaning
//config: ------ -------
//config: @reboot Run once, at startup
//config: @yearly Run once a year: "0 0 1 1 *"
//config: @annually Same as @yearly: "0 0 1 1 *"
//config: @monthly Run once a month: "0 0 1 * *"
//config: @weekly Run once a week: "0 0 * * 0"
//config: @daily Run once a day: "0 0 * * *"
//config: @midnight Same as @daily: "0 0 * * *"
//config: @hourly Run once an hour: "0 * * * *"
//config:
//config:config FEATURE_CROND_DIR
//config: string "crond spool directory"
//config: default "/var/spool/cron"
@ -74,6 +90,7 @@
#define CRON_DIR CONFIG_FEATURE_CROND_DIR
#define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs"
#define CRON_REBOOT CONFIG_PID_FILE_PATH "/crond.reboot"
#ifndef SENDMAIL
# define SENDMAIL "sendmail"
#endif
@ -101,6 +118,8 @@ typedef struct CronLine {
struct CronLine *cl_next;
char *cl_cmd; /* shell command */
pid_t cl_pid; /* >0:running, <0:needs to be started in this minute, 0:dormant */
#define START_ME_REBOOT -2
#define START_ME_NORMAL -1
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */
char *cl_mailto; /* whom to mail results, may be NULL */
@ -452,6 +471,59 @@ static void load_crontab(const char *fileName)
shell = xstrdup(&tokens[0][6]);
continue;
}
#if ENABLE_FEATURE_CROND_SPECIAL_TIMES
if (tokens[0][0] == '@') {
/*
* "@daily /a/script/to/run PARAM1 PARAM2..."
*/
typedef struct SpecialEntry {
const char *name;
const char tokens[8];
} SpecialEntry;
static const SpecialEntry SpecAry[] = {
/* hour day month weekday */
{ "yearly", "0\0" "1\0" "1\0" "*" },
{ "annually", "0\0" "1\0" "1\0" "*" },
{ "monthly", "0\0" "1\0" "*\0" "*" },
{ "weekly", "0\0" "*\0" "*\0" "0" },
{ "daily", "0\0" "*\0" "*\0" "*" },
{ "midnight", "0\0" "*\0" "*\0" "*" },
{ "hourly", "*\0" "*\0" "*\0" "*" },
{ "reboot", "" },
};
const SpecialEntry *e = SpecAry;
if (n < 2)
continue;
for (;;) {
if (strcmp(e->name, tokens[0] + 1) == 0) {
/*
* tokens[1] is only the first word of command,
* find the entire command in unmodified string:
*/
tokens[5] = strstr(
skip_non_whitespace(skip_whitespace(parser->data)),
/* ^^^^ avoids mishandling e.g. "@daily aily PARAM" */
tokens[1]
);
if (e->tokens[0]) {
char *et = (char*)e->tokens;
/* minute is "0" for all specials */
tokens[0] = (char*)"0";
tokens[1] = et;
tokens[2] = et + 2;
tokens[3] = et + 4;
tokens[4] = et + 6;
}
goto got_it;
}
if (!e->tokens[0])
break;
e++;
}
continue; /* bad line (unrecognized '@foo') */
}
#endif
//TODO: handle HOME= too? "man crontab" says:
//name = value
//
@ -468,18 +540,30 @@ static void load_crontab(const char *fileName)
/* check if a minimum of tokens is specified */
if (n < 6)
continue;
IF_FEATURE_CROND_SPECIAL_TIMES(
got_it:
)
*pline = line = xzalloc(sizeof(*line));
/* parse date ranges */
ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]);
ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]);
ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]);
ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]);
ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]);
/*
* fix days and dow - if one is not "*" and the other
* is "*", the other is set to 0, and vise-versa
*/
FixDayDow(line);
#if ENABLE_FEATURE_CROND_SPECIAL_TIMES
if (tokens[0][0] == '@') { /* "@reboot" line */
file->cf_wants_starting = 1;
line->cl_pid = START_ME_REBOOT; /* wants to start */
/* line->cl_Mins/Hrs/etc stay zero: never match any time */
} else
#endif
{
/* parse date ranges */
ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]);
ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]);
ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]);
ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]);
ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]);
/*
* fix days and dow - if one is not "*" and the other
* is "*", the other is set to 0, and vise-versa
*/
FixDayDow(line);
}
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
/* copy mailto (can be NULL) */
line->cl_mailto = xstrdup(mailTo);
@ -664,7 +748,7 @@ fork_job(const char *user, int mailFd, CronLine *line, bool run_sendmail)
return pid;
}
static void start_one_job(const char *user, CronLine *line)
static pid_t start_one_job(const char *user, CronLine *line)
{
char mailFile[128];
int mailFd = -1;
@ -698,6 +782,8 @@ static void start_one_job(const char *user, CronLine *line)
free(mailFile2);
}
}
return line->cl_pid;
}
/*
@ -748,7 +834,7 @@ static void process_finished_job(const char *user, CronLine *line)
#else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
static void start_one_job(const char *user, CronLine *line)
static pid_t start_one_job(const char *user, CronLine *line)
{
const char *shell;
struct passwd *pas;
@ -782,6 +868,7 @@ static void start_one_job(const char *user, CronLine *line)
pid = 0;
}
line->cl_pid = pid;
return pid;
}
#define process_finished_job(user, line) ((line)->cl_pid = 0)
@ -825,7 +912,7 @@ static void flag_starting_jobs(time_t t1, time_t t2)
log8("user %s: process already running: %s",
file->cf_username, line->cl_cmd);
} else if (line->cl_pid == 0) {
line->cl_pid = -1;
line->cl_pid = START_ME_NORMAL;
file->cf_wants_starting = 1;
}
}
@ -834,7 +921,20 @@ static void flag_starting_jobs(time_t t1, time_t t2)
}
}
static void start_jobs(void)
#if ENABLE_FEATURE_CROND_SPECIAL_TIMES
static int touch_reboot_file(void)
{
int fd = open(CRON_REBOOT, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, 0000);
if (fd >= 0) {
close(fd);
return 1;
}
/* File (presumably) exists - this is not the first run after reboot */
return 0;
}
#endif
static void start_jobs(int wants_start)
{
CronFile *file;
CronLine *line;
@ -846,11 +946,10 @@ static void start_jobs(void)
file->cf_wants_starting = 0;
for (line = file->cf_lines; line; line = line->cl_next) {
pid_t pid;
if (line->cl_pid >= 0)
if (line->cl_pid != wants_start)
continue;
start_one_job(file->cf_username, line);
pid = line->cl_pid;
pid = start_one_job(file->cf_username, line);
log8("USER %s pid %3d cmd %s",
file->cf_username, (int)pid, line->cl_cmd);
if (pid < 0) {
@ -950,6 +1049,10 @@ int crond_main(int argc UNUSED_PARAM, char **argv)
log8("crond (busybox "BB_VER") started, log level %d", G.log_level);
rescan_crontab_dir();
write_pidfile(CONFIG_PID_FILE_PATH "/crond.pid");
#if ENABLE_FEATURE_CROND_SPECIAL_TIMES
if (touch_reboot_file())
start_jobs(START_ME_REBOOT); /* start @reboot entries, if any */
#endif
/* Main loop */
t2 = time(NULL);
@ -1002,7 +1105,7 @@ int crond_main(int argc UNUSED_PARAM, char **argv)
} else if (dt > 0) {
/* Usual case: time advances forward, as expected */
flag_starting_jobs(t1, t2);
start_jobs();
start_jobs(START_ME_NORMAL);
sleep_time = 60;
if (check_completions() > 0) {
/* some jobs are still running */