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