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