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