xref: /openbsd-src/usr.sbin/cron/cron.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: cron.c,v 1.74 2016/01/11 14:23:50 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 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <sys/stat.h>
23 #include <sys/time.h>
24 #include <sys/un.h>
25 #include <sys/wait.h>
26 
27 #include <bitstring.h>
28 #include <err.h>
29 #include <errno.h>
30 #include <grp.h>
31 #include <locale.h>
32 #include <poll.h>
33 #include <signal.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <syslog.h>
38 #include <time.h>
39 #include <unistd.h>
40 
41 #include "config.h"
42 #include "pathnames.h"
43 #include "macros.h"
44 #include "structs.h"
45 #include "funcs.h"
46 #include "globals.h"
47 
48 enum timejump { negative, small, medium, large };
49 
50 static	void	usage(void),
51 		run_reboot_jobs(cron_db *),
52 		find_jobs(time_t, cron_db *, int, int),
53 		set_time(int),
54 		cron_sleep(time_t, sigset_t *),
55 		sigchld_handler(int),
56 		sigchld_reaper(void),
57 		parse_args(int c, char *v[]);
58 
59 static	int	open_socket(void);
60 
61 static	volatile sig_atomic_t	got_sigchld;
62 static	time_t			timeRunning, virtualTime, clockTime;
63 static	int			cronSock;
64 static	long			GMToff;
65 static	cron_db			*database;
66 static	at_db			*at_database;
67 static	double			batch_maxload = BATCH_MAXLOAD;
68 static	int			NoFork;
69 static	time_t			StartTime;
70 
71 static void
72 usage(void)
73 {
74 
75 	fprintf(stderr, "usage: %s [-n] [-l load_avg]\n", __progname);
76 	exit(EXIT_FAILURE);
77 }
78 
79 int
80 main(int argc, char *argv[])
81 {
82 	struct sigaction sact;
83 	sigset_t blocked, omask;
84 
85 	setlocale(LC_ALL, "");
86 
87 	setvbuf(stdout, NULL, _IOLBF, 0);
88 	setvbuf(stderr, NULL, _IOLBF, 0);
89 
90 	parse_args(argc, argv);
91 
92 	bzero((char *)&sact, sizeof sact);
93 	sigemptyset(&sact.sa_mask);
94 	sact.sa_flags = SA_RESTART;
95 	sact.sa_handler = sigchld_handler;
96 	(void) sigaction(SIGCHLD, &sact, NULL);
97 	sact.sa_handler = SIG_IGN;
98 	(void) sigaction(SIGHUP, &sact, NULL);
99 	(void) sigaction(SIGPIPE, &sact, NULL);
100 
101 	openlog(__progname, LOG_PID, LOG_CRON);
102 
103 	if (pledge("stdio rpath wpath cpath fattr getpw unix id dns proc exec",
104 	    NULL) == -1) {
105 		warn("pledge");
106 		syslog(LOG_ERR, "(CRON) PLEDGE (%m)");
107 		exit(EXIT_FAILURE);
108 	}
109 
110 	cronSock = open_socket();
111 
112 	if (putenv("PATH="_PATH_DEFPATH) < 0) {
113 		warn("putenv");
114 		syslog(LOG_ERR, "(CRON) DEATH (%m)");
115 		exit(EXIT_FAILURE);
116 	}
117 
118 	if (NoFork == 0) {
119 		if (daemon(0, 0) == -1) {
120 			syslog(LOG_ERR, "(CRON) DEATH (%m)");
121 			exit(EXIT_FAILURE);
122 		}
123 		syslog(LOG_INFO, "(CRON) STARTUP (%s)", CRON_VERSION);
124 	}
125 
126 	load_database(&database);
127 	scan_atjobs(&at_database, NULL);
128 	set_time(TRUE);
129 	run_reboot_jobs(database);
130 	timeRunning = virtualTime = clockTime;
131 
132 	/*
133 	 * We block SIGHUP and SIGCHLD while running jobs and receive them
134 	 * only while sleeping in ppoll().  This ensures no signal is lost.
135 	 */
136 	sigemptyset(&blocked);
137 	sigaddset(&blocked, SIGCHLD);
138 	sigaddset(&blocked, SIGHUP);
139 	sigprocmask(SIG_BLOCK, &blocked, &omask);
140 
141 	/*
142 	 * Too many clocks, not enough time (Al. Einstein)
143 	 * These clocks are in minutes since the epoch, adjusted for timezone.
144 	 * virtualTime: is the time it *would* be if we woke up
145 	 * promptly and nobody ever changed the clock. It is
146 	 * monotonically increasing... unless a timejump happens.
147 	 * At the top of the loop, all jobs for 'virtualTime' have run.
148 	 * timeRunning: is the time we last awakened.
149 	 * clockTime: is the time when set_time was last called.
150 	 */
151 	while (TRUE) {
152 		int timeDiff;
153 		enum timejump wakeupKind;
154 
155 		/* ... wait for the time (in minutes) to change ... */
156 		do {
157 			cron_sleep(timeRunning + 1, &omask);
158 			set_time(FALSE);
159 		} while (clockTime == timeRunning);
160 		timeRunning = clockTime;
161 
162 		/*
163 		 * Calculate how the current time differs from our virtual
164 		 * clock.  Classify the change into one of 4 cases.
165 		 */
166 		timeDiff = timeRunning - virtualTime;
167 
168 		/* shortcut for the most common case */
169 		if (timeDiff == 1) {
170 			virtualTime = timeRunning;
171 			find_jobs(virtualTime, database, TRUE, TRUE);
172 		} else {
173 			if (timeDiff > (3*MINUTE_COUNT) ||
174 			    timeDiff < -(3*MINUTE_COUNT))
175 				wakeupKind = large;
176 			else if (timeDiff > 5)
177 				wakeupKind = medium;
178 			else if (timeDiff > 0)
179 				wakeupKind = small;
180 			else
181 				wakeupKind = negative;
182 
183 			switch (wakeupKind) {
184 			case small:
185 				/*
186 				 * case 1: timeDiff is a small positive number
187 				 * (wokeup late) run jobs for each virtual
188 				 * minute until caught up.
189 				 */
190 				do {
191 					if (job_runqueue())
192 						sleep(10);
193 					virtualTime++;
194 					find_jobs(virtualTime, database,
195 					    TRUE, TRUE);
196 				} while (virtualTime < timeRunning);
197 				break;
198 
199 			case medium:
200 				/*
201 				 * case 2: timeDiff is a medium-sized positive
202 				 * number, for example because we went to DST
203 				 * run wildcard jobs once, then run any
204 				 * fixed-time jobs that would otherwise be
205 				 * skipped if we use up our minute (possible,
206 				 * if there are a lot of jobs to run) go
207 				 * around the loop again so that wildcard jobs
208 				 * have a chance to run, and we do our
209 				 * housekeeping.
210 				 */
211 				/* run wildcard jobs for current minute */
212 				find_jobs(timeRunning, database, TRUE, FALSE);
213 
214 				/* run fixed-time jobs for each minute missed */
215 				do {
216 					if (job_runqueue())
217 						sleep(10);
218 					virtualTime++;
219 					find_jobs(virtualTime, database,
220 					    FALSE, TRUE);
221 					set_time(FALSE);
222 				} while (virtualTime< timeRunning &&
223 				    clockTime == timeRunning);
224 				break;
225 
226 			case negative:
227 				/*
228 				 * case 3: timeDiff is a small or medium-sized
229 				 * negative num, eg. because of DST ending.
230 				 * Just run the wildcard jobs. The fixed-time
231 				 * jobs probably have already run, and should
232 				 * not be repeated.  Virtual time does not
233 				 * change until we are caught up.
234 				 */
235 				find_jobs(timeRunning, database, TRUE, FALSE);
236 				break;
237 			default:
238 				/*
239 				 * other: time has changed a *lot*,
240 				 * jump virtual time, and run everything
241 				 */
242 				virtualTime = timeRunning;
243 				find_jobs(timeRunning, database, TRUE, TRUE);
244 			}
245 		}
246 
247 		/* Jobs to be run (if any) are loaded; clear the queue. */
248 		job_runqueue();
249 
250 		/* Run any jobs in the at queue. */
251 		atrun(at_database, batch_maxload,
252 		    timeRunning * SECONDS_PER_MINUTE - GMToff);
253 
254 		/* Reload jobs as needed. */
255 		load_database(&database);
256 		scan_atjobs(&at_database, NULL);
257 	}
258 }
259 
260 static void
261 run_reboot_jobs(cron_db *db)
262 {
263 	user *u;
264 	entry *e;
265 
266 	TAILQ_FOREACH(u, &db->users, entries) {
267 		SLIST_FOREACH(e, &u->crontab, entries) {
268 			if (e->flags & WHEN_REBOOT)
269 				job_add(e, u);
270 		}
271 	}
272 	(void) job_runqueue();
273 }
274 
275 static void
276 find_jobs(time_t vtime, cron_db *db, int doWild, int doNonWild)
277 {
278 	time_t virtualSecond  = vtime * SECONDS_PER_MINUTE;
279 	struct tm *tm = gmtime(&virtualSecond);
280 	int minute, hour, dom, month, dow;
281 	user *u;
282 	entry *e;
283 
284 	/* make 0-based values out of these so we can use them as indices
285 	 */
286 	minute = tm->tm_min -FIRST_MINUTE;
287 	hour = tm->tm_hour -FIRST_HOUR;
288 	dom = tm->tm_mday -FIRST_DOM;
289 	month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
290 	dow = tm->tm_wday -FIRST_DOW;
291 
292 	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
293 	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
294 	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
295 	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
296 	 * like many bizarre things, it's the standard.
297 	 */
298 	TAILQ_FOREACH(u, &db->users, entries) {
299 		SLIST_FOREACH(e, &u->crontab, entries) {
300 			if (bit_test(e->minute, minute) &&
301 			    bit_test(e->hour, hour) &&
302 			    bit_test(e->month, month) &&
303 			    ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
304 			      ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
305 			      : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
306 			    )
307 			   ) {
308 				if ((doNonWild &&
309 				    !(e->flags & (MIN_STAR|HR_STAR))) ||
310 				    (doWild && (e->flags & (MIN_STAR|HR_STAR))))
311 					job_add(e, u);
312 			}
313 		}
314 	}
315 }
316 
317 /*
318  * Set StartTime and clockTime to the current time.
319  * These are used for computing what time it really is right now.
320  * Note that clockTime is a unix wallclock time converted to minutes.
321  */
322 static void
323 set_time(int initialize)
324 {
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, sigset_t *mask)
344 {
345 	int fd, nfds;
346 	unsigned char poke;
347 	struct timespec t1, t2, timeout;
348 	struct sockaddr_un s_un;
349 	socklen_t sunlen;
350 	static struct pollfd pfd[1];
351 
352 	clock_gettime(CLOCK_REALTIME, &t1);
353 	t1.tv_sec += GMToff;
354 	timeout.tv_sec = (target * SECONDS_PER_MINUTE - t1.tv_sec) + 1;
355 	timeout.tv_nsec = 0;
356 
357 	pfd[0].fd = cronSock;
358 	pfd[0].events = POLLIN;
359 
360 	while (timespecisset(&timeout) && timeout.tv_sec < 65) {
361 		poke = RELOAD_CRON | RELOAD_AT;
362 
363 		/* Sleep until we time out, get a poke, or get a signal. */
364 		nfds = ppoll(pfd, 1, &timeout, mask);
365 		if (nfds == 0)
366 			break;		/* timer expired */
367 		if (nfds == -1 && errno != EINTR)
368 			break;		/* an error occurred */
369 		if (nfds > 0) {
370 			sunlen = sizeof(s_un);
371 			fd = accept4(cronSock, (struct sockaddr *)&s_un,
372 			    &sunlen, SOCK_NONBLOCK);
373 			if (fd >= 0) {
374 				(void) read(fd, &poke, 1);
375 				close(fd);
376 				if (poke & RELOAD_CRON) {
377 					timespecclear(&database->mtime);
378 					load_database(&database);
379 				}
380 				if (poke & RELOAD_AT) {
381 					/*
382 					 * We run any pending at jobs right
383 					 * away so that "at now" really runs
384 					 * jobs immediately.
385 					 */
386 					clock_gettime(CLOCK_REALTIME, &t2);
387 					timespecclear(&at_database->mtime);
388 					if (scan_atjobs(&at_database, &t2))
389 						atrun(at_database,
390 						    batch_maxload, t2.tv_sec);
391 				}
392 			}
393 		} else {
394 			/* Interrupted by a signal. */
395 			if (got_sigchld) {
396 				got_sigchld = 0;
397 				sigchld_reaper();
398 			}
399 		}
400 
401 		/* Adjust tv and continue where we left off.  */
402 		clock_gettime(CLOCK_REALTIME, &t2);
403 		t2.tv_sec += GMToff;
404 		timespecsub(&t2, &t1, &t1);
405 		timespecsub(&timeout, &t1, &timeout);
406 		memcpy(&t1, &t2, sizeof(t1));
407 		if (timeout.tv_sec < 0)
408 			timeout.tv_sec = 0;
409 		if (timeout.tv_nsec < 0)
410 			timeout.tv_nsec = 0;
411 	}
412 }
413 
414 /* int open_socket(void)
415  *	opens a UNIX domain socket that crontab uses to poke cron.
416  *	If the socket is already in use, return an error.
417  */
418 static int
419 open_socket(void)
420 {
421 	int		   sock, rc;
422 	mode_t		   omask;
423 	struct group *grp;
424 	struct sockaddr_un s_un;
425 
426 	if ((grp = getgrnam(CRON_GROUP)) == NULL)
427 		syslog(LOG_WARNING, "(CRON) STARTUP (can't find cron group)");
428 
429 	sock = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
430 	if (sock == -1) {
431 		warn("socket");
432 		syslog(LOG_ERR, "(CRON) DEATH (can't create socket)");
433 		exit(EXIT_FAILURE);
434 	}
435 	bzero(&s_un, sizeof(s_un));
436 	if (strlcpy(s_un.sun_path, _PATH_CRON_SOCK, sizeof(s_un.sun_path))
437 	    >= sizeof(s_un.sun_path)) {
438 		warnc(ENAMETOOLONG, _PATH_CRON_SOCK);
439 		syslog(LOG_ERR, "(CRON) DEATH (socket path too long)");
440 		exit(EXIT_FAILURE);
441 	}
442 	s_un.sun_family = AF_UNIX;
443 
444 	if (connect(sock, (struct sockaddr *)&s_un, sizeof(s_un)) == 0) {
445 		warnx("already running");
446 		syslog(LOG_ERR, "(CRON) DEATH (already running)");
447 		exit(EXIT_FAILURE);
448 	}
449 	if (errno != ENOENT)
450 		unlink(s_un.sun_path);
451 
452 	omask = umask(007);
453 	rc = bind(sock, (struct sockaddr *)&s_un, sizeof(s_un));
454 	umask(omask);
455 	if (rc != 0) {
456 		warn("bind");
457 		syslog(LOG_ERR, "(CRON) DEATH (can't bind socket)");
458 		exit(EXIT_FAILURE);
459 	}
460 	if (listen(sock, SOMAXCONN)) {
461 		warn("listen");
462 		syslog(LOG_ERR, "(CRON) DEATH (can't listen on socket)");
463 		exit(EXIT_FAILURE);
464 	}
465 	chmod(s_un.sun_path, 0660);
466 	if (grp != NULL) {
467 		/* pledge won't let us change files to a foreign group. */
468 		if (setegid(grp->gr_gid) == 0) {
469 			chown(s_un.sun_path, -1, grp->gr_gid);
470 			(void)setegid(getgid());
471 		}
472 	}
473 
474 	return(sock);
475 }
476 
477 static void
478 sigchld_handler(int x)
479 {
480 	got_sigchld = 1;
481 }
482 
483 static void
484 sigchld_reaper(void)
485 {
486 	int waiter;
487 	pid_t pid;
488 
489 	do {
490 		pid = waitpid(-1, &waiter, WNOHANG);
491 		switch (pid) {
492 		case -1:
493 			if (errno == EINTR)
494 				continue;
495 			break;
496 		case 0:
497 			break;
498 		default:
499 			break;
500 		}
501 	} while (pid > 0);
502 }
503 
504 static void
505 parse_args(int argc, char *argv[])
506 {
507 	int argch;
508 	char *ep;
509 
510 	while (-1 != (argch = getopt(argc, argv, "l:n"))) {
511 		switch (argch) {
512 		case 'l':
513 			errno = 0;
514 			batch_maxload = strtod(optarg, &ep);
515 			if (*ep != '\0' || ep == optarg || errno == ERANGE ||
516 			    batch_maxload < 0) {
517 				warnx("illegal load average: %s", optarg);
518 				usage();
519 			}
520 			break;
521 		case 'n':
522 			NoFork = 1;
523 			break;
524 		default:
525 			usage();
526 		}
527 	}
528 }
529