1 /* $OpenBSD: cron.c,v 1.43 2011/08/22 19:32:42 millert 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(EXIT_FAILURE); 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 98 acquire_daemonlock(0); 99 set_cron_uid(); 100 set_cron_cwd(); 101 102 if (putenv("PATH="_PATH_DEFPATH) < 0) { 103 log_it("CRON", getpid(), "DEATH", "can't malloc"); 104 exit(EXIT_FAILURE); 105 } 106 107 /* if there are no debug flags turned on, fork as a daemon should. 108 */ 109 if (DebugFlags) { 110 #if DEBUGGING 111 (void) fprintf(stderr, "[%ld] cron started\n", (long)getpid()); 112 #endif 113 } else if (NoFork == 0) { 114 switch (fork()) { 115 case -1: 116 log_it("CRON",getpid(),"DEATH","can't fork"); 117 exit(EXIT_FAILURE); 118 break; 119 case 0: 120 /* child process */ 121 (void) setsid(); 122 if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) >= 0) { 123 (void) dup2(fd, STDIN_FILENO); 124 (void) dup2(fd, STDOUT_FILENO); 125 (void) dup2(fd, STDERR_FILENO); 126 if (fd != STDERR_FILENO) 127 (void) close(fd); 128 } 129 log_it("CRON",getpid(),"STARTUP",CRON_VERSION); 130 break; 131 default: 132 /* parent process should just die */ 133 _exit(EXIT_SUCCESS); 134 } 135 } 136 137 acquire_daemonlock(0); 138 cronSock = open_socket(); 139 database.head = NULL; 140 database.tail = NULL; 141 database.mtime = (time_t) 0; 142 load_database(&database); 143 at_database.head = NULL; 144 at_database.tail = NULL; 145 at_database.mtime = (time_t) 0; 146 scan_atjobs(&at_database, NULL); 147 set_time(TRUE); 148 run_reboot_jobs(&database); 149 timeRunning = virtualTime = clockTime; 150 151 /* 152 * Too many clocks, not enough time (Al. Einstein) 153 * These clocks are in minutes since the epoch, adjusted for timezone. 154 * virtualTime: is the time it *would* be if we woke up 155 * promptly and nobody ever changed the clock. It is 156 * monotonically increasing... unless a timejump happens. 157 * At the top of the loop, all jobs for 'virtualTime' have run. 158 * timeRunning: is the time we last awakened. 159 * clockTime: is the time when set_time was last called. 160 */ 161 while (TRUE) { 162 int timeDiff; 163 enum timejump wakeupKind; 164 165 /* ... wait for the time (in minutes) to change ... */ 166 do { 167 cron_sleep(timeRunning + 1); 168 set_time(FALSE); 169 } while (clockTime == timeRunning); 170 timeRunning = clockTime; 171 172 /* 173 * Calculate how the current time differs from our virtual 174 * clock. Classify the change into one of 4 cases. 175 */ 176 timeDiff = timeRunning - virtualTime; 177 178 /* shortcut for the most common case */ 179 if (timeDiff == 1) { 180 virtualTime = timeRunning; 181 find_jobs(virtualTime, &database, TRUE, TRUE); 182 } else { 183 if (timeDiff > (3*MINUTE_COUNT) || 184 timeDiff < -(3*MINUTE_COUNT)) 185 wakeupKind = large; 186 else if (timeDiff > 5) 187 wakeupKind = medium; 188 else if (timeDiff > 0) 189 wakeupKind = small; 190 else 191 wakeupKind = negative; 192 193 switch (wakeupKind) { 194 case small: 195 /* 196 * case 1: timeDiff is a small positive number 197 * (wokeup late) run jobs for each virtual 198 * minute until caught up. 199 */ 200 Debug(DSCH, ("[%ld], normal case %d minutes to go\n", 201 (long)getpid(), timeDiff)) 202 do { 203 if (job_runqueue()) 204 sleep(10); 205 virtualTime++; 206 find_jobs(virtualTime, &database, 207 TRUE, TRUE); 208 } while (virtualTime < timeRunning); 209 break; 210 211 case medium: 212 /* 213 * case 2: timeDiff is a medium-sized positive 214 * number, for example because we went to DST 215 * run wildcard jobs once, then run any 216 * fixed-time jobs that would otherwise be 217 * skipped if we use up our minute (possible, 218 * if there are a lot of jobs to run) go 219 * around the loop again so that wildcard jobs 220 * have a chance to run, and we do our 221 * housekeeping. 222 */ 223 Debug(DSCH, ("[%ld], DST begins %d minutes to go\n", 224 (long)getpid(), timeDiff)) 225 /* run wildcard jobs for current minute */ 226 find_jobs(timeRunning, &database, TRUE, FALSE); 227 228 /* run fixed-time jobs for each minute missed */ 229 do { 230 if (job_runqueue()) 231 sleep(10); 232 virtualTime++; 233 find_jobs(virtualTime, &database, 234 FALSE, TRUE); 235 set_time(FALSE); 236 } while (virtualTime< timeRunning && 237 clockTime == timeRunning); 238 break; 239 240 case negative: 241 /* 242 * case 3: timeDiff is a small or medium-sized 243 * negative num, eg. because of DST ending. 244 * Just run the wildcard jobs. The fixed-time 245 * jobs probably have already run, and should 246 * not be repeated. Virtual time does not 247 * change until we are caught up. 248 */ 249 Debug(DSCH, ("[%ld], DST ends %d minutes to go\n", 250 (long)getpid(), timeDiff)) 251 find_jobs(timeRunning, &database, TRUE, FALSE); 252 break; 253 default: 254 /* 255 * other: time has changed a *lot*, 256 * jump virtual time, and run everything 257 */ 258 Debug(DSCH, ("[%ld], clock jumped\n", 259 (long)getpid())) 260 virtualTime = timeRunning; 261 find_jobs(timeRunning, &database, TRUE, TRUE); 262 } 263 } 264 265 /* Jobs to be run (if any) are loaded; clear the queue. */ 266 job_runqueue(); 267 268 /* Run any jobs in the at queue. */ 269 atrun(&at_database, batch_maxload, 270 timeRunning * SECONDS_PER_MINUTE - GMToff); 271 272 /* Check to see if we received a signal while running jobs. */ 273 if (got_sighup) { 274 got_sighup = 0; 275 log_close(); 276 } 277 if (got_sigchld) { 278 got_sigchld = 0; 279 sigchld_reaper(); 280 } 281 load_database(&database); 282 scan_atjobs(&at_database, NULL); 283 } 284 } 285 286 static void 287 run_reboot_jobs(cron_db *db) { 288 user *u; 289 entry *e; 290 291 for (u = db->head; u != NULL; u = u->next) { 292 for (e = u->crontab; e != NULL; e = e->next) { 293 if (e->flags & WHEN_REBOOT) 294 job_add(e, u); 295 } 296 } 297 (void) job_runqueue(); 298 } 299 300 static void 301 find_jobs(int vtime, cron_db *db, int doWild, int doNonWild) { 302 time_t virtualSecond = vtime * SECONDS_PER_MINUTE; 303 struct tm *tm = gmtime(&virtualSecond); 304 int minute, hour, dom, month, dow; 305 user *u; 306 entry *e; 307 308 /* make 0-based values out of these so we can use them as indices 309 */ 310 minute = tm->tm_min -FIRST_MINUTE; 311 hour = tm->tm_hour -FIRST_HOUR; 312 dom = tm->tm_mday -FIRST_DOM; 313 month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 314 dow = tm->tm_wday -FIRST_DOW; 315 316 Debug(DSCH, ("[%ld] tick(%d,%d,%d,%d,%d) %s %s\n", 317 (long)getpid(), minute, hour, dom, month, dow, 318 doWild?" ":"No wildcard",doNonWild?" ":"Wildcard only")) 319 320 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the 321 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* 322 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this 323 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. 324 * like many bizarre things, it's the standard. 325 */ 326 for (u = db->head; u != NULL; u = u->next) { 327 for (e = u->crontab; e != NULL; e = e->next) { 328 Debug(DSCH|DEXT, ("user [%s:%lu:%lu:...] cmd=\"%s\"\n", 329 e->pwd->pw_name, (unsigned long)e->pwd->pw_uid, 330 (unsigned long)e->pwd->pw_gid, e->cmd)) 331 if (bit_test(e->minute, minute) && 332 bit_test(e->hour, hour) && 333 bit_test(e->month, month) && 334 ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 335 ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) 336 : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) 337 ) 338 ) { 339 if ((doNonWild && 340 !(e->flags & (MIN_STAR|HR_STAR))) || 341 (doWild && (e->flags & (MIN_STAR|HR_STAR)))) 342 job_add(e, u); 343 } 344 } 345 } 346 } 347 348 /* 349 * Set StartTime and clockTime to the current time. 350 * These are used for computing what time it really is right now. 351 * Note that clockTime is a unix wallclock time converted to minutes. 352 */ 353 static void 354 set_time(int initialize) { 355 struct tm tm; 356 static int isdst; 357 358 StartTime = time(NULL); 359 360 /* We adjust the time to GMT so we can catch DST changes. */ 361 tm = *localtime(&StartTime); 362 if (initialize || tm.tm_isdst != isdst) { 363 isdst = tm.tm_isdst; 364 GMToff = get_gmtoff(&StartTime, &tm); 365 Debug(DSCH, ("[%ld] GMToff=%ld\n", 366 (long)getpid(), (long)GMToff)) 367 } 368 clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE; 369 } 370 371 /* 372 * Try to just hit the next minute. 373 */ 374 static void 375 cron_sleep(int target) { 376 int fd, nfds; 377 unsigned char poke; 378 struct timeval t1, t2, tv; 379 struct sockaddr_un s_un; 380 socklen_t sunlen; 381 static fd_set *fdsr; 382 383 gettimeofday(&t1, NULL); 384 t1.tv_sec += GMToff; 385 tv.tv_sec = (target * SECONDS_PER_MINUTE - t1.tv_sec) + 1; 386 tv.tv_usec = 0; 387 388 if (fdsr == NULL) { 389 fdsr = (fd_set *)calloc(howmany(cronSock + 1, NFDBITS), 390 sizeof(fd_mask)); 391 } 392 393 while (timerisset(&tv) && tv.tv_sec < 65) { 394 Debug(DSCH, ("[%ld] Target time=%ld, sec-to-wait=%ld\n", 395 (long)getpid(), (long)target*SECONDS_PER_MINUTE, tv.tv_sec)) 396 397 poke = RELOAD_CRON | RELOAD_AT; 398 if (fdsr) 399 FD_SET(cronSock, fdsr); 400 /* Sleep until we time out, get a poke, or get a signal. */ 401 nfds = select(cronSock + 1, fdsr, NULL, NULL, &tv); 402 if (nfds == 0) 403 break; /* timer expired */ 404 if (nfds == -1 && errno != EINTR) 405 break; /* an error occurred */ 406 if (nfds > 0) { 407 Debug(DSCH, ("[%ld] Got a poke on the socket\n", 408 (long)getpid())) 409 sunlen = sizeof(s_un); 410 fd = accept(cronSock, (struct sockaddr *)&s_un, &sunlen); 411 if (fd >= 0 && fcntl(fd, F_SETFL, O_NONBLOCK) == 0) { 412 (void) read(fd, &poke, 1); 413 close(fd); 414 if (poke & RELOAD_CRON) { 415 database.mtime = (time_t)0; 416 load_database(&database); 417 } 418 if (poke & RELOAD_AT) { 419 /* 420 * We run any pending at jobs right 421 * away so that "at now" really runs 422 * jobs immediately. 423 */ 424 gettimeofday(&t2, NULL); 425 at_database.mtime = (time_t)0; 426 if (scan_atjobs(&at_database, &t2)) 427 atrun(&at_database, 428 batch_maxload, t2.tv_sec); 429 } 430 } 431 } else { 432 /* Interrupted by a signal. */ 433 if (got_sighup) { 434 got_sighup = 0; 435 log_close(); 436 } 437 if (got_sigchld) { 438 got_sigchld = 0; 439 sigchld_reaper(); 440 } 441 } 442 443 /* Adjust tv and continue where we left off. */ 444 gettimeofday(&t2, NULL); 445 t2.tv_sec += GMToff; 446 timersub(&t2, &t1, &t1); 447 timersub(&tv, &t1, &tv); 448 memcpy(&t1, &t2, sizeof(t1)); 449 if (tv.tv_sec < 0) 450 tv.tv_sec = 0; 451 if (tv.tv_usec < 0) 452 tv.tv_usec = 0; 453 } 454 } 455 456 static void 457 sighup_handler(int x) { 458 got_sighup = 1; 459 } 460 461 static void 462 sigchld_handler(int x) { 463 got_sigchld = 1; 464 } 465 466 static void 467 quit(int x) { 468 (void) unlink(_PATH_CRON_PID); 469 _exit(0); 470 } 471 472 static void 473 sigchld_reaper(void) { 474 WAIT_T waiter; 475 PID_T pid; 476 477 do { 478 pid = waitpid(-1, &waiter, WNOHANG); 479 switch (pid) { 480 case -1: 481 if (errno == EINTR) 482 continue; 483 Debug(DPROC, 484 ("[%ld] sigchld...no children\n", 485 (long)getpid())) 486 break; 487 case 0: 488 Debug(DPROC, 489 ("[%ld] sigchld...no dead kids\n", 490 (long)getpid())) 491 break; 492 default: 493 Debug(DPROC, 494 ("[%ld] sigchld...pid #%ld died, stat=%d\n", 495 (long)getpid(), (long)pid, WEXITSTATUS(waiter))) 496 break; 497 } 498 } while (pid > 0); 499 } 500 501 static void 502 parse_args(int argc, char *argv[]) { 503 int argch; 504 char *ep; 505 506 while (-1 != (argch = getopt(argc, argv, "l:nx:"))) { 507 switch (argch) { 508 case 'l': 509 errno = 0; 510 batch_maxload = strtod(optarg, &ep); 511 if (*ep != '\0' || ep == optarg || errno == ERANGE || 512 batch_maxload < 0) { 513 fprintf(stderr, "Illegal load average: %s\n", 514 optarg); 515 usage(); 516 } 517 break; 518 case 'n': 519 NoFork = 1; 520 break; 521 case 'x': 522 if (!set_debug_flags(optarg)) 523 usage(); 524 break; 525 default: 526 usage(); 527 } 528 } 529 } 530