summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheo de Raadt <deraadt@cvs.openbsd.org>1997-12-22 08:10:44 +0000
committerTheo de Raadt <deraadt@cvs.openbsd.org>1997-12-22 08:10:44 +0000
commit8fd3f7479252956ea0e14cfe8da7753a8d637254 (patch)
tree131d56d7a5c8a408d6bc0f0f61a0d67fcfbf9d62
parent63e26201a500f8d88680876924801dceece97a26 (diff)
handle timing normally except when clock jumps between 1 and 3 hours. If it
jumps, attempt as best as possible to gaurantee that jobs DO run, but only run ONCE; patch by thompson@.tgsoft.com
-rw-r--r--usr.sbin/cron/cron.817
-rw-r--r--usr.sbin/cron/cron.c226
-rw-r--r--usr.sbin/cron/cron.h20
-rw-r--r--usr.sbin/cron/database.c8
-rw-r--r--usr.sbin/cron/do_command.c4
-rw-r--r--usr.sbin/cron/entry.c11
6 files changed, 204 insertions, 82 deletions
diff --git a/usr.sbin/cron/cron.8 b/usr.sbin/cron/cron.8
index 8270b457d9d..a76c2efa313 100644
--- a/usr.sbin/cron/cron.8
+++ b/usr.sbin/cron/cron.8
@@ -15,7 +15,7 @@
.\" * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
.\" */
.\"
-.\" $Id: cron.8,v 1.1 1995/10/18 08:47:30 deraadt Exp $
+.\" $Id: cron.8,v 1.2 1997/12/22 08:10:40 deraadt Exp $
.\"
.TH CRON 8 "20 December 1993"
.UC 4
@@ -54,6 +54,21 @@ need not be restarted whenever a crontab file is modified. Note that the
.IR Crontab (1)
command updates the modtime of the spool directory whenever it changes a
crontab.
+.PP
+Special considerations exist when the clock is changed by less than 3
+hours, for example at the beginning and end of daylight savings
+time. If the time has moved forwards, those jobs which would have
+run in the time that was skipped will be run soon after the change.
+Conversely, if the time has moved backwards by less than 3 hours,
+those jobs that fall into the repeated time will not be run.
+.PP
+Only jobs that run at a particular time (not specified as
+@hourly, nor with '*' in the hour or minute specifier) are
+affected. Jobs which are specified with wildcards are run based on the
+new time immediately.
+.PP
+Clock changes of more than 3 hours are considered to be corrections to
+the clock, and the new time is used immediately.
.SH "SEE ALSO"
crontab(1), crontab(5)
.SH AUTHOR
diff --git a/usr.sbin/cron/cron.c b/usr.sbin/cron/cron.c
index 30e0c4f0909..f2243a2316c 100644
--- a/usr.sbin/cron/cron.c
+++ b/usr.sbin/cron/cron.c
@@ -16,7 +16,7 @@
*/
#if !defined(lint) && !defined(LINT)
-static char rcsid[] = "$Id: cron.c,v 1.3 1997/08/04 19:26:08 deraadt Exp $";
+static char rcsid[] = "$Id: cron.c,v 1.4 1997/12/22 08:10:41 deraadt Exp $";
#endif
@@ -34,9 +34,9 @@ static char rcsid[] = "$Id: cron.c,v 1.3 1997/08/04 19:26:08 deraadt Exp $";
static void usage __P((void)),
run_reboot_jobs __P((cron_db *)),
- cron_tick __P((cron_db *)),
- cron_sync __P((void)),
- cron_sleep __P((void)),
+ find_jobs __P((time_min, cron_db *, int, int)),
+ set_time __P((void)),
+ cron_sleep __P((time_min)),
#ifdef USE_SIGCHLD
sigchld_handler __P((int)),
#endif
@@ -89,12 +89,11 @@ main(argc, argv)
/* if there are no debug flags turned on, fork as a daemon should.
*/
-# if DEBUGGING
+
if (DebugFlags) {
-# else
- if (0) {
-# endif
+#if DEBUGGING
(void) fprintf(stderr, "[%d] cron started\n", getpid());
+#endif
} else {
switch (fork()) {
case -1:
@@ -117,23 +116,123 @@ main(argc, argv)
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);
+
+ set_time();
run_reboot_jobs(&database);
- cron_sync();
+ timeRunning = virtualTime = clockTime;
+
+ /*
+ * too many clocks, not enough time (Al. Einstein)
+ * These clocks are in minutes since the epoch (time()/60).
+ * 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.
+ */
while (TRUE) {
-# if DEBUGGING
- if (!(DebugFlags & DTEST))
-# endif /*DEBUGGING*/
- cron_sleep();
+ time_min timeDiff;
+ int wakeupKind;
load_database(&database);
- /* do this iteration
+ /* ... wait for the time (in minutes) to change ... */
+ do {
+ cron_sleep(timeRunning + 1);
+ set_time();
+ } while (clockTime == timeRunning);
+ timeRunning = clockTime;
+
+ /*
+ * ... calculate how the current time differs from
+ * our virtual clock. Classify the change into one
+ * of 4 cases
*/
- cron_tick(&database);
-
- /* sleep 1 minute
- */
- TargetTime += 60;
+ timeDiff = timeRunning - virtualTime;
+
+ /* shortcut for the most common case */
+ if (timeDiff == 1) {
+ virtualTime = timeRunning;
+ find_jobs(virtualTime, &database, TRUE, TRUE);
+ } else {
+ wakeupKind = -1;
+ if (timeDiff > -(3*MINUTE_COUNT))
+ wakeupKind = 0;
+ if (timeDiff > 0)
+ wakeupKind = 1;
+ if (timeDiff > 5)
+ wakeupKind = 2;
+ if (timeDiff > (3*MINUTE_COUNT))
+ wakeupKind = 3;
+
+ switch (wakeupKind) {
+ case 1:
+ /*
+ * case 1: timeDiff is a small positive number
+ * (wokeup late) run jobs for each virtual minute
+ * until caught up.
+ */
+ Debug(DSCH, ("[%d], normal case %d minutes to go\n",
+ getpid(), timeRunning - virtualTime))
+ do {
+ if (job_runqueue())
+ sleep(10);
+ virtualTime++;
+ find_jobs(virtualTime, &database, TRUE, TRUE);
+ } while (virtualTime< timeRunning);
+ break;
+
+ case 2:
+ /*
+ * 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, ("[%d], DST begins %d minutes to go\n",
+ getpid(), timeRunning - virtualTime))
+ /* run wildcard jobs for current minute */
+ find_jobs(timeRunning, &database, TRUE, FALSE);
+
+ /* run fixed-time jobs for each minute missed */
+ do {
+ if (job_runqueue())
+ sleep(10);
+ virtualTime++;
+ find_jobs(virtualTime, &database, FALSE, TRUE);
+ set_time();
+ } while (virtualTime< timeRunning &&
+ clockTime == timeRunning);
+ break;
+
+ case 0:
+ /*
+ * 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, ("[%d], DST ends %d minutes to go\n",
+ getpid(), virtualTime - timeRunning))
+ find_jobs(timeRunning, &database, TRUE, FALSE);
+ break;
+ default:
+ /*
+ * other: time has changed a *lot*,
+ * jump virtual time, and run everything
+ */
+ Debug(DSCH, ("[%d], clock jumped\n", getpid()))
+ virtualTime = timeRunning;
+ find_jobs(timeRunning, &database, TRUE, TRUE);
+ }
+ }
+ /* jobs to be run (if any) are loaded. clear the queue */
+ job_runqueue();
}
}
@@ -157,10 +256,14 @@ run_reboot_jobs(db)
static void
-cron_tick(db)
+find_jobs(vtime, db, doWild, doNonWild)
+ time_min vtime;
cron_db *db;
+ int doWild;
+ int doNonWild;
{
- register struct tm *tm = localtime(&TargetTime);
+ time_t virtualSecond = vtime * SECONDS_PER_MINUTE;
+ register struct tm *tm = localtime(&virtualSecond);
register int minute, hour, dom, month, dow;
register user *u;
register entry *e;
@@ -173,8 +276,9 @@ cron_tick(db)
month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
dow = tm->tm_wday -FIRST_DOW;
- Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n",
- getpid(), minute, hour, dom, month, dow))
+ Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d) %s %s\n",
+ getpid(), minute, hour, dom, month, dow,
+ doWild?" ":"No wildcard",doNonWild?" ":"Wildcard only"))
/* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
* first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
@@ -185,67 +289,51 @@ cron_tick(db)
for (u = db->head; u != NULL; u = u->next) {
for (e = u->crontab; e != NULL; e = e->next) {
Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n",
- env_get("LOGNAME", e->envp),
- e->uid, e->gid, 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))
+ env_get("LOGNAME", e->envp),
+ e->uid, e->gid, 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))
- )
- ) {
- job_add(e, u);
+ : (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);
}
}
}
}
-/* the task here is to figure out how long it's going to be until :00 of the
- * following minute and initialize TargetTime to this value. TargetTime
- * will subsequently slide 60 seconds at a time, with correction applied
- * implicitly in cron_sleep(). it would be nice to let cron execute in
- * the "current minute" before going to sleep, but by restarting cron you
- * could then get it to execute a given minute's jobs more than once.
- * instead we have the chance of missing a minute's jobs completely, but
- * that's something sysadmin's know to expect what with crashing computers..
+/*
+ * 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
-cron_sync() {
- register struct tm *tm;
-
- TargetTime = time((time_t*)0);
- tm = localtime(&TargetTime);
- TargetTime += (60 - tm->tm_sec);
+set_time()
+{
+ StartTime = time((time_t *)0);
+ clockTime = StartTime / (unsigned long)SECONDS_PER_MINUTE;
}
-
+/*
+ * try to just hit the next minute
+ */
static void
-cron_sleep() {
+cron_sleep(target)
+ time_min target;
+{
register int seconds_to_wait;
+ register struct tm *tm;
- do {
- seconds_to_wait = (int) (TargetTime - time((time_t*)0));
- Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
- getpid(), TargetTime, seconds_to_wait))
-
- /* if we intend to sleep, this means that it's finally
- * time to empty the job queue (execute it).
- *
- * if we run any jobs, we'll probably screw up our timing,
- * so go recompute.
- *
- * note that we depend here on the left-to-right nature
- * of &&, and the short-circuiting.
- */
- } while (seconds_to_wait > 0 && job_runqueue());
+ seconds_to_wait = (int)(target*SECONDS_PER_MINUTE - time((time_t*)0)) + 1;
+ Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
+ getpid(), target*SECONDS_PER_MINUTE, seconds_to_wait))
- while (seconds_to_wait > 0) {
- Debug(DSCH, ("[%d] sleeping for %d seconds\n",
- getpid(), seconds_to_wait))
- seconds_to_wait = (int) sleep((unsigned int) seconds_to_wait);
- }
+ if (seconds_to_wait > 0 && seconds_to_wait< 65)
+ sleep((unsigned int) seconds_to_wait);
}
diff --git a/usr.sbin/cron/cron.h b/usr.sbin/cron/cron.h
index 60cfada5899..ebc772ccd49 100644
--- a/usr.sbin/cron/cron.h
+++ b/usr.sbin/cron/cron.h
@@ -17,7 +17,7 @@
/* cron.h - header for vixie's cron
*
- * $Id: cron.h,v 1.1 1995/10/18 08:47:30 deraadt Exp $
+ * $Id: cron.h,v 1.2 1997/12/22 08:10:41 deraadt Exp $
*
* vix 14nov88 [rest of log is in RCS]
* vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley]
@@ -118,6 +118,10 @@
LineNumber = ln; \
}
+typedef int time_min;
+
+#define SECONDS_PER_MINUTE 60
+
#define FIRST_MINUTE 0
#define LAST_MINUTE 59
#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1)
@@ -160,6 +164,8 @@ typedef struct _entry {
#define DOM_STAR 0x01
#define DOW_STAR 0x02
#define WHEN_REBOOT 0x04
+#define MIN_STAR 0x08
+#define HR_STAR 0x10
} entry;
/* the crontab database will be a list of the
@@ -254,7 +260,10 @@ char *DowNames[] = {
char *ProgramName;
int LineNumber;
-time_t TargetTime;
+time_t StartTime;
+time_min timeRunning;
+time_min virtualTime;
+time_min clockTime;
# if DEBUGGING
int DebugFlags;
@@ -262,6 +271,8 @@ char *DebugFlagNames[] = { /* sync with #defines */
"ext", "sch", "proc", "pars", "load", "misc", "test", "bit",
NULL /* NULL must be last element */
};
+# else
+#define DebugFlags 0
# endif /* DEBUGGING */
#else /*MAIN_PROGRAM*/
extern char *copyright[],
@@ -269,7 +280,10 @@ extern char *copyright[],
*DowNames[],
*ProgramName;
extern int LineNumber;
-extern time_t TargetTime;
+extern time_t StartTime;
+extern time_min timeRunning;
+extern time_min virtualTime;
+extern time_min clockTime;
# if DEBUGGING
extern int DebugFlags;
extern char *DebugFlagNames[];
diff --git a/usr.sbin/cron/database.c b/usr.sbin/cron/database.c
index 6831c744413..92d4a4960b3 100644
--- a/usr.sbin/cron/database.c
+++ b/usr.sbin/cron/database.c
@@ -16,7 +16,7 @@
*/
#if !defined(lint) && !defined(LINT)
-static char rcsid[] = "$Id: database.c,v 1.2 1996/09/15 09:13:18 deraadt Exp $";
+static char rcsid[] = "$Id: database.c,v 1.3 1997/12/22 08:10:42 deraadt Exp $";
#endif
/* vix 26jan87 [RCS has the log]
@@ -30,7 +30,7 @@ static char rcsid[] = "$Id: database.c,v 1.2 1996/09/15 09:13:18 deraadt Exp $";
#define TMAX(a,b) ((a)>(b)?(a):(b))
-
+#define HASH(a,b) ((a)+(b))
static void process_crontab __P((char *, char *, char *,
struct stat *,
@@ -71,7 +71,7 @@ load_database(old_db)
* so is guaranteed to be different than the stat() mtime the first
* time this function is called.
*/
- if (old_db->mtime == TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) {
+ if (old_db->mtime == HASH(statbuf.st_mtime, syscron_stat.st_mtime)) {
Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n",
getpid()))
return;
@@ -82,7 +82,7 @@ load_database(old_db)
* actually changed. Whatever is left in the old database when
* we're done is chaff -- crontabs that disappeared.
*/
- new_db.mtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime);
+ new_db.mtime = HASH(statbuf.st_mtime, syscron_stat.st_mtime);
new_db.head = new_db.tail = NULL;
if (syscron_stat.st_mtime) {
diff --git a/usr.sbin/cron/do_command.c b/usr.sbin/cron/do_command.c
index 3e4dbf1f05e..32c12478178 100644
--- a/usr.sbin/cron/do_command.c
+++ b/usr.sbin/cron/do_command.c
@@ -16,7 +16,7 @@
*/
#if !defined(lint) && !defined(LINT)
-static char rcsid[] = "$Id: do_command.c,v 1.2 1996/08/07 06:18:33 deraadt Exp $";
+static char rcsid[] = "$Id: do_command.c,v 1.3 1997/12/22 08:10:42 deraadt Exp $";
#endif
@@ -380,7 +380,7 @@ child_process(e, u)
e->cmd);
# if defined(MAIL_DATE)
fprintf(mail, "Date: %s\n",
- arpadate(&TargetTime));
+ arpadate(&StartTime));
# endif /* MAIL_DATE */
for (env = e->envp; *env; env++)
fprintf(mail, "X-Cron-Env: <%s>\n",
diff --git a/usr.sbin/cron/entry.c b/usr.sbin/cron/entry.c
index 2c7fd9f90f5..4b39d2650a6 100644
--- a/usr.sbin/cron/entry.c
+++ b/usr.sbin/cron/entry.c
@@ -16,7 +16,7 @@
*/
#if !defined(lint) && !defined(LINT)
-static char rcsid[] = "$Id: entry.c,v 1.4 1996/12/16 18:40:53 deraadt Exp $";
+static char rcsid[] = "$Id: entry.c,v 1.5 1997/12/22 08:10:43 deraadt Exp $";
#endif
/* vix 26jan87 [RCS'd; rest of log is in RCS file]
@@ -154,6 +154,7 @@ load_entry(file, error_func, pw, envp)
bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+ e->flags |= HR_STAR;
} else {
ecode = e_timespec;
goto eof;
@@ -161,6 +162,8 @@ load_entry(file, error_func, pw, envp)
} else {
Debug(DPARS, ("load_entry()...about to parse numerics\n"))
+ if (ch == '*')
+ e->flags |= MIN_STAR;
ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
PPC_NULL, ch, file);
if (ch == EOF) {
@@ -171,6 +174,8 @@ load_entry(file, error_func, pw, envp)
/* hours
*/
+ if (ch == '*')
+ e->flags |= HR_STAR;
ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
PPC_NULL, ch, file);
if (ch == EOF) {
@@ -241,8 +246,8 @@ load_entry(file, error_func, pw, envp)
}
Debug(DPARS, ("load_entry()...uid %d, gid %d\n",e->uid,e->gid))
} else if (ch == '*') {
- ecode = e_cmd;
- goto eof;
+ ecode = e_cmd;
+ goto eof;
}
e->uid = pw->pw_uid;