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