xref: /openbsd/usr.sbin/cron/cron.c (revision 5c7b7de1)
1 /*	$OpenBSD: cron.c,v 1.56 2015/10/26 14:27:41 millert Exp $	*/
2 
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
4  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
5  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
17  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #define	MAIN_PROGRAM
21 
22 #include "cron.h"
23 
24 enum timejump { negative, small, medium, large };
25 
26 static	void	usage(void),
27 		run_reboot_jobs(cron_db *),
28 		find_jobs(time_t, cron_db *, int, int),
29 		set_time(int),
30 		cron_sleep(time_t, sigset_t *),
31 		sigchld_handler(int),
32 		sighup_handler(int),
33 		sigchld_reaper(void),
34 		parse_args(int c, char *v[]);
35 
36 static	volatile sig_atomic_t	got_sighup, got_sigchld;
37 static	time_t			timeRunning, virtualTime, clockTime;
38 static	int			cronSock;
39 static	long			GMToff;
40 static	cron_db			database;
41 static	at_db			at_database;
42 static	double			batch_maxload = BATCH_MAXLOAD;
43 
44 static void
45 usage(void)
46 {
47 
48 	fprintf(stderr, "usage: %s [-n] [-l load_avg]\n", ProgramName);
49 	exit(EXIT_FAILURE);
50 }
51 
52 int
53 main(int argc, char *argv[])
54 {
55 	struct sigaction sact;
56 	sigset_t blocked, omask;
57 	int fd;
58 
59 	ProgramName = argv[0];
60 
61 	setlocale(LC_ALL, "");
62 
63 	setvbuf(stdout, NULL, _IOLBF, 0);
64 	setvbuf(stderr, NULL, _IOLBF, 0);
65 
66 	NoFork = 0;
67 	parse_args(argc, argv);
68 
69 	bzero((char *)&sact, sizeof sact);
70 	sigemptyset(&sact.sa_mask);
71 	sact.sa_flags = 0;
72 	sact.sa_flags |= SA_RESTART;
73 	sact.sa_handler = sigchld_handler;
74 	(void) sigaction(SIGCHLD, &sact, NULL);
75 	sact.sa_handler = sighup_handler;
76 	(void) sigaction(SIGHUP, &sact, NULL);
77 	sact.sa_handler = SIG_IGN;
78 	(void) sigaction(SIGPIPE, &sact, NULL);
79 
80 	set_cron_cwd();
81 
82 	cronSock = open_socket();
83 
84 	if (putenv("PATH="_PATH_DEFPATH) < 0) {
85 		log_it("CRON", getpid(), "DEATH", "can't malloc");
86 		exit(EXIT_FAILURE);
87 	}
88 
89 	if (NoFork == 0) {
90 		if (daemon(1, 0) == -1) {
91 			log_it("CRON",getpid(),"DEATH","can't fork");
92 			exit(EXIT_FAILURE);
93 		}
94 		log_it("CRON",getpid(),"STARTUP",CRON_VERSION);
95 	}
96 
97 	database.head = NULL;
98 	database.tail = NULL;
99 	database.mtime = 0;
100 	load_database(&database);
101 	at_database.head = NULL;
102 	at_database.tail = NULL;
103 	at_database.mtime = 0;
104 	scan_atjobs(&at_database, NULL);
105 	set_time(TRUE);
106 	run_reboot_jobs(&database);
107 	timeRunning = virtualTime = clockTime;
108 
109 	/*
110 	 * We block SIGHUP and SIGCHLD while running jobs and receive them
111 	 * only while sleeping in ppoll().  This ensures no signal is lost.
112 	 */
113 	sigemptyset(&blocked);
114 	sigaddset(&blocked, SIGCHLD);
115 	sigaddset(&blocked, SIGHUP);
116 	sigprocmask(SIG_BLOCK, &blocked, &omask);
117 
118 	/*
119 	 * Too many clocks, not enough time (Al. Einstein)
120 	 * These clocks are in minutes since the epoch, adjusted for timezone.
121 	 * virtualTime: is the time it *would* be if we woke up
122 	 * promptly and nobody ever changed the clock. It is
123 	 * monotonically increasing... unless a timejump happens.
124 	 * At the top of the loop, all jobs for 'virtualTime' have run.
125 	 * timeRunning: is the time we last awakened.
126 	 * clockTime: is the time when set_time was last called.
127 	 */
128 	while (TRUE) {
129 		int timeDiff;
130 		enum timejump wakeupKind;
131 
132 		/* ... wait for the time (in minutes) to change ... */
133 		do {
134 			cron_sleep(timeRunning + 1, &omask);
135 			set_time(FALSE);
136 		} while (clockTime == timeRunning);
137 		timeRunning = clockTime;
138 
139 		/*
140 		 * Calculate how the current time differs from our virtual
141 		 * clock.  Classify the change into one of 4 cases.
142 		 */
143 		timeDiff = timeRunning - virtualTime;
144 
145 		/* shortcut for the most common case */
146 		if (timeDiff == 1) {
147 			virtualTime = timeRunning;
148 			find_jobs(virtualTime, &database, TRUE, TRUE);
149 		} else {
150 			if (timeDiff > (3*MINUTE_COUNT) ||
151 			    timeDiff < -(3*MINUTE_COUNT))
152 				wakeupKind = large;
153 			else if (timeDiff > 5)
154 				wakeupKind = medium;
155 			else if (timeDiff > 0)
156 				wakeupKind = small;
157 			else
158 				wakeupKind = negative;
159 
160 			switch (wakeupKind) {
161 			case small:
162 				/*
163 				 * case 1: timeDiff is a small positive number
164 				 * (wokeup late) run jobs for each virtual
165 				 * minute until caught up.
166 				 */
167 				do {
168 					if (job_runqueue())
169 						sleep(10);
170 					virtualTime++;
171 					find_jobs(virtualTime, &database,
172 					    TRUE, TRUE);
173 				} while (virtualTime < timeRunning);
174 				break;
175 
176 			case medium:
177 				/*
178 				 * case 2: timeDiff is a medium-sized positive
179 				 * number, for example because we went to DST
180 				 * run wildcard jobs once, then run any
181 				 * fixed-time jobs that would otherwise be
182 				 * skipped if we use up our minute (possible,
183 				 * if there are a lot of jobs to run) go
184 				 * around the loop again so that wildcard jobs
185 				 * have a chance to run, and we do our
186 				 * housekeeping.
187 				 */
188 				/* run wildcard jobs for current minute */
189 				find_jobs(timeRunning, &database, TRUE, FALSE);
190 
191 				/* run fixed-time jobs for each minute missed */
192 				do {
193 					if (job_runqueue())
194 						sleep(10);
195 					virtualTime++;
196 					find_jobs(virtualTime, &database,
197 					    FALSE, TRUE);
198 					set_time(FALSE);
199 				} while (virtualTime< timeRunning &&
200 				    clockTime == timeRunning);
201 				break;
202 
203 			case negative:
204 				/*
205 				 * case 3: timeDiff is a small or medium-sized
206 				 * negative num, eg. because of DST ending.
207 				 * Just run the wildcard jobs. The fixed-time
208 				 * jobs probably have already run, and should
209 				 * not be repeated.  Virtual time does not
210 				 * change until we are caught up.
211 				 */
212 				find_jobs(timeRunning, &database, TRUE, FALSE);
213 				break;
214 			default:
215 				/*
216 				 * other: time has changed a *lot*,
217 				 * jump virtual time, and run everything
218 				 */
219 				virtualTime = timeRunning;
220 				find_jobs(timeRunning, &database, TRUE, TRUE);
221 			}
222 		}
223 
224 		/* Jobs to be run (if any) are loaded; clear the queue. */
225 		job_runqueue();
226 
227 		/* Run any jobs in the at queue. */
228 		atrun(&at_database, batch_maxload,
229 		    timeRunning * SECONDS_PER_MINUTE - GMToff);
230 
231 		/* Reload jobs as needed. */
232 		load_database(&database);
233 		scan_atjobs(&at_database, NULL);
234 	}
235 }
236 
237 static void
238 run_reboot_jobs(cron_db *db)
239 {
240 	user *u;
241 	entry *e;
242 
243 	for (u = db->head; u != NULL; u = u->next) {
244 		for (e = u->crontab; e != NULL; e = e->next) {
245 			if (e->flags & WHEN_REBOOT)
246 				job_add(e, u);
247 		}
248 	}
249 	(void) job_runqueue();
250 }
251 
252 static void
253 find_jobs(time_t vtime, cron_db *db, int doWild, int doNonWild)
254 {
255 	time_t virtualSecond  = vtime * SECONDS_PER_MINUTE;
256 	struct tm *tm = gmtime(&virtualSecond);
257 	int minute, hour, dom, month, dow;
258 	user *u;
259 	entry *e;
260 
261 	/* make 0-based values out of these so we can use them as indices
262 	 */
263 	minute = tm->tm_min -FIRST_MINUTE;
264 	hour = tm->tm_hour -FIRST_HOUR;
265 	dom = tm->tm_mday -FIRST_DOM;
266 	month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
267 	dow = tm->tm_wday -FIRST_DOW;
268 
269 	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
270 	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
271 	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
272 	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
273 	 * like many bizarre things, it's the standard.
274 	 */
275 	for (u = db->head; u != NULL; u = u->next) {
276 		for (e = u->crontab; e != NULL; e = e->next) {
277 			if (bit_test(e->minute, minute) &&
278 			    bit_test(e->hour, hour) &&
279 			    bit_test(e->month, month) &&
280 			    ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
281 			      ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
282 			      : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
283 			    )
284 			   ) {
285 				if ((doNonWild &&
286 				    !(e->flags & (MIN_STAR|HR_STAR))) ||
287 				    (doWild && (e->flags & (MIN_STAR|HR_STAR))))
288 					job_add(e, u);
289 			}
290 		}
291 	}
292 }
293 
294 /*
295  * Set StartTime and clockTime to the current time.
296  * These are used for computing what time it really is right now.
297  * Note that clockTime is a unix wallclock time converted to minutes.
298  */
299 static void
300 set_time(int initialize)
301 {
302 	struct tm tm;
303 	static int isdst;
304 
305 	StartTime = time(NULL);
306 
307 	/* We adjust the time to GMT so we can catch DST changes. */
308 	tm = *localtime(&StartTime);
309 	if (initialize || tm.tm_isdst != isdst) {
310 		isdst = tm.tm_isdst;
311 		GMToff = get_gmtoff(&StartTime, &tm);
312 	}
313 	clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE;
314 }
315 
316 /*
317  * Try to just hit the next minute.
318  */
319 static void
320 cron_sleep(time_t target, sigset_t *mask)
321 {
322 	int fd, nfds;
323 	unsigned char poke;
324 	struct timespec t1, t2, timeout;
325 	struct sockaddr_un s_un;
326 	socklen_t sunlen;
327 	static struct pollfd pfd[1];
328 
329 	clock_gettime(CLOCK_REALTIME, &t1);
330 	t1.tv_sec += GMToff;
331 	timeout.tv_sec = (target * SECONDS_PER_MINUTE - t1.tv_sec) + 1;
332 	timeout.tv_nsec = 0;
333 
334 	pfd[0].fd = cronSock;
335 	pfd[0].events = POLLIN;
336 
337 	while (timespecisset(&timeout) && timeout.tv_sec < 65) {
338 		poke = RELOAD_CRON | RELOAD_AT;
339 
340 		/* Sleep until we time out, get a poke, or get a signal. */
341 		nfds = ppoll(pfd, 1, &timeout, mask);
342 		if (nfds == 0)
343 			break;		/* timer expired */
344 		if (nfds == -1 && errno != EINTR)
345 			break;		/* an error occurred */
346 		if (nfds > 0) {
347 			sunlen = sizeof(s_un);
348 			fd = accept(cronSock, (struct sockaddr *)&s_un, &sunlen);
349 			if (fd >= 0 && fcntl(fd, F_SETFL, O_NONBLOCK) == 0) {
350 				(void) read(fd, &poke, 1);
351 				close(fd);
352 				if (poke & RELOAD_CRON) {
353 					database.mtime = 0;
354 					load_database(&database);
355 				}
356 				if (poke & RELOAD_AT) {
357 					/*
358 					 * We run any pending at jobs right
359 					 * away so that "at now" really runs
360 					 * jobs immediately.
361 					 */
362 					clock_gettime(CLOCK_REALTIME, &t2);
363 					at_database.mtime = 0;
364 					if (scan_atjobs(&at_database, &t2))
365 						atrun(&at_database,
366 						    batch_maxload, t2.tv_sec);
367 				}
368 			}
369 		} else {
370 			/* Interrupted by a signal. */
371 			if (got_sighup) {
372 				got_sighup = 0;
373 				log_close();
374 			}
375 			if (got_sigchld) {
376 				got_sigchld = 0;
377 				sigchld_reaper();
378 			}
379 		}
380 
381 		/* Adjust tv and continue where we left off.  */
382 		clock_gettime(CLOCK_REALTIME, &t2);
383 		t2.tv_sec += GMToff;
384 		timespecsub(&t2, &t1, &t1);
385 		timespecsub(&timeout, &t1, &timeout);
386 		memcpy(&t1, &t2, sizeof(t1));
387 		if (timeout.tv_sec < 0)
388 			timeout.tv_sec = 0;
389 		if (timeout.tv_nsec < 0)
390 			timeout.tv_nsec = 0;
391 	}
392 }
393 
394 static void
395 sighup_handler(int x)
396 {
397 	got_sighup = 1;
398 }
399 
400 static void
401 sigchld_handler(int x)
402 {
403 	got_sigchld = 1;
404 }
405 
406 static void
407 sigchld_reaper(void)
408 {
409 	int waiter;
410 	pid_t pid;
411 
412 	do {
413 		pid = waitpid(-1, &waiter, WNOHANG);
414 		switch (pid) {
415 		case -1:
416 			if (errno == EINTR)
417 				continue;
418 			break;
419 		case 0:
420 			break;
421 		default:
422 			break;
423 		}
424 	} while (pid > 0);
425 }
426 
427 static void
428 parse_args(int argc, char *argv[])
429 {
430 	int argch;
431 	char *ep;
432 
433 	while (-1 != (argch = getopt(argc, argv, "l:n"))) {
434 		switch (argch) {
435 		case 'l':
436 			errno = 0;
437 			batch_maxload = strtod(optarg, &ep);
438 			if (*ep != '\0' || ep == optarg || errno == ERANGE ||
439 			    batch_maxload < 0) {
440 				fprintf(stderr, "Illegal load average: %s\n",
441 				    optarg);
442 				usage();
443 			}
444 			break;
445 		case 'n':
446 			NoFork = 1;
447 			break;
448 		default:
449 			usage();
450 		}
451 	}
452 }
453