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