Skip site navigation (1)Skip section navigation (2)
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>