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