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