/* Copyright 1988,1990,1993,1994 by Paul Vixie
* All rights reserved
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/cdefs.h>
#if !defined(lint) && !defined(LINT)
#if 0
static char rcsid[] = "Id: cron.c,v 1.12 2004/01/23 18:56:42 vixie Exp";
#else
__RCSID("$NetBSD: cron.c,v 1.11 2020/04/18 19:32:19 christos Exp $");
#endif
#endif
/* if there are no debug flags turned on, fork as a daemon should.
*/
if (DebugFlags) {
#if DEBUGGING
(void)fprintf(stderr, "[%ld] cron started\n", (long)getpid());
#endif
} else if (NoFork == 0) {
if (daemon(1, 0)) {
log_it("CRON",getpid(),"DEATH","can't fork");
exit(1);
}
}
/*
* Too many clocks, not enough time (Al. Einstein)
* These clocks are in minutes since the epoch, adjusted for timezone.
* virtualTime: is the time it *would* be if we woke up
* promptly and nobody ever changed the clock. It is
* monotonically increasing... unless a timejump happens.
* At the top of the loop, all jobs for 'virtualTime' have run.
* timeRunning: is the time we last awakened.
* clockTime: is the time when set_time was last called.
*/
for (;;) {
time_t timeDiff;
enum timejump wakeupKind;
/* ... wait for the time (in minutes) to change ... */
do {
cron_sleep(timeRunning + 1);
set_time(FALSE);
} while (clockTime == timeRunning);
timeRunning = clockTime;
/*
* Calculate how the current time differs from our virtual
* clock. Classify the change into one of 4 cases.
*/
timeDiff = timeRunning - virtualTime;
/* shortcut for the most common case */
if (timeDiff == 1) {
virtualTime = timeRunning;
find_jobs(virtualTime, &database, TRUE, TRUE);
} else {
if (timeDiff > (3*MINUTE_COUNT) ||
timeDiff < -(3*MINUTE_COUNT))
wakeupKind = large;
else if (timeDiff > 5)
wakeupKind = medium;
else if (timeDiff > 0)
wakeupKind = small;
else
wakeupKind = negative;
switch (wakeupKind) {
case small:
/*
* case 1: timeDiff is a small positive number
* (wokeup late) run jobs for each virtual
* minute until caught up.
*/
Debug(DSCH, ("[%jd], normal case %jd minutes to go\n",
(intmax_t)getpid(), (intmax_t)timeDiff));
do {
if (job_runqueue())
(void)sleep(10);
virtualTime++;
find_jobs(virtualTime, &database,
TRUE, TRUE);
} while (virtualTime < timeRunning);
break;
case medium:
/*
* case 2: timeDiff is a medium-sized positive
* number, for example because we went to DST
* run wildcard jobs once, then run any
* fixed-time jobs that would otherwise be
* skipped if we use up our minute (possible,
* if there are a lot of jobs to run) go
* around the loop again so that wildcard jobs
* have a chance to run, and we do our
* housekeeping.
*/
Debug(DSCH, ("[%jd], DST begins %jd minutes to go\n",
(intmax_t)getpid(), (intmax_t)timeDiff));
/* run wildcard jobs for current minute */
find_jobs(timeRunning, &database, TRUE, FALSE);
/* run fixed-time jobs for each minute missed */
do {
if (job_runqueue())
(void)sleep(10);
virtualTime++;
find_jobs(virtualTime, &database,
FALSE, TRUE);
set_time(FALSE);
} while (virtualTime < timeRunning &&
clockTime == timeRunning);
break;
case negative:
/*
* case 3: timeDiff is a small or medium-sized
* negative num, eg. because of DST ending.
* Just run the wildcard jobs. The fixed-time
* jobs probably have already run, and should
* not be repeated. Virtual time does not
* change until we are caught up.
*/
Debug(DSCH, ("[%jd], DST ends %jd minutes to go\n",
(intmax_t)getpid(), (intmax_t)timeDiff));
find_jobs(timeRunning, &database, TRUE, FALSE);
break;
default:
/*
* other: time has changed a *lot*,
* jump virtual time, and run everything
*/
Debug(DSCH, ("[%ld], clock jumped\n",
(long)getpid()));
virtualTime = timeRunning;
find_jobs(timeRunning, &database, TRUE, TRUE);
}
}
/* Jobs to be run (if any) are loaded; clear the queue. */
(void)job_runqueue();
/* Check to see if we received a signal while running jobs. */
if (got_sighup) {
got_sighup = 0;
log_close();
}
if (got_sigchld) {
got_sigchld = 0;
sigchld_reaper();
}
load_database(&database);
}
}
static void
run_reboot_jobs(cron_db *db) {
user *u;
entry *e;
for (u = db->head; u != NULL; u = u->next) {
for (e = u->crontab; e != NULL; e = e->next) {
if (e->flags & WHEN_REBOOT)
job_add(e, u, StartTime);
}
}
(void) job_runqueue();
}
/* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
* first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
* on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
* is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre.
* like many bizarre things, it's the standard.
*/
for (u = db->head; u != NULL; u = u->next) {
for (e = u->crontab; e != NULL; e = e->next) {
#ifndef CRON_LOCALTIME
job_tz = env_get("CRON_TZ", e->envp);
maketime(job_tz, orig_tz);
#else
#define job_tz "N/A"
#define orig_tz "N/A"
#endif
Debug(DSCH|DEXT, ("user [%s:%ld:%ld:...] "
"[jobtz=%s, origtz=%s] "
"tick(%s), cmd=\"%s\"\n",
e->pwd->pw_name, (long)e->pwd->pw_uid,
(long)e->pwd->pw_gid, job_tz, orig_tz,
tick(e), e->cmd));
if (bit_test(e->minute, minute) &&
bit_test(e->hour, hour) &&
bit_test(e->month, month) &&
( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
: (bit_test(e->dow,dow) || bit_test(e->dom,dom))
)
) {
if ((doNonWild &&
!(e->flags & (MIN_STAR|HR_STAR))) ||
(doWild && (e->flags & (MIN_STAR|HR_STAR))))
job_add(e, u, StartTime);
}
}
}
#ifndef CRON_LOCALTIME
if (orig_tz != NULL)
setenv("TZ", orig_tz, 1);
else
unsetenv("TZ");
#endif
}
/*
* Set StartTime and clockTime to the current time.
* These are used for computing what time it really is right now.
* Note that clockTime is a unix wallclock time converted to minutes.
*/
static void
set_time(int initialize) {
#ifdef CRON_LOCALTIME
struct tm tm;
static int isdst;
StartTime = time(NULL);
/* We adjust the time to GMT so we can catch DST changes. */
tm = *localtime(&StartTime);
if (initialize || tm.tm_isdst != isdst) {
isdst = tm.tm_isdst;
GMToff = get_gmtoff(&StartTime, &tm);
Debug(DSCH, ("[%ld] GMToff=%ld\n",
(long)getpid(), (long)GMToff));
}
#else
StartTime = time(NULL);
#endif
clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE;
}
/*
* Try to just hit the next minute.
*/
static void
cron_sleep(time_t target) {
time_t t1, t2;
int seconds_to_wait;
/*
* Check to see if we were interrupted by a signal.
* If so, service the signal(s) then continue sleeping
* where we left off.
*/
if (got_sighup) {
got_sighup = 0;
log_close();
}
if (got_sigchld) {
got_sigchld = 0;
sigchld_reaper();
}
t2 = time(NULL) + GMToff;
seconds_to_wait -= (int)(t2 - t1);
t1 = t2;
}
}