xref: /netbsd/external/bsd/cron/dist/cron.c (revision bd4a2c22)
1 /*	$NetBSD: cron.c,v 1.11 2020/04/18 19:32:19 christos Exp $	*/
2 
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
4  * All rights reserved
5  */
6 
7 /*
8  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
9  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
10  *
11  * Permission to use, copy, modify, and distribute this software for any
12  * purpose with or without fee is hereby granted, provided that the above
13  * copyright notice and this permission notice appear in all copies.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
16  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
18  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
21  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  */
23 #include <sys/cdefs.h>
24 #if !defined(lint) && !defined(LINT)
25 #if 0
26 static char rcsid[] = "Id: cron.c,v 1.12 2004/01/23 18:56:42 vixie Exp";
27 #else
28 __RCSID("$NetBSD: cron.c,v 1.11 2020/04/18 19:32:19 christos Exp $");
29 #endif
30 #endif
31 
32 #define	MAIN_PROGRAM
33 
34 #include "cron.h"
35 
36 enum timejump { negative, small, medium, large };
37 
38 static	void	usage(void) __dead,
39 		run_reboot_jobs(cron_db *),
40 		find_jobs(time_t, cron_db *, int, int),
41 		set_time(int),
42 		cron_sleep(time_t),
43 		sigchld_handler(int),
44 		sighup_handler(int),
45 		sigchld_reaper(void),
46 		quit(int) __dead,
47 		parse_args(int c, char *v[]);
48 
49 static	volatile sig_atomic_t	got_sighup, got_sigchld;
50 static	time_t			timeRunning, virtualTime, clockTime;
51 static	long			GMToff;
52 
53 static void
usage(void)54 usage(void) {
55 	const char **dflags;
56 
57 	(void)fprintf(stderr, "usage:  %s [-n] [-x [", getprogname());
58 	for (dflags = DebugFlagNames; *dflags; dflags++)
59 		(void)fprintf(stderr, "%s%s", *dflags, dflags[1] ? "," : "]");
60 	(void)fprintf(stderr, "]\n");
61 	exit(ERROR_EXIT);
62 }
63 
64 int
main(int argc,char * argv[])65 main(int argc, char *argv[]) {
66 	struct sigaction sact;
67 	cron_db	database;
68 
69 	setprogname(argv[0]);
70 	(void)setlocale(LC_ALL, "");
71 
72 	(void)setvbuf(stdout, NULL, _IOLBF, 0);
73 	(void)setvbuf(stderr, NULL, _IOLBF, 0);
74 
75 	NoFork = 0;
76 	parse_args(argc, argv);
77 
78 	(void)memset(&sact, 0, sizeof sact);
79 	(void)sigemptyset(&sact.sa_mask);
80 	sact.sa_flags = 0;
81 #ifdef SA_RESTART
82 	sact.sa_flags |= SA_RESTART;
83 #endif
84 	sact.sa_handler = sigchld_handler;
85 	(void) sigaction(SIGCHLD, &sact, NULL);
86 	sact.sa_handler = sighup_handler;
87 	(void) sigaction(SIGHUP, &sact, NULL);
88 	sact.sa_handler = quit;
89 	(void) sigaction(SIGINT, &sact, NULL);
90 	(void) sigaction(SIGTERM, &sact, NULL);
91 
92 	acquire_daemonlock(0);
93 	set_cron_uid();
94 	set_cron_cwd();
95 
96 	if (setenv("PATH", _PATH_DEFPATH, 1) < 0) {
97 		log_it("CRON", getpid(), "DEATH", "can't malloc");
98 		exit(1);
99 	}
100 
101 	/* if there are no debug flags turned on, fork as a daemon should.
102 	 */
103 	if (DebugFlags) {
104 #if DEBUGGING
105 		(void)fprintf(stderr, "[%ld] cron started\n", (long)getpid());
106 #endif
107 	} else if (NoFork == 0) {
108 		if (daemon(1, 0)) {
109 			log_it("CRON",getpid(),"DEATH","can't fork");
110 			exit(1);
111 		}
112 	}
113 
114 	acquire_daemonlock(0);
115 	database.head = NULL;
116 	database.tail = NULL;
117 	database.mtime = (time_t) 0;
118 	load_database(&database);
119 	set_time(TRUE);
120 	run_reboot_jobs(&database);
121 	timeRunning = virtualTime = clockTime;
122 
123 	/*
124 	 * Too many clocks, not enough time (Al. Einstein)
125 	 * These clocks are in minutes since the epoch, adjusted for timezone.
126 	 * virtualTime: is the time it *would* be if we woke up
127 	 * promptly and nobody ever changed the clock. It is
128 	 * monotonically increasing... unless a timejump happens.
129 	 * At the top of the loop, all jobs for 'virtualTime' have run.
130 	 * timeRunning: is the time we last awakened.
131 	 * clockTime: is the time when set_time was last called.
132 	 */
133 	for (;;) {
134 		time_t timeDiff;
135 		enum timejump wakeupKind;
136 
137 		/* ... wait for the time (in minutes) to change ... */
138 		do {
139 			cron_sleep(timeRunning + 1);
140 			set_time(FALSE);
141 		} while (clockTime == timeRunning);
142 		timeRunning = clockTime;
143 
144 		/*
145 		 * Calculate how the current time differs from our virtual
146 		 * clock.  Classify the change into one of 4 cases.
147 		 */
148 		timeDiff = timeRunning - virtualTime;
149 
150 		/* shortcut for the most common case */
151 		if (timeDiff == 1) {
152 			virtualTime = timeRunning;
153 			find_jobs(virtualTime, &database, TRUE, TRUE);
154 		} else {
155 			if (timeDiff > (3*MINUTE_COUNT) ||
156 			    timeDiff < -(3*MINUTE_COUNT))
157 				wakeupKind = large;
158 			else if (timeDiff > 5)
159 				wakeupKind = medium;
160 			else if (timeDiff > 0)
161 				wakeupKind = small;
162 			else
163 				wakeupKind = negative;
164 
165 			switch (wakeupKind) {
166 			case small:
167 				/*
168 				 * case 1: timeDiff is a small positive number
169 				 * (wokeup late) run jobs for each virtual
170 				 * minute until caught up.
171 				 */
172 				Debug(DSCH, ("[%jd], normal case %jd minutes to go\n",
173 				    (intmax_t)getpid(), (intmax_t)timeDiff));
174 				do {
175 					if (job_runqueue())
176 						(void)sleep(10);
177 					virtualTime++;
178 					find_jobs(virtualTime, &database,
179 					    TRUE, TRUE);
180 				} while (virtualTime < timeRunning);
181 				break;
182 
183 			case medium:
184 				/*
185 				 * case 2: timeDiff is a medium-sized positive
186 				 * number, for example because we went to DST
187 				 * run wildcard jobs once, then run any
188 				 * fixed-time jobs that would otherwise be
189 				 * skipped if we use up our minute (possible,
190 				 * if there are a lot of jobs to run) go
191 				 * around the loop again so that wildcard jobs
192 				 * have a chance to run, and we do our
193 				 * housekeeping.
194 				 */
195 				Debug(DSCH, ("[%jd], DST begins %jd minutes to go\n",
196 				    (intmax_t)getpid(), (intmax_t)timeDiff));
197 				/* run wildcard jobs for current minute */
198 				find_jobs(timeRunning, &database, TRUE, FALSE);
199 
200 				/* run fixed-time jobs for each minute missed */
201 				do {
202 					if (job_runqueue())
203 						(void)sleep(10);
204 					virtualTime++;
205 					find_jobs(virtualTime, &database,
206 					    FALSE, TRUE);
207 					set_time(FALSE);
208 				} while (virtualTime < timeRunning &&
209 				    clockTime == timeRunning);
210 				break;
211 
212 			case negative:
213 				/*
214 				 * case 3: timeDiff is a small or medium-sized
215 				 * negative num, eg. because of DST ending.
216 				 * Just run the wildcard jobs. The fixed-time
217 				 * jobs probably have already run, and should
218 				 * not be repeated.  Virtual time does not
219 				 * change until we are caught up.
220 				 */
221 				Debug(DSCH, ("[%jd], DST ends %jd minutes to go\n",
222 				    (intmax_t)getpid(), (intmax_t)timeDiff));
223 				find_jobs(timeRunning, &database, TRUE, FALSE);
224 				break;
225 			default:
226 				/*
227 				 * other: time has changed a *lot*,
228 				 * jump virtual time, and run everything
229 				 */
230 				Debug(DSCH, ("[%ld], clock jumped\n",
231 				    (long)getpid()));
232 				virtualTime = timeRunning;
233 				find_jobs(timeRunning, &database, TRUE, TRUE);
234 			}
235 		}
236 
237 		/* Jobs to be run (if any) are loaded; clear the queue. */
238 		(void)job_runqueue();
239 
240 		/* Check to see if we received a signal while running jobs. */
241 		if (got_sighup) {
242 			got_sighup = 0;
243 			log_close();
244 		}
245 		if (got_sigchld) {
246 			got_sigchld = 0;
247 			sigchld_reaper();
248 		}
249 		load_database(&database);
250 	}
251 }
252 
253 static void
run_reboot_jobs(cron_db * db)254 run_reboot_jobs(cron_db *db) {
255 	user *u;
256 	entry *e;
257 
258 	for (u = db->head; u != NULL; u = u->next) {
259 		for (e = u->crontab; e != NULL; e = e->next) {
260 			if (e->flags & WHEN_REBOOT)
261 				job_add(e, u, StartTime);
262 		}
263 	}
264 	(void) job_runqueue();
265 }
266 
267 static const char *
bitprint(const bitstr_t * b,size_t l)268 bitprint(const bitstr_t *b, size_t l)
269 {
270 	size_t i, set, clear;
271 	static char result[1024];
272 	char tmp[1024];
273 
274 	result[0] = '\0';
275 	for (i = 0;;) {
276 		for (; i < l; i++)
277 			if (bit_test(b, i))
278 				break;
279 		if (i == l)
280 			return result;
281 		set = i;
282 		for (; i < l; i++)
283 			if (!bit_test(b, i))
284 				break;
285 		clear = i;
286 		if (set == 0 && clear == l) {
287 			snprintf(result, sizeof(result), "*");
288 			return result;
289 		}
290 		if (clear == l || set == clear - 1)
291 			snprintf(tmp, sizeof(tmp), ",%zu", set);
292 		else
293 			snprintf(tmp, sizeof(tmp), ",%zu-%zu", set, clear - 1);
294 		if (result[0] == '\0')
295 			strlcpy(result, tmp + 1, sizeof(result));
296 		else
297 			strlcat(result, tmp, sizeof(result));
298 	}
299 }
300 
301 static const char *
flagsprint(int flags)302 flagsprint(int flags)
303 {
304 	static char result[1024];
305 
306 	result[0] = '\0';
307 	if (flags & MIN_STAR)
308 		strlcat(result, ",min", sizeof(result));
309 	if (flags & HR_STAR)
310 		strlcat(result, ",hr", sizeof(result));
311 	if (flags & DOM_STAR)
312 		strlcat(result, ",dom", sizeof(result));
313 	if (flags & DOW_STAR)
314 		strlcat(result, ",dow", sizeof(result));
315 	if (flags & WHEN_REBOOT)
316 		strlcat(result, ",reboot", sizeof(result));
317 	if (flags & DONT_LOG)
318 		strlcat(result, ",nolog", sizeof(result));
319 
320 	return result + (result[0] == ',');
321 }
322 
323 static void
printone(char * res,size_t len,const char * b)324 printone(char *res, size_t len, const char *b)
325 {
326 	int comma = strchr(b, ',') != NULL;
327 	if (comma)
328 		strlcat(res, "[", len);
329 	strlcat(res, b, len);
330 	strlcat(res, "]," + (comma ? 0 : 1), len);
331 }
332 
333 static const char *
tick(const entry * e)334 tick(const entry *e)
335 {
336 	static char result[1024];
337 
338 	result[0] = '\0';
339 	printone(result, sizeof(result), bitprint(e->minute, MINUTE_COUNT));
340 	printone(result, sizeof(result), bitprint(e->hour, HOUR_COUNT));
341 	printone(result, sizeof(result), bitprint(e->dom, DOM_COUNT));
342 	printone(result, sizeof(result), bitprint(e->month, MONTH_COUNT));
343 	printone(result, sizeof(result), bitprint(e->dow, DOW_COUNT));
344 	strlcat(result, "flags=", sizeof(result));
345 	strlcat(result, flagsprint(e->flags), sizeof(result));
346 	return result;
347 }
348 
349 static void
find_jobs(time_t vtime,cron_db * db,int doWild,int doNonWild)350 find_jobs(time_t vtime, cron_db *db, int doWild, int doNonWild) {
351 	time_t virtualSecond = vtime * SECONDS_PER_MINUTE;
352 	struct tm *tm;
353 	int minute, hour, dom, month, dow;
354 	user *u;
355 	entry *e;
356 #ifndef CRON_LOCALTIME
357 	char *orig_tz, *job_tz;
358 
359 #define maketime(tz1, tz2) do { \
360 	char *t = tz1; \
361 	if (t != NULL && *t != '\0') \
362 		setenv("TZ", t, 1); \
363 	else if ((tz2) != NULL) \
364 		setenv("TZ", (tz2), 1); \
365 	else \
366 		unsetenv("TZ"); \
367 	tzset(); \
368 	tm = localtime(&virtualSecond); \
369 	minute = tm->tm_min -FIRST_MINUTE; \
370 	hour = tm->tm_hour -FIRST_HOUR; \
371 	dom = tm->tm_mday -FIRST_DOM; \
372 	month = tm->tm_mon + 1 /* 0..11 -> 1..12 */ -FIRST_MONTH; \
373 	dow = tm->tm_wday -FIRST_DOW; \
374 	} while (/*CONSTCOND*/0)
375 
376 	orig_tz = getenv("TZ");
377 	maketime(NULL, orig_tz);
378 #else
379 	tm = gmtime(&virtualSecond);
380 #endif
381 
382 	Debug(DSCH, ("[%ld] tick(%d,%d,%d,%d,%d) %s %s\n",
383 		     (long)getpid(), minute, hour, dom, month, dow,
384 		     doWild?" ":"No wildcard",doNonWild?" ":"Wildcard only"));
385 
386 	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
387 	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
388 	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
389 	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
390 	 * like many bizarre things, it's the standard.
391 	 */
392 	for (u = db->head; u != NULL; u = u->next) {
393 		for (e = u->crontab; e != NULL; e = e->next) {
394 #ifndef CRON_LOCALTIME
395 			job_tz = env_get("CRON_TZ", e->envp);
396 			maketime(job_tz, orig_tz);
397 #else
398 #define job_tz "N/A"
399 #define orig_tz "N/A"
400 #endif
401 			Debug(DSCH|DEXT, ("user [%s:%ld:%ld:...] "
402 			    "[jobtz=%s, origtz=%s] "
403 			    "tick(%s), cmd=\"%s\"\n",
404 			    e->pwd->pw_name, (long)e->pwd->pw_uid,
405 			    (long)e->pwd->pw_gid, job_tz, orig_tz,
406 			    tick(e), e->cmd));
407 			if (bit_test(e->minute, minute) &&
408 			    bit_test(e->hour, hour) &&
409 			    bit_test(e->month, month) &&
410 			    ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
411 			      ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
412 			      : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
413 			    )
414 			   ) {
415 				if ((doNonWild &&
416 				    !(e->flags & (MIN_STAR|HR_STAR))) ||
417 				    (doWild && (e->flags & (MIN_STAR|HR_STAR))))
418 					job_add(e, u, StartTime);
419 			}
420 		}
421 	}
422 #ifndef CRON_LOCALTIME
423 	if (orig_tz != NULL)
424 		setenv("TZ", orig_tz, 1);
425 	else
426 		unsetenv("TZ");
427 #endif
428 }
429 
430 /*
431  * Set StartTime and clockTime to the current time.
432  * These are used for computing what time it really is right now.
433  * Note that clockTime is a unix wallclock time converted to minutes.
434  */
435 static void
set_time(int initialize)436 set_time(int initialize) {
437 
438 #ifdef CRON_LOCALTIME
439 	struct tm tm;
440 	static int isdst;
441 
442 	StartTime = time(NULL);
443 	/* We adjust the time to GMT so we can catch DST changes. */
444 	tm = *localtime(&StartTime);
445 	if (initialize || tm.tm_isdst != isdst) {
446 		isdst = tm.tm_isdst;
447 		GMToff = get_gmtoff(&StartTime, &tm);
448 		Debug(DSCH, ("[%ld] GMToff=%ld\n",
449 		    (long)getpid(), (long)GMToff));
450 	}
451 #else
452 	StartTime = time(NULL);
453 #endif
454 	clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE;
455 }
456 
457 /*
458  * Try to just hit the next minute.
459  */
460 static void
cron_sleep(time_t target)461 cron_sleep(time_t target) {
462 	time_t t1, t2;
463 	int seconds_to_wait;
464 
465 	t1 = time(NULL) + GMToff;
466 	seconds_to_wait = (int)(target * SECONDS_PER_MINUTE - t1);
467 	Debug(DSCH, ("[%ld] Target time=%lld, sec-to-wait=%d\n",
468 	    (long)getpid(), (long long)target*SECONDS_PER_MINUTE,
469 	    seconds_to_wait));
470 
471 	while (seconds_to_wait > 0 && seconds_to_wait < 65) {
472 		(void)sleep((unsigned int) seconds_to_wait);
473 
474 		/*
475 		 * Check to see if we were interrupted by a signal.
476 		 * If so, service the signal(s) then continue sleeping
477 		 * where we left off.
478 		 */
479 		if (got_sighup) {
480 			got_sighup = 0;
481 			log_close();
482 		}
483 		if (got_sigchld) {
484 			got_sigchld = 0;
485 			sigchld_reaper();
486 		}
487 		t2 = time(NULL) + GMToff;
488 		seconds_to_wait -= (int)(t2 - t1);
489 		t1 = t2;
490 	}
491 }
492 
493 /*ARGSUSED*/
494 static void
sighup_handler(int x __unused)495 sighup_handler(int x __unused) {
496 	got_sighup = 1;
497 }
498 
499 /*ARGSUSED*/
500 static void
sigchld_handler(int x __unused)501 sigchld_handler(int x __unused) {
502 	got_sigchld = 1;
503 }
504 
505 /*ARGSUSED*/
506 static void
quit(int x __unused)507 quit(int x __unused) {
508 	(void) unlink(_PATH_CRON_PID);
509 	_exit(0);
510 }
511 
512 static void
sigchld_reaper(void)513 sigchld_reaper(void) {
514 	for (;;) {
515 		WAIT_T waiter;
516 		PID_T pid = waitpid(-1, &waiter, WNOHANG);
517 
518 		switch (pid) {
519 		case -1:
520 			if (errno == EINTR)
521 				continue;
522 			Debug(DPROC,
523 			      ("[%ld] sigchld...no children\n",
524 			       (long)getpid()));
525 			return;
526 		case 0:
527 			Debug(DPROC,
528 			      ("[%ld] sigchld...no dead kids\n",
529 			       (long)getpid()));
530 			return;
531 		default:
532 			Debug(DPROC,
533 			      ("[%ld] sigchld...pid #%ld died, stat=%d\n",
534 			       (long)getpid(), (long)pid, WEXITSTATUS(waiter)));
535 			job_exit(pid);
536 			break;
537 		}
538 	}
539 }
540 
541 static void
parse_args(int argc,char * argv[])542 parse_args(int argc, char *argv[]) {
543 	int argch;
544 
545 	while (-1 != (argch = getopt(argc, argv, "nx:"))) {
546 		switch (argch) {
547 		default:
548 			usage();
549 			break;
550 		case 'x':
551 			if (!set_debug_flags(optarg))
552 				usage();
553 			break;
554 		case 'n':
555 			NoFork = 1;
556 			break;
557 		}
558 	}
559 }
560