diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 1997-12-22 08:10:44 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 1997-12-22 08:10:44 +0000 |
commit | 8fd3f7479252956ea0e14cfe8da7753a8d637254 (patch) | |
tree | 131d56d7a5c8a408d6bc0f0f61a0d67fcfbf9d62 /usr.sbin | |
parent | 63e26201a500f8d88680876924801dceece97a26 (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
Diffstat (limited to 'usr.sbin')
-rw-r--r-- | usr.sbin/cron/cron.8 | 17 | ||||
-rw-r--r-- | usr.sbin/cron/cron.c | 226 | ||||
-rw-r--r-- | usr.sbin/cron/cron.h | 20 | ||||
-rw-r--r-- | usr.sbin/cron/database.c | 8 | ||||
-rw-r--r-- | usr.sbin/cron/do_command.c | 4 | ||||
-rw-r--r-- | usr.sbin/cron/entry.c | 11 |
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; |