Date: 17 Apr 1997 13:33:15 -0000 From: thompson@squirrel.tgsoft.com To: FreeBSD-gnats-submit@freebsd.org Subject: bin/3314: /etc/daily did not run on April 6, 1997 Message-ID: <19970417133315.7323.qmail@squirrel.tgsoft.com> Resent-Message-ID: <199704171340.GAA01935@freefall.freebsd.org>
next in thread | raw e-mail | index | archive | help
>Number: 3314 >Category: bin >Synopsis: /etc/daily did not run on April 6, 1997 >Confidential: no >Severity: non-critical >Priority: low >Responsible: freebsd-bugs >State: open >Class: sw-bug >Submitter-Id: current-users >Arrival-Date: Thu Apr 17 06:40:01 PDT 1997 >Last-Modified: >Originator: mark thompson >Organization: tgsoft >Release: FreeBSD 2.2.1-RELEASE i386 >Environment: Standard 2.2.1, and many BSDs before it >Description: cron is simply not equipped to handle time changes caused by the arrival and departure of daylight savings time. Since this is a common event around the world, it seems that it should be. >How-To-Repeat: Submit a cron job that executes a few minutes hence, then adjust your system clock to an hour from now. >Fix: Simple patch enclosed diff -c -r -b ./cron/cron.8 cron/NEW/cron/cron.8 *** ./cron/cron.8 Sun Jun 30 15:11:50 1996 --- cron/NEW/cron/cron.8 Thu Apr 17 06:17:38 1997 *************** *** 13,18 **** --- 13,21 ---- .\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and .\" * I'll try to keep a version up to date. I can be reached as follows: .\" * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + .\" + .\" * new timekeeping (handle daylight savings time) by + .\" * mark thompson <thompson@tgsoft.com> ... so don't blame Paul .\" */ .\" .\" $Id: cron.8,v 1.2 1996/06/30 22:11:50 wosch Exp $ *************** *** 54,59 **** --- 57,77 ---- .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 -c -r -b ./cron/cron.c cron/NEW/cron/cron.c *** ./cron/cron.c Sun Jun 30 15:11:51 1996 --- cron/NEW/cron/cron.c Thu Apr 17 06:05:25 1997 *************** *** 13,18 **** --- 13,21 ---- * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + * + * New timekeeping (main after first load_database, last if in find_jobs, + * set_time and cron_sleep) by mark thompson <thompson@tgsoft.com> */ #if !defined(lint) && !defined(LINT) *************** *** 34,42 **** static void usage __P((void)), run_reboot_jobs __P((cron_db *)), ! cron_tick __P((cron_db *)), ! cron_sync __P((void)), ! cron_sleep __P((void)), #ifdef USE_SIGCHLD sigchld_handler __P((int)), #endif --- 37,45 ---- static void usage __P((void)), run_reboot_jobs __P((cron_db *)), ! 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 *************** *** 93,104 **** /* if there are no debug flags turned on, fork as a daemon should. */ ! # if DEBUGGING if (DebugFlags) { ! # else ! if (0) { ! # endif (void) fprintf(stderr, "[%d] cron started\n", getpid()); } else { switch (fork()) { case -1: --- 96,106 ---- /* if there are no debug flags turned on, fork as a daemon should. */ ! if (DebugFlags) { ! #if DEBUGGING (void) fprintf(stderr, "[%d] cron started\n", getpid()); + #endif } else { switch (fork()) { case -1: *************** *** 121,143 **** database.tail = NULL; database.mtime = (time_t) 0; load_database(&database); run_reboot_jobs(&database); ! cron_sync(); while (TRUE) { ! # if DEBUGGING ! /* if (!(DebugFlags & DTEST)) */ ! # endif /*DEBUGGING*/ ! cron_sleep(); load_database(&database); ! /* do this iteration ! */ ! cron_tick(&database); ! /* sleep 1 minute ! */ ! TargetTime += 60; } } --- 123,232 ---- database.tail = NULL; database.mtime = (time_t) 0; load_database(&database); + + set_time(); run_reboot_jobs(&database); ! 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) { ! time_min timeDiff; ! int wakeupKind; load_database(&database); ! /* ... 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 */ ! 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: timeDiff is a small positive number (wokeup late) */ ! /* run jobs for each virtual minute until caught up. */ ! case 1: ! 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: 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 */ ! case 2: ! 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 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 */ ! case 0: ! Debug(DSCH, ("[%d], DST ends %d minutes to go\n", ! getpid(), virtualTime - timeRunning)) ! find_jobs(timeRunning, &database, TRUE, FALSE); ! break; ! ! /* other: time has changed a *lot* */ ! /* jump virtual time, and run everything */ ! default: ! 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(); } } *************** *** 161,170 **** static void ! cron_tick(db) cron_db *db; { ! register struct tm *tm = localtime(&TargetTime); register int minute, hour, dom, month, dow; register user *u; register entry *e; --- 250,263 ---- static void ! find_jobs(vtime, db, doWild, doNonWild) ! time_min vtime; cron_db *db; + int doWild; + int doNonWild; { ! 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; *************** *** 177,184 **** 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)) /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* --- 270,278 ---- 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) %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* *************** *** 199,204 **** --- 293,300 ---- : (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); } } *************** *** 206,255 **** } ! /* 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.. */ static void ! cron_sync() { ! register struct tm *tm; ! ! TargetTime = time((time_t*)0); ! tm = localtime(&TargetTime); ! TargetTime += (60 - tm->tm_sec); } ! static void ! cron_sleep() { register int seconds_to_wait; ! 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()); ! ! 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); ! } } --- 302,334 ---- } ! /* ! * 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() { ! StartTime = time((time_t *)0); ! clockTime = StartTime / (unsigned long)SECONDS_PER_MINUTE; } ! /* ! * try to just hit the next minute ! */ static void ! cron_sleep(target) ! time_min target; ! { register int seconds_to_wait; + register struct tm *tm; ! 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)) ! if (seconds_to_wait > 0 && seconds_to_wait < 65) ! sleep((unsigned int) seconds_to_wait); } diff -c -r -b ./cron/cron.h cron/NEW/cron/cron.h *** ./cron/cron.h Sun Aug 4 17:31:24 1996 --- cron/NEW/cron/cron.h Wed Apr 16 06:28:15 1997 *************** *** 120,125 **** --- 120,129 ---- LineNumber = ln; \ } + typedef long time_min; + + #define SECONDS_PER_MINUTE 60 + #define FIRST_MINUTE 0 #define LAST_MINUTE 59 #define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1) *************** *** 162,167 **** --- 166,173 ---- #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 *************** *** 256,262 **** char *ProgramName; int LineNumber; ! time_t TargetTime; # if DEBUGGING int DebugFlags; --- 262,271 ---- char *ProgramName; int LineNumber; ! time_t StartTime; ! time_min timeRunning; ! time_min virtualTime; ! time_min clockTime; # if DEBUGGING int DebugFlags; *************** *** 264,269 **** --- 273,280 ---- "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[], *************** *** 271,277 **** *DowNames[], *ProgramName; extern int LineNumber; ! extern time_t TargetTime; # if DEBUGGING extern int DebugFlags; extern char *DebugFlagNames[]; --- 282,291 ---- *DowNames[], *ProgramName; extern int LineNumber; ! 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 -c -r -b ./cron/database.c cron/NEW/cron/database.c *** ./cron/database.c Mon Sep 9 20:38:20 1996 --- cron/NEW/cron/database.c Wed Apr 16 08:33:51 1997 *************** *** 13,18 **** --- 13,22 ---- * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + * + * stored database time is hash of time of SPOOL_DIR and of /etc/crontab + * so that clock fluctuations will be correctly detected + * mark thompson <thompson@tgsoft.com> */ #if !defined(lint) && !defined(LINT) *************** *** 30,36 **** #define TMAX(a,b) ((a)>(b)?(a):(b)) ! static void process_crontab __P((char *, char *, char *, struct stat *, --- 34,40 ---- #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,77 **** * 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)) { Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n", getpid())) return; --- 75,81 ---- * so is guaranteed to be different than the stat() mtime the first * time this function is called. */ ! 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,88 **** * 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.head = new_db.tail = NULL; if (syscron_stat.st_mtime) { --- 86,92 ---- * actually changed. Whatever is left in the old database when * we're done is chaff -- crontabs that disappeared. */ ! new_db.mtime = HASH(statbuf.st_mtime, syscron_stat.st_mtime); new_db.head = new_db.tail = NULL; if (syscron_stat.st_mtime) { diff -c -r -b ./cron/do_command.c cron/NEW/cron/do_command.c *** ./cron/do_command.c Sun Sep 10 06:02:56 1995 --- cron/NEW/cron/do_command.c Wed Apr 16 05:25:23 1997 *************** *** 388,394 **** e->cmd); # if defined(MAIL_DATE) fprintf(mail, "Date: %s\n", ! arpadate(&TargetTime)); # endif /* MAIL_DATE */ for (env = e->envp; *env; env++) fprintf(mail, "X-Cron-Env: <%s>\n", --- 388,394 ---- e->cmd); # if defined(MAIL_DATE) fprintf(mail, "Date: %s\n", ! arpadate(&StartTime)); # endif /* MAIL_DATE */ for (env = e->envp; *env; env++) fprintf(mail, "X-Cron-Env: <%s>\n", diff -c -r -b ./lib/entry.c cron/NEW/lib/entry.c *** ./lib/entry.c Mon Aug 28 14:30:46 1995 --- cron/NEW/lib/entry.c Wed Apr 16 08:30:14 1997 *************** *** 19,25 **** static char rcsid[] = "$Id: entry.c,v 1.4 1995/08/28 21:30:46 mpp Exp $"; #endif ! /* vix 26jan87 [RCS'd; rest of log is in RCS file] * vix 01jan87 [added line-level error recovery] * vix 31dec86 [added /step to the from-to range, per bob@acornrc] * vix 30dec86 [written] --- 19,27 ---- static char rcsid[] = "$Id: entry.c,v 1.4 1995/08/28 21:30:46 mpp Exp $"; #endif ! /* ! * -mt 15Apr97 add HR_STAR and MIN_STAR [<thompson@tgsoft.com>] ! * vix 26jan87 [RCS'd; rest of log is in RCS file] * vix 01jan87 [added line-level error recovery] * vix 31dec86 [added /step to the from-to range, per bob@acornrc] * vix 30dec86 [written] *************** *** 153,158 **** --- 155,161 ---- 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; *************** *** 160,165 **** --- 163,170 ---- } 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) { *************** *** 170,175 **** --- 175,182 ---- /* hours */ + if (ch == '*') + e->flags |= HR_STAR; ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, PPC_NULL, ch, file); if (ch == EOF) { >Audit-Trail: >Unformatted:
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?19970417133315.7323.qmail>