1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
2 * All rights reserved
3 *
4 * Distribute freely, except: don't remove my name from the source or
5 * documentation (don't take credit for my work), mark your changes (don't
6 * get me blamed for your possible bugs), don't alter or remove this
7 * notice. May be sold if buildable source is provided to buyer. No
8 * warrantee of any kind, express or implied, is included with this
9 * software; use at your own risk, responsibility for damages (if any) to
10 * anyone resulting from the use of this software rests entirely with the
11 * user.
12 *
13 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
14 * I'll try to keep a version up to date. I can be reached as follows:
15 * Paul Vixie <[email protected]> uunet!decwrl!vixie!paul
16 */
17
18 #if !defined(lint) && !defined(LINT)
19 static const char rcsid[] =
20 "$FreeBSD$";
21 #endif
22
23 #define MAIN_PROGRAM
24
25
26 #include "cron.h"
27 #include <sys/mman.h>
28 #include <sys/signal.h>
29 #if SYS_TIME_H
30 # include <sys/time.h>
31 #else
32 # include <time.h>
33 #endif
34
35
36 static void usage(void),
37 run_reboot_jobs(cron_db *),
38 cron_tick(cron_db *, int),
39 cron_sync(int),
40 cron_sleep(cron_db *, int),
41 cron_clean(cron_db *),
42 #ifdef USE_SIGCHLD
43 sigchld_handler(int),
44 #endif
45 sighup_handler(int),
46 parse_args(int c, char *v[]);
47
48 static int run_at_secres(cron_db *);
49 static void find_interval_entry(pid_t);
50
51 static cron_db database;
52 static time_t last_time = 0;
53 static int dst_enabled = 0;
54 static int dont_daemonize = 0;
55 struct pidfh *pfh;
56
57 static void
usage()58 usage() {
59 #if DEBUGGING
60 char **dflags;
61 #endif
62
63 fprintf(stderr, "usage: cron [-j jitter] [-J rootjitter] "
64 "[-m mailto] [-n] [-s] [-o] [-x debugflag[,...]]\n");
65 #if DEBUGGING
66 fprintf(stderr, "\ndebugflags: ");
67
68 for(dflags = DebugFlagNames; *dflags; dflags++) {
69 fprintf(stderr, "%s ", *dflags);
70 }
71 fprintf(stderr, "\n");
72 #endif
73
74 exit(ERROR_EXIT);
75 }
76
77 static void
open_pidfile(void)78 open_pidfile(void)
79 {
80 char pidfile[MAX_FNAME];
81 char buf[MAX_TEMPSTR];
82 int otherpid;
83
84 (void) snprintf(pidfile, sizeof(pidfile), PIDFILE, PIDDIR);
85 pfh = pidfile_open(pidfile, 0600, &otherpid);
86 if (pfh == NULL) {
87 if (errno == EEXIST) {
88 snprintf(buf, sizeof(buf),
89 "cron already running, pid: %d", otherpid);
90 } else {
91 snprintf(buf, sizeof(buf),
92 "can't open or create %s: %s", pidfile,
93 strerror(errno));
94 }
95 log_it("CRON", getpid(), "DEATH", buf);
96 errx(ERROR_EXIT, "%s", buf);
97 }
98 }
99
100 int
main(argc,argv)101 main(argc, argv)
102 int argc;
103 char *argv[];
104 {
105 int runnum;
106 int secres1, secres2;
107 struct tm *tm;
108
109 ProgramName = argv[0];
110
111 #if defined(BSD)
112 setlinebuf(stdout);
113 setlinebuf(stderr);
114 #endif
115
116 parse_args(argc, argv);
117
118 #ifdef USE_SIGCHLD
119 (void) signal(SIGCHLD, sigchld_handler);
120 #else
121 (void) signal(SIGCLD, SIG_IGN);
122 #endif
123 (void) signal(SIGHUP, sighup_handler);
124
125 open_pidfile();
126 set_cron_uid();
127 set_cron_cwd();
128
129 #if defined(POSIX)
130 setenv("PATH", _PATH_DEFPATH, 1);
131 #endif
132
133 /* if there are no debug flags turned on, fork as a daemon should.
134 */
135 # if DEBUGGING
136 if (DebugFlags) {
137 # else
138 if (0) {
139 # endif
140 (void) fprintf(stderr, "[%d] cron started\n", getpid());
141 } else if (dont_daemonize == 0) {
142 if (daemon(1, 0) == -1) {
143 pidfile_remove(pfh);
144 log_it("CRON",getpid(),"DEATH","can't become daemon");
145 exit(0);
146 }
147 }
148
149 if (madvise(NULL, 0, MADV_PROTECT) != 0)
150 log_it("CRON", getpid(), "WARNING", "madvise() failed");
151
152 pidfile_write(pfh);
153 database.head = NULL;
154 database.tail = NULL;
155 database.mtime = (time_t) 0;
156 load_database(&database);
157 secres1 = secres2 = run_at_secres(&database);
158 cron_sync(secres1);
159 run_reboot_jobs(&database);
160 runnum = 0;
161 while (TRUE) {
162 # if DEBUGGING
163 /* if (!(DebugFlags & DTEST)) */
164 # endif /*DEBUGGING*/
165 cron_sleep(&database, secres1);
166
167 if (secres1 == 0 || runnum % 60 == 0) {
168 load_database(&database);
169 secres2 = run_at_secres(&database);
170 if (secres2 != secres1) {
171 secres1 = secres2;
172 if (secres1 != 0) {
173 runnum = 0;
174 } else {
175 /*
176 * Going from 1 sec to 60 sec res. If we
177 * are already at minute's boundary, so
178 * let it run, otherwise schedule for the
179 * next minute.
180 */
181 tm = localtime(&TargetTime);
182 if (tm->tm_sec > 0) {
183 cron_sync(secres2);
184 continue;
185 }
186 }
187 }
188 }
189
190 /* do this iteration
191 */
192 cron_tick(&database, secres1);
193
194 /* sleep 1 or 60 seconds
195 */
196 TargetTime += (secres1 != 0) ? 1 : 60;
197 runnum += 1;
198 }
199 }
200
201
202 static void
run_reboot_jobs(db)203 run_reboot_jobs(db)
204 cron_db *db;
205 {
206 register user *u;
207 register entry *e;
208
209 for (u = db->head; u != NULL; u = u->next) {
210 for (e = u->crontab; e != NULL; e = e->next) {
211 if (e->flags & WHEN_REBOOT) {
212 job_add(e, u);
213 }
214 if (e->flags & INTERVAL) {
215 e->lastexit = TargetTime;
216 }
217 }
218 }
219 (void) job_runqueue();
220 }
221
222
223 static void
cron_tick(cron_db * db,int secres)224 cron_tick(cron_db *db, int secres)
225 {
226 static struct tm lasttm;
227 static time_t diff = 0, /* time difference in seconds from the last offset change */
228 difflimit = 0; /* end point for the time zone correction */
229 struct tm otztm; /* time in the old time zone */
230 int otzsecond, otzminute, otzhour, otzdom, otzmonth, otzdow;
231 register struct tm *tm = localtime(&TargetTime);
232 register int second, minute, hour, dom, month, dow;
233 register user *u;
234 register entry *e;
235
236 /* make 0-based values out of these so we can use them as indices
237 */
238 second = (secres == 0) ? 0 : tm->tm_sec -FIRST_SECOND;
239 minute = tm->tm_min -FIRST_MINUTE;
240 hour = tm->tm_hour -FIRST_HOUR;
241 dom = tm->tm_mday -FIRST_DOM;
242 month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
243 dow = tm->tm_wday -FIRST_DOW;
244
245 Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d,%d)\n",
246 getpid(), second, minute, hour, dom, month, dow))
247
248 if (dst_enabled && last_time != 0
249 && TargetTime > last_time /* exclude stepping back */
250 && tm->tm_gmtoff != lasttm.tm_gmtoff ) {
251
252 diff = tm->tm_gmtoff - lasttm.tm_gmtoff;
253
254 if ( diff > 0 ) { /* ST->DST */
255 /* mark jobs for an earlier run */
256 difflimit = TargetTime + diff;
257 for (u = db->head; u != NULL; u = u->next) {
258 for (e = u->crontab; e != NULL; e = e->next) {
259 e->flags &= ~NOT_UNTIL;
260 if ( e->lastrun >= TargetTime )
261 e->lastrun = 0;
262 /* not include the ends of hourly ranges */
263 if ( e->lastrun < TargetTime - 3600 )
264 e->flags |= RUN_AT;
265 else
266 e->flags &= ~RUN_AT;
267 }
268 }
269 } else { /* diff < 0 : DST->ST */
270 /* mark jobs for skipping */
271 difflimit = TargetTime - diff;
272 for (u = db->head; u != NULL; u = u->next) {
273 for (e = u->crontab; e != NULL; e = e->next) {
274 e->flags |= NOT_UNTIL;
275 e->flags &= ~RUN_AT;
276 }
277 }
278 }
279 }
280
281 if (diff != 0) {
282 /* if the time was reset of the end of special zone is reached */
283 if (last_time == 0 || TargetTime >= difflimit) {
284 /* disable the TZ switch checks */
285 diff = 0;
286 difflimit = 0;
287 for (u = db->head; u != NULL; u = u->next) {
288 for (e = u->crontab; e != NULL; e = e->next) {
289 e->flags &= ~(RUN_AT|NOT_UNTIL);
290 }
291 }
292 } else {
293 /* get the time in the old time zone */
294 time_t difftime = TargetTime + tm->tm_gmtoff - diff;
295 gmtime_r(&difftime, &otztm);
296
297 /* make 0-based values out of these so we can use them as indices
298 */
299 otzsecond = (secres == 0) ? 0 : otztm.tm_sec -FIRST_SECOND;
300 otzminute = otztm.tm_min -FIRST_MINUTE;
301 otzhour = otztm.tm_hour -FIRST_HOUR;
302 otzdom = otztm.tm_mday -FIRST_DOM;
303 otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
304 otzdow = otztm.tm_wday -FIRST_DOW;
305 }
306 }
307
308 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
309 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
310 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
311 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre.
312 * like many bizarre things, it's the standard.
313 */
314 for (u = db->head; u != NULL; u = u->next) {
315 for (e = u->crontab; e != NULL; e = e->next) {
316 Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n",
317 env_get("LOGNAME", e->envp),
318 e->uid, e->gid, e->cmd))
319
320 if (e->flags & INTERVAL) {
321 if (e->lastexit > 0 &&
322 TargetTime >= e->lastexit + e->interval)
323 job_add(e, u);
324 continue;
325 }
326
327 if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) {
328 if (bit_test(e->second, otzsecond)
329 && bit_test(e->minute, otzminute)
330 && bit_test(e->hour, otzhour)
331 && bit_test(e->month, otzmonth)
332 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
333 ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom))
334 : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom))
335 )
336 ) {
337 if ( e->flags & RUN_AT ) {
338 e->flags &= ~RUN_AT;
339 e->lastrun = TargetTime;
340 job_add(e, u);
341 continue;
342 } else
343 e->flags &= ~NOT_UNTIL;
344 } else if ( e->flags & NOT_UNTIL )
345 continue;
346 }
347
348 if (bit_test(e->second, second)
349 && bit_test(e->minute, minute)
350 && bit_test(e->hour, hour)
351 && bit_test(e->month, month)
352 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
353 ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
354 : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
355 )
356 ) {
357 e->flags &= ~RUN_AT;
358 e->lastrun = TargetTime;
359 job_add(e, u);
360 }
361 }
362 }
363
364 last_time = TargetTime;
365 lasttm = *tm;
366 }
367
368
369 /* the task here is to figure out how long it's going to be until :00 of the
370 * following minute and initialize TargetTime to this value. TargetTime
371 * will subsequently slide 60 seconds at a time, with correction applied
372 * implicitly in cron_sleep(). it would be nice to let cron execute in
373 * the "current minute" before going to sleep, but by restarting cron you
374 * could then get it to execute a given minute's jobs more than once.
375 * instead we have the chance of missing a minute's jobs completely, but
376 * that's something sysadmin's know to expect what with crashing computers..
377 */
378 static void
cron_sync(int secres)379 cron_sync(int secres) {
380 struct tm *tm;
381
382 TargetTime = time((time_t*)0);
383 if (secres != 0) {
384 TargetTime += 1;
385 } else {
386 tm = localtime(&TargetTime);
387 TargetTime += (60 - tm->tm_sec);
388 }
389 }
390
391 static void
timespec_subtract(struct timespec * result,struct timespec * x,struct timespec * y)392 timespec_subtract(struct timespec *result, struct timespec *x,
393 struct timespec *y)
394 {
395 *result = *x;
396 result->tv_sec -= y->tv_sec;
397 result->tv_nsec -= y->tv_nsec;
398 if (result->tv_nsec < 0) {
399 result->tv_sec--;
400 result->tv_nsec += 1000000000;
401 }
402 }
403
404 static void
cron_sleep(cron_db * db,int secres)405 cron_sleep(cron_db *db, int secres)
406 {
407 int seconds_to_wait;
408 int rval;
409 struct timespec ctime, ttime, stime, remtime;
410
411 /*
412 * Loop until we reach the top of the next minute, sleep when possible.
413 */
414
415 for (;;) {
416 clock_gettime(CLOCK_REALTIME, &ctime);
417 ttime.tv_sec = TargetTime;
418 ttime.tv_nsec = 0;
419 timespec_subtract(&stime, &ttime, &ctime);
420
421 /*
422 * If the seconds_to_wait value is insane, jump the cron
423 */
424
425 if (stime.tv_sec < -600 || stime.tv_sec > 600) {
426 cron_clean(db);
427 cron_sync(secres);
428 continue;
429 }
430
431 seconds_to_wait = (stime.tv_nsec > 0) ? stime.tv_sec + 1 :
432 stime.tv_sec;
433
434 Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
435 getpid(), (long)TargetTime, seconds_to_wait))
436
437 /*
438 * If we've run out of wait time or there are no jobs left
439 * to run, break
440 */
441
442 if (stime.tv_sec < 0)
443 break;
444 if (job_runqueue() == 0) {
445 Debug(DSCH, ("[%d] sleeping for %d seconds\n",
446 getpid(), seconds_to_wait))
447
448 for (;;) {
449 rval = nanosleep(&stime, &remtime);
450 if (rval == 0 || errno != EINTR)
451 break;
452 stime.tv_sec = remtime.tv_sec;
453 stime.tv_nsec = remtime.tv_nsec;
454 }
455 }
456 }
457 }
458
459
460 /* if the time was changed abruptly, clear the flags related
461 * to the daylight time switch handling to avoid strange effects
462 */
463
464 static void
cron_clean(db)465 cron_clean(db)
466 cron_db *db;
467 {
468 user *u;
469 entry *e;
470
471 last_time = 0;
472
473 for (u = db->head; u != NULL; u = u->next) {
474 for (e = u->crontab; e != NULL; e = e->next) {
475 e->flags &= ~(RUN_AT|NOT_UNTIL);
476 }
477 }
478 }
479
480 #ifdef USE_SIGCHLD
481 static void
sigchld_handler(int x)482 sigchld_handler(int x)
483 {
484 WAIT_T waiter;
485 PID_T pid;
486
487 for (;;) {
488 #ifdef POSIX
489 pid = waitpid(-1, &waiter, WNOHANG);
490 #else
491 pid = wait3(&waiter, WNOHANG, (struct rusage *)0);
492 #endif
493 switch (pid) {
494 case -1:
495 Debug(DPROC,
496 ("[%d] sigchld...no children\n", getpid()))
497 return;
498 case 0:
499 Debug(DPROC,
500 ("[%d] sigchld...no dead kids\n", getpid()))
501 return;
502 default:
503 find_interval_entry(pid);
504 Debug(DPROC,
505 ("[%d] sigchld...pid #%d died, stat=%d\n",
506 getpid(), pid, WEXITSTATUS(waiter)))
507 }
508 }
509 }
510 #endif /*USE_SIGCHLD*/
511
512
513 static void
sighup_handler(int x)514 sighup_handler(int x)
515 {
516 log_close();
517 }
518
519
520 static void
parse_args(argc,argv)521 parse_args(argc, argv)
522 int argc;
523 char *argv[];
524 {
525 int argch;
526 char *endp;
527
528 while ((argch = getopt(argc, argv, "j:J:m:nosx:")) != -1) {
529 switch (argch) {
530 case 'j':
531 Jitter = strtoul(optarg, &endp, 10);
532 if (*optarg == '\0' || *endp != '\0' || Jitter > 60)
533 errx(ERROR_EXIT,
534 "bad value for jitter: %s", optarg);
535 break;
536 case 'J':
537 RootJitter = strtoul(optarg, &endp, 10);
538 if (*optarg == '\0' || *endp != '\0' || RootJitter > 60)
539 errx(ERROR_EXIT,
540 "bad value for root jitter: %s", optarg);
541 break;
542 case 'm':
543 defmailto = optarg;
544 break;
545 case 'n':
546 dont_daemonize = 1;
547 break;
548 case 'o':
549 dst_enabled = 0;
550 break;
551 case 's':
552 dst_enabled = 1;
553 break;
554 case 'x':
555 if (!set_debug_flags(optarg))
556 usage();
557 break;
558 default:
559 usage();
560 }
561 }
562 }
563
564 static int
run_at_secres(cron_db * db)565 run_at_secres(cron_db *db)
566 {
567 user *u;
568 entry *e;
569
570 for (u = db->head; u != NULL; u = u->next) {
571 for (e = u->crontab; e != NULL; e = e->next) {
572 if ((e->flags & (SEC_RES | INTERVAL)) != 0)
573 return 1;
574 }
575 }
576 return 0;
577 }
578
579 static void
find_interval_entry(pid_t pid)580 find_interval_entry(pid_t pid)
581 {
582 user *u;
583 entry *e;
584
585 for (u = database.head; u != NULL; u = u->next) {
586 for (e = u->crontab; e != NULL; e = e->next) {
587 if ((e->flags & INTERVAL) && e->child == pid) {
588 e->lastexit = time(NULL);
589 e->child = 0;
590 break;
591 }
592 }
593 }
594 }
595