xref: /freebsd/usr.sbin/cron/cron/cron.c (revision 0957b409)
1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
2  * All rights reserved
3  *
4  * Distribute freely, except: don't remove my name from the source or
5  * documentation (don't take credit for my work), mark your changes (don't
6  * get me blamed for your possible bugs), don't alter or remove this
7  * notice.  May be sold if buildable source is provided to buyer.  No
8  * warrantee of any kind, express or implied, is included with this
9  * software; use at your own risk, responsibility for damages (if any) to
10  * anyone resulting from the use of this software rests entirely with the
11  * user.
12  *
13  * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
14  * I'll try to keep a version up to date.  I can be reached as follows:
15  * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
16  */
17 
18 #if !defined(lint) && !defined(LINT)
19 static const char rcsid[] =
20   "$FreeBSD$";
21 #endif
22 
23 #define	MAIN_PROGRAM
24 
25 
26 #include "cron.h"
27 #include <sys/mman.h>
28 #include <sys/signal.h>
29 #if SYS_TIME_H
30 # include <sys/time.h>
31 #else
32 # include <time.h>
33 #endif
34 
35 
36 static	void	usage(void),
37 		run_reboot_jobs(cron_db *),
38 		cron_tick(cron_db *, int),
39 		cron_sync(int),
40 		cron_sleep(cron_db *, int),
41 		cron_clean(cron_db *),
42 #ifdef USE_SIGCHLD
43 		sigchld_handler(int),
44 #endif
45 		sighup_handler(int),
46 		parse_args(int c, char *v[]);
47 
48 static int	run_at_secres(cron_db *);
49 static void	find_interval_entry(pid_t);
50 
51 static cron_db	database;
52 static time_t	last_time = 0;
53 static int	dst_enabled = 0;
54 static int	dont_daemonize = 0;
55 struct pidfh *pfh;
56 
57 static void
58 usage() {
59 #if DEBUGGING
60     char **dflags;
61 #endif
62 
63 	fprintf(stderr, "usage: cron [-j jitter] [-J rootjitter] "
64 			"[-m mailto] [-n] [-s] [-o] [-x debugflag[,...]]\n");
65 #if DEBUGGING
66 	fprintf(stderr, "\ndebugflags: ");
67 
68         for(dflags = DebugFlagNames; *dflags; dflags++) {
69 		fprintf(stderr, "%s ", *dflags);
70 	}
71         fprintf(stderr, "\n");
72 #endif
73 
74 	exit(ERROR_EXIT);
75 }
76 
77 static void
78 open_pidfile(void)
79 {
80 	char	pidfile[MAX_FNAME];
81 	char	buf[MAX_TEMPSTR];
82 	int	otherpid;
83 
84 	(void) snprintf(pidfile, sizeof(pidfile), PIDFILE, PIDDIR);
85 	pfh = pidfile_open(pidfile, 0600, &otherpid);
86 	if (pfh == NULL) {
87 		if (errno == EEXIST) {
88 			snprintf(buf, sizeof(buf),
89 			    "cron already running, pid: %d", otherpid);
90 		} else {
91 			snprintf(buf, sizeof(buf),
92 			    "can't open or create %s: %s", pidfile,
93 			    strerror(errno));
94 		}
95 		log_it("CRON", getpid(), "DEATH", buf);
96 		errx(ERROR_EXIT, "%s", buf);
97 	}
98 }
99 
100 int
101 main(argc, argv)
102 	int	argc;
103 	char	*argv[];
104 {
105 	int runnum;
106 	int secres1, secres2;
107 	struct tm *tm;
108 
109 	ProgramName = argv[0];
110 
111 #if defined(BSD)
112 	setlinebuf(stdout);
113 	setlinebuf(stderr);
114 #endif
115 
116 	parse_args(argc, argv);
117 
118 #ifdef USE_SIGCHLD
119 	(void) signal(SIGCHLD, sigchld_handler);
120 #else
121 	(void) signal(SIGCLD, SIG_IGN);
122 #endif
123 	(void) signal(SIGHUP, sighup_handler);
124 
125 	open_pidfile();
126 	set_cron_uid();
127 	set_cron_cwd();
128 
129 #if defined(POSIX)
130 	setenv("PATH", _PATH_DEFPATH, 1);
131 #endif
132 
133 	/* if there are no debug flags turned on, fork as a daemon should.
134 	 */
135 # if DEBUGGING
136 	if (DebugFlags) {
137 # else
138 	if (0) {
139 # endif
140 		(void) fprintf(stderr, "[%d] cron started\n", getpid());
141 	} else if (dont_daemonize == 0) {
142 		if (daemon(1, 0) == -1) {
143 			pidfile_remove(pfh);
144 			log_it("CRON",getpid(),"DEATH","can't become daemon");
145 			exit(0);
146 		}
147 	}
148 
149 	if (madvise(NULL, 0, MADV_PROTECT) != 0)
150 		log_it("CRON", getpid(), "WARNING", "madvise() failed");
151 
152 	pidfile_write(pfh);
153 	database.head = NULL;
154 	database.tail = NULL;
155 	database.mtime = (time_t) 0;
156 	load_database(&database);
157 	secres1 = secres2 = run_at_secres(&database);
158 	cron_sync(secres1);
159 	run_reboot_jobs(&database);
160 	runnum = 0;
161 	while (TRUE) {
162 # if DEBUGGING
163 	    /* if (!(DebugFlags & DTEST)) */
164 # endif /*DEBUGGING*/
165 			cron_sleep(&database, secres1);
166 
167 		if (secres1 == 0 || runnum % 60 == 0) {
168 			load_database(&database);
169 			secres2 = run_at_secres(&database);
170 			if (secres2 != secres1) {
171 				secres1 = secres2;
172 				if (secres1 != 0) {
173 					runnum = 0;
174 				} else {
175 					/*
176 					 * Going from 1 sec to 60 sec res. If we
177 					 * are already at minute's boundary, so
178 					 * let it run, otherwise schedule for the
179 					 * next minute.
180 					 */
181 					tm = localtime(&TargetTime);
182 					if (tm->tm_sec > 0)  {
183 						cron_sync(secres2);
184 						continue;
185 					}
186 				}
187 			}
188 		}
189 
190 		/* do this iteration
191 		 */
192 		cron_tick(&database, secres1);
193 
194 		/* sleep 1 or 60 seconds
195 		 */
196 		TargetTime += (secres1 != 0) ? 1 : 60;
197 		runnum += 1;
198 	}
199 }
200 
201 
202 static void
203 run_reboot_jobs(db)
204 	cron_db *db;
205 {
206 	register user		*u;
207 	register entry		*e;
208 
209 	for (u = db->head;  u != NULL;  u = u->next) {
210 		for (e = u->crontab;  e != NULL;  e = e->next) {
211 			if (e->flags & WHEN_REBOOT) {
212 				job_add(e, u);
213 			}
214 			if (e->flags & INTERVAL) {
215 				e->lastexit = TargetTime;
216 			}
217 		}
218 	}
219 	(void) job_runqueue();
220 }
221 
222 
223 static void
224 cron_tick(cron_db *db, int secres)
225 {
226 	static struct tm	lasttm;
227 	static time_t	diff = 0, /* time difference in seconds from the last offset change */
228 		difflimit = 0; /* end point for the time zone correction */
229 	struct tm	otztm; /* time in the old time zone */
230 	int		otzsecond, otzminute, otzhour, otzdom, otzmonth, otzdow;
231  	register struct tm	*tm = localtime(&TargetTime);
232 	register int		second, minute, hour, dom, month, dow;
233 	register user		*u;
234 	register entry		*e;
235 
236 	/* make 0-based values out of these so we can use them as indices
237 	 */
238 	second = (secres == 0) ? 0 : tm->tm_sec -FIRST_SECOND;
239 	minute = tm->tm_min -FIRST_MINUTE;
240 	hour = tm->tm_hour -FIRST_HOUR;
241 	dom = tm->tm_mday -FIRST_DOM;
242 	month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
243 	dow = tm->tm_wday -FIRST_DOW;
244 
245 	Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d,%d)\n",
246 		getpid(), second, minute, hour, dom, month, dow))
247 
248 	if (dst_enabled && last_time != 0
249 	&& TargetTime > last_time /* exclude stepping back */
250 	&& tm->tm_gmtoff != lasttm.tm_gmtoff ) {
251 
252 		diff = tm->tm_gmtoff - lasttm.tm_gmtoff;
253 
254 		if ( diff > 0 ) { /* ST->DST */
255 			/* mark jobs for an earlier run */
256 			difflimit = TargetTime + diff;
257 			for (u = db->head;  u != NULL;  u = u->next) {
258 				for (e = u->crontab;  e != NULL;  e = e->next) {
259 					e->flags &= ~NOT_UNTIL;
260 					if ( e->lastrun >= TargetTime )
261 						e->lastrun = 0;
262 					/* not include the ends of hourly ranges */
263 					if ( e->lastrun < TargetTime - 3600 )
264 						e->flags |= RUN_AT;
265 					else
266 						e->flags &= ~RUN_AT;
267 				}
268 			}
269 		} else { /* diff < 0 : DST->ST */
270 			/* mark jobs for skipping */
271 			difflimit = TargetTime - diff;
272 			for (u = db->head;  u != NULL;  u = u->next) {
273 				for (e = u->crontab;  e != NULL;  e = e->next) {
274 					e->flags |= NOT_UNTIL;
275 					e->flags &= ~RUN_AT;
276 				}
277 			}
278 		}
279 	}
280 
281 	if (diff != 0) {
282 		/* if the time was reset of the end of special zone is reached */
283 		if (last_time == 0 || TargetTime >= difflimit) {
284 			/* disable the TZ switch checks */
285 			diff = 0;
286 			difflimit = 0;
287 			for (u = db->head;  u != NULL;  u = u->next) {
288 				for (e = u->crontab;  e != NULL;  e = e->next) {
289 					e->flags &= ~(RUN_AT|NOT_UNTIL);
290 				}
291 			}
292 		} else {
293 			/* get the time in the old time zone */
294 			time_t difftime = TargetTime + tm->tm_gmtoff - diff;
295 			gmtime_r(&difftime, &otztm);
296 
297 			/* make 0-based values out of these so we can use them as indices
298 			 */
299 			otzsecond = (secres == 0) ? 0 : otztm.tm_sec -FIRST_SECOND;
300 			otzminute = otztm.tm_min -FIRST_MINUTE;
301 			otzhour = otztm.tm_hour -FIRST_HOUR;
302 			otzdom = otztm.tm_mday -FIRST_DOM;
303 			otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
304 			otzdow = otztm.tm_wday -FIRST_DOW;
305 		}
306 	}
307 
308 	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
309 	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
310 	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
311 	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
312 	 * like many bizarre things, it's the standard.
313 	 */
314 	for (u = db->head;  u != NULL;  u = u->next) {
315 		for (e = u->crontab;  e != NULL;  e = e->next) {
316 			Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n",
317 					  env_get("LOGNAME", e->envp),
318 					  e->uid, e->gid, e->cmd))
319 
320 			if (e->flags & INTERVAL) {
321 				if (e->lastexit > 0 &&
322 				    TargetTime >= e->lastexit + e->interval)
323 					job_add(e, u);
324 				continue;
325 			}
326 
327 			if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) {
328 				if (bit_test(e->second, otzsecond)
329 				 && bit_test(e->minute, otzminute)
330 				 && bit_test(e->hour, otzhour)
331 				 && bit_test(e->month, otzmonth)
332 				 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
333 					  ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom))
334 					  : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom))
335 					)
336 				   ) {
337 					if ( e->flags & RUN_AT ) {
338 						e->flags &= ~RUN_AT;
339 						e->lastrun = TargetTime;
340 						job_add(e, u);
341 						continue;
342 					} else
343 						e->flags &= ~NOT_UNTIL;
344 				} else if ( e->flags & NOT_UNTIL )
345 					continue;
346 			}
347 
348 			if (bit_test(e->second, second)
349 			 && bit_test(e->minute, minute)
350 			 && bit_test(e->hour, hour)
351 			 && bit_test(e->month, month)
352 			 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
353 			      ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
354 			      : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
355 			    )
356 			   ) {
357 				e->flags &= ~RUN_AT;
358 				e->lastrun = TargetTime;
359 				job_add(e, u);
360 			}
361 		}
362 	}
363 
364 	last_time = TargetTime;
365 	lasttm = *tm;
366 }
367 
368 
369 /* the task here is to figure out how long it's going to be until :00 of the
370  * following minute and initialize TargetTime to this value.  TargetTime
371  * will subsequently slide 60 seconds at a time, with correction applied
372  * implicitly in cron_sleep().  it would be nice to let cron execute in
373  * the "current minute" before going to sleep, but by restarting cron you
374  * could then get it to execute a given minute's jobs more than once.
375  * instead we have the chance of missing a minute's jobs completely, but
376  * that's something sysadmin's know to expect what with crashing computers..
377  */
378 static void
379 cron_sync(int secres) {
380  	struct tm *tm;
381 
382 	TargetTime = time((time_t*)0);
383 	if (secres != 0) {
384 		TargetTime += 1;
385 	} else {
386 		tm = localtime(&TargetTime);
387 		TargetTime += (60 - tm->tm_sec);
388 	}
389 }
390 
391 static void
392 timespec_subtract(struct timespec *result, struct timespec *x,
393     struct timespec *y)
394 {
395 	*result = *x;
396 	result->tv_sec -= y->tv_sec;
397 	result->tv_nsec -= y->tv_nsec;
398 	if (result->tv_nsec < 0) {
399 		result->tv_sec--;
400 		result->tv_nsec += 1000000000;
401 	}
402 }
403 
404 static void
405 cron_sleep(cron_db *db, int secres)
406 {
407 	int seconds_to_wait;
408 	int rval;
409 	struct timespec ctime, ttime, stime, remtime;
410 
411 	/*
412 	 * Loop until we reach the top of the next minute, sleep when possible.
413 	 */
414 
415 	for (;;) {
416 		clock_gettime(CLOCK_REALTIME, &ctime);
417 		ttime.tv_sec = TargetTime;
418 		ttime.tv_nsec = 0;
419 		timespec_subtract(&stime, &ttime, &ctime);
420 
421 		/*
422 		 * If the seconds_to_wait value is insane, jump the cron
423 		 */
424 
425 		if (stime.tv_sec < -600 || stime.tv_sec > 600) {
426 			cron_clean(db);
427 			cron_sync(secres);
428 			continue;
429 		}
430 
431 		seconds_to_wait = (stime.tv_nsec > 0) ? stime.tv_sec + 1 :
432 		    stime.tv_sec;
433 
434 		Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
435 			getpid(), (long)TargetTime, seconds_to_wait))
436 
437 		/*
438 		 * If we've run out of wait time or there are no jobs left
439 		 * to run, break
440 		 */
441 
442 		if (stime.tv_sec < 0)
443 			break;
444 		if (job_runqueue() == 0) {
445 			Debug(DSCH, ("[%d] sleeping for %d seconds\n",
446 				getpid(), seconds_to_wait))
447 
448 			for (;;) {
449 				rval = nanosleep(&stime, &remtime);
450 				if (rval == 0 || errno != EINTR)
451 					break;
452 				stime.tv_sec = remtime.tv_sec;
453 				stime.tv_nsec = remtime.tv_nsec;
454 			}
455 		}
456 	}
457 }
458 
459 
460 /* if the time was changed abruptly, clear the flags related
461  * to the daylight time switch handling to avoid strange effects
462  */
463 
464 static void
465 cron_clean(db)
466 	cron_db	*db;
467 {
468 	user		*u;
469 	entry		*e;
470 
471 	last_time = 0;
472 
473 	for (u = db->head;  u != NULL;  u = u->next) {
474 		for (e = u->crontab;  e != NULL;  e = e->next) {
475 			e->flags &= ~(RUN_AT|NOT_UNTIL);
476 		}
477 	}
478 }
479 
480 #ifdef USE_SIGCHLD
481 static void
482 sigchld_handler(int x)
483 {
484 	WAIT_T		waiter;
485 	PID_T		pid;
486 
487 	for (;;) {
488 #ifdef POSIX
489 		pid = waitpid(-1, &waiter, WNOHANG);
490 #else
491 		pid = wait3(&waiter, WNOHANG, (struct rusage *)0);
492 #endif
493 		switch (pid) {
494 		case -1:
495 			Debug(DPROC,
496 				("[%d] sigchld...no children\n", getpid()))
497 			return;
498 		case 0:
499 			Debug(DPROC,
500 				("[%d] sigchld...no dead kids\n", getpid()))
501 			return;
502 		default:
503 			find_interval_entry(pid);
504 			Debug(DPROC,
505 				("[%d] sigchld...pid #%d died, stat=%d\n",
506 				getpid(), pid, WEXITSTATUS(waiter)))
507 		}
508 	}
509 }
510 #endif /*USE_SIGCHLD*/
511 
512 
513 static void
514 sighup_handler(int x)
515 {
516 	log_close();
517 }
518 
519 
520 static void
521 parse_args(argc, argv)
522 	int	argc;
523 	char	*argv[];
524 {
525 	int	argch;
526 	char	*endp;
527 
528 	while ((argch = getopt(argc, argv, "j:J:m:nosx:")) != -1) {
529 		switch (argch) {
530 		case 'j':
531 			Jitter = strtoul(optarg, &endp, 10);
532 			if (*optarg == '\0' || *endp != '\0' || Jitter > 60)
533 				errx(ERROR_EXIT,
534 				     "bad value for jitter: %s", optarg);
535 			break;
536 		case 'J':
537 			RootJitter = strtoul(optarg, &endp, 10);
538 			if (*optarg == '\0' || *endp != '\0' || RootJitter > 60)
539 				errx(ERROR_EXIT,
540 				     "bad value for root jitter: %s", optarg);
541 			break;
542 		case 'm':
543 			defmailto = optarg;
544 			break;
545 		case 'n':
546 			dont_daemonize = 1;
547 			break;
548 		case 'o':
549 			dst_enabled = 0;
550 			break;
551 		case 's':
552 			dst_enabled = 1;
553 			break;
554 		case 'x':
555 			if (!set_debug_flags(optarg))
556 				usage();
557 			break;
558 		default:
559 			usage();
560 		}
561 	}
562 }
563 
564 static int
565 run_at_secres(cron_db *db)
566 {
567 	user *u;
568 	entry *e;
569 
570 	for (u = db->head;  u != NULL;  u = u->next) {
571 		for (e = u->crontab;  e != NULL;  e = e->next) {
572 			if ((e->flags & (SEC_RES | INTERVAL)) != 0)
573 				return 1;
574 		}
575 	}
576 	return 0;
577 }
578 
579 static void
580 find_interval_entry(pid_t pid)
581 {
582 	user *u;
583 	entry *e;
584 
585 	for (u = database.head;  u != NULL;  u = u->next) {
586 		for (e = u->crontab;  e != NULL;  e = e->next) {
587 			if ((e->flags & INTERVAL) && e->child == pid) {
588 				e->lastexit = time(NULL);
589 				e->child = 0;
590 				break;
591 			}
592 		}
593 	}
594 }
595