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