1 /* $OpenBSD: cron.c,v 1.49 2015/01/23 01:01:06 tedu 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(time_t, cron_db *, int, int), 33 set_time(int), 34 cron_sleep(time_t), 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 time_t timeRunning, virtualTime, clockTime; 43 static int cronSock; 44 static long GMToff; 45 static cron_db database; 46 static at_db at_database; 47 static double batch_maxload = BATCH_MAXLOAD; 48 49 static void 50 usage(void) { 51 52 fprintf(stderr, "usage: %s [-n] [-l load_avg] [-x [", ProgramName); 53 fprintf(stderr, "debugging flags (none supported in this build)]"); 54 fprintf(stderr, "]\n"); 55 exit(EXIT_FAILURE); 56 } 57 58 int 59 main(int argc, char *argv[]) { 60 struct sigaction sact; 61 int fd; 62 63 ProgramName = argv[0]; 64 65 setlocale(LC_ALL, ""); 66 67 setvbuf(stdout, NULL, _IOLBF, 0); 68 setvbuf(stderr, NULL, _IOLBF, 0); 69 70 NoFork = 0; 71 parse_args(argc, argv); 72 73 bzero((char *)&sact, sizeof sact); 74 sigemptyset(&sact.sa_mask); 75 sact.sa_flags = 0; 76 #ifdef SA_RESTART 77 sact.sa_flags |= SA_RESTART; 78 #endif 79 sact.sa_handler = sigchld_handler; 80 (void) sigaction(SIGCHLD, &sact, NULL); 81 sact.sa_handler = sighup_handler; 82 (void) sigaction(SIGHUP, &sact, NULL); 83 sact.sa_handler = quit; 84 (void) sigaction(SIGINT, &sact, NULL); 85 (void) sigaction(SIGTERM, &sact, NULL); 86 sact.sa_handler = SIG_IGN; 87 (void) sigaction(SIGPIPE, &sact, NULL); 88 89 acquire_daemonlock(0); 90 set_cron_uid(); 91 set_cron_cwd(); 92 93 if (putenv("PATH="_PATH_DEFPATH) < 0) { 94 log_it("CRON", getpid(), "DEATH", "can't malloc"); 95 exit(EXIT_FAILURE); 96 } 97 98 if (NoFork == 0) { 99 switch (fork()) { 100 case -1: 101 log_it("CRON",getpid(),"DEATH","can't fork"); 102 exit(EXIT_FAILURE); 103 break; 104 case 0: 105 /* child process */ 106 (void) setsid(); 107 if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) >= 0) { 108 (void) dup2(fd, STDIN_FILENO); 109 (void) dup2(fd, STDOUT_FILENO); 110 (void) dup2(fd, STDERR_FILENO); 111 if (fd != STDERR_FILENO) 112 (void) close(fd); 113 } 114 log_it("CRON",getpid(),"STARTUP",CRON_VERSION); 115 break; 116 default: 117 /* parent process should just die */ 118 _exit(EXIT_SUCCESS); 119 } 120 } 121 122 acquire_daemonlock(0); 123 cronSock = open_socket(); 124 database.head = NULL; 125 database.tail = NULL; 126 database.mtime = 0; 127 load_database(&database); 128 at_database.head = NULL; 129 at_database.tail = NULL; 130 at_database.mtime = 0; 131 scan_atjobs(&at_database, NULL); 132 set_time(TRUE); 133 run_reboot_jobs(&database); 134 timeRunning = virtualTime = clockTime; 135 136 /* 137 * Too many clocks, not enough time (Al. Einstein) 138 * These clocks are in minutes since the epoch, adjusted for timezone. 139 * virtualTime: is the time it *would* be if we woke up 140 * promptly and nobody ever changed the clock. It is 141 * monotonically increasing... unless a timejump happens. 142 * At the top of the loop, all jobs for 'virtualTime' have run. 143 * timeRunning: is the time we last awakened. 144 * clockTime: is the time when set_time was last called. 145 */ 146 while (TRUE) { 147 int timeDiff; 148 enum timejump wakeupKind; 149 150 /* ... wait for the time (in minutes) to change ... */ 151 do { 152 cron_sleep(timeRunning + 1); 153 set_time(FALSE); 154 } while (clockTime == timeRunning); 155 timeRunning = clockTime; 156 157 /* 158 * Calculate how the current time differs from our virtual 159 * clock. Classify the change into one of 4 cases. 160 */ 161 timeDiff = timeRunning - virtualTime; 162 163 /* shortcut for the most common case */ 164 if (timeDiff == 1) { 165 virtualTime = timeRunning; 166 find_jobs(virtualTime, &database, TRUE, TRUE); 167 } else { 168 if (timeDiff > (3*MINUTE_COUNT) || 169 timeDiff < -(3*MINUTE_COUNT)) 170 wakeupKind = large; 171 else if (timeDiff > 5) 172 wakeupKind = medium; 173 else if (timeDiff > 0) 174 wakeupKind = small; 175 else 176 wakeupKind = negative; 177 178 switch (wakeupKind) { 179 case small: 180 /* 181 * case 1: timeDiff is a small positive number 182 * (wokeup late) run jobs for each virtual 183 * minute until caught up. 184 */ 185 do { 186 if (job_runqueue()) 187 sleep(10); 188 virtualTime++; 189 find_jobs(virtualTime, &database, 190 TRUE, TRUE); 191 } while (virtualTime < timeRunning); 192 break; 193 194 case medium: 195 /* 196 * case 2: timeDiff is a medium-sized positive 197 * number, for example because we went to DST 198 * run wildcard jobs once, then run any 199 * fixed-time jobs that would otherwise be 200 * skipped if we use up our minute (possible, 201 * if there are a lot of jobs to run) go 202 * around the loop again so that wildcard jobs 203 * have a chance to run, and we do our 204 * housekeeping. 205 */ 206 /* run wildcard jobs for current minute */ 207 find_jobs(timeRunning, &database, TRUE, FALSE); 208 209 /* run fixed-time jobs for each minute missed */ 210 do { 211 if (job_runqueue()) 212 sleep(10); 213 virtualTime++; 214 find_jobs(virtualTime, &database, 215 FALSE, TRUE); 216 set_time(FALSE); 217 } while (virtualTime< timeRunning && 218 clockTime == timeRunning); 219 break; 220 221 case negative: 222 /* 223 * case 3: timeDiff is a small or medium-sized 224 * negative num, eg. because of DST ending. 225 * Just run the wildcard jobs. The fixed-time 226 * jobs probably have already run, and should 227 * not be repeated. Virtual time does not 228 * change until we are caught up. 229 */ 230 find_jobs(timeRunning, &database, TRUE, FALSE); 231 break; 232 default: 233 /* 234 * other: time has changed a *lot*, 235 * jump virtual time, and run everything 236 */ 237 virtualTime = timeRunning; 238 find_jobs(timeRunning, &database, TRUE, TRUE); 239 } 240 } 241 242 /* Jobs to be run (if any) are loaded; clear the queue. */ 243 job_runqueue(); 244 245 /* Run any jobs in the at queue. */ 246 atrun(&at_database, batch_maxload, 247 timeRunning * SECONDS_PER_MINUTE - GMToff); 248 249 /* Check to see if we received a signal while running jobs. */ 250 if (got_sighup) { 251 got_sighup = 0; 252 log_close(); 253 } 254 if (got_sigchld) { 255 got_sigchld = 0; 256 sigchld_reaper(); 257 } 258 load_database(&database); 259 scan_atjobs(&at_database, NULL); 260 } 261 } 262 263 static void 264 run_reboot_jobs(cron_db *db) { 265 user *u; 266 entry *e; 267 268 for (u = db->head; u != NULL; u = u->next) { 269 for (e = u->crontab; e != NULL; e = e->next) { 270 if (e->flags & WHEN_REBOOT) 271 job_add(e, u); 272 } 273 } 274 (void) job_runqueue(); 275 } 276 277 static void 278 find_jobs(time_t vtime, cron_db *db, int doWild, int doNonWild) { 279 time_t virtualSecond = vtime * SECONDS_PER_MINUTE; 280 struct tm *tm = gmtime(&virtualSecond); 281 int minute, hour, dom, month, dow; 282 user *u; 283 entry *e; 284 285 /* make 0-based values out of these so we can use them as indices 286 */ 287 minute = tm->tm_min -FIRST_MINUTE; 288 hour = tm->tm_hour -FIRST_HOUR; 289 dom = tm->tm_mday -FIRST_DOM; 290 month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 291 dow = tm->tm_wday -FIRST_DOW; 292 293 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the 294 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* 295 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this 296 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. 297 * like many bizarre things, it's the standard. 298 */ 299 for (u = db->head; u != NULL; u = u->next) { 300 for (e = u->crontab; e != NULL; e = e->next) { 301 if (bit_test(e->minute, minute) && 302 bit_test(e->hour, hour) && 303 bit_test(e->month, month) && 304 ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 305 ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) 306 : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) 307 ) 308 ) { 309 if ((doNonWild && 310 !(e->flags & (MIN_STAR|HR_STAR))) || 311 (doWild && (e->flags & (MIN_STAR|HR_STAR)))) 312 job_add(e, u); 313 } 314 } 315 } 316 } 317 318 /* 319 * Set StartTime and clockTime to the current time. 320 * These are used for computing what time it really is right now. 321 * Note that clockTime is a unix wallclock time converted to minutes. 322 */ 323 static void 324 set_time(int initialize) { 325 struct tm tm; 326 static int isdst; 327 328 StartTime = time(NULL); 329 330 /* We adjust the time to GMT so we can catch DST changes. */ 331 tm = *localtime(&StartTime); 332 if (initialize || tm.tm_isdst != isdst) { 333 isdst = tm.tm_isdst; 334 GMToff = get_gmtoff(&StartTime, &tm); 335 } 336 clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE; 337 } 338 339 /* 340 * Try to just hit the next minute. 341 */ 342 static void 343 cron_sleep(time_t target) { 344 int fd, nfds; 345 unsigned char poke; 346 struct timeval t1, t2, tv; 347 struct sockaddr_un s_un; 348 socklen_t sunlen; 349 static struct pollfd pfd[1]; 350 351 gettimeofday(&t1, NULL); 352 t1.tv_sec += GMToff; 353 tv.tv_sec = (target * SECONDS_PER_MINUTE - t1.tv_sec) + 1; 354 tv.tv_usec = 0; 355 356 pfd[0].fd = cronSock; 357 pfd[0].events = POLLIN; 358 359 while (timerisset(&tv) && tv.tv_sec < 65) { 360 poke = RELOAD_CRON | RELOAD_AT; 361 362 /* Sleep until we time out, get a poke, or get a signal. */ 363 nfds = poll(pfd, 1, tv.tv_sec * 1000 + tv.tv_usec / 1000); 364 if (nfds == 0) 365 break; /* timer expired */ 366 if (nfds == -1 && errno != EINTR) 367 break; /* an error occurred */ 368 if (nfds > 0) { 369 sunlen = sizeof(s_un); 370 fd = accept(cronSock, (struct sockaddr *)&s_un, &sunlen); 371 if (fd >= 0 && fcntl(fd, F_SETFL, O_NONBLOCK) == 0) { 372 (void) read(fd, &poke, 1); 373 close(fd); 374 if (poke & RELOAD_CRON) { 375 database.mtime = 0; 376 load_database(&database); 377 } 378 if (poke & RELOAD_AT) { 379 /* 380 * We run any pending at jobs right 381 * away so that "at now" really runs 382 * jobs immediately. 383 */ 384 gettimeofday(&t2, NULL); 385 at_database.mtime = 0; 386 if (scan_atjobs(&at_database, &t2)) 387 atrun(&at_database, 388 batch_maxload, t2.tv_sec); 389 } 390 } 391 } else { 392 /* Interrupted by a signal. */ 393 if (got_sighup) { 394 got_sighup = 0; 395 log_close(); 396 } 397 if (got_sigchld) { 398 got_sigchld = 0; 399 sigchld_reaper(); 400 } 401 } 402 403 /* Adjust tv and continue where we left off. */ 404 gettimeofday(&t2, NULL); 405 t2.tv_sec += GMToff; 406 timersub(&t2, &t1, &t1); 407 timersub(&tv, &t1, &tv); 408 memcpy(&t1, &t2, sizeof(t1)); 409 if (tv.tv_sec < 0) 410 tv.tv_sec = 0; 411 if (tv.tv_usec < 0) 412 tv.tv_usec = 0; 413 } 414 } 415 416 static void 417 sighup_handler(int x) { 418 got_sighup = 1; 419 } 420 421 static void 422 sigchld_handler(int x) { 423 got_sigchld = 1; 424 } 425 426 static void 427 quit(int x) { 428 (void) unlink(_PATH_CRON_PID); 429 _exit(0); 430 } 431 432 static void 433 sigchld_reaper(void) { 434 int waiter; 435 pid_t pid; 436 437 do { 438 pid = waitpid(-1, &waiter, WNOHANG); 439 switch (pid) { 440 case -1: 441 if (errno == EINTR) 442 continue; 443 break; 444 case 0: 445 break; 446 default: 447 break; 448 } 449 } while (pid > 0); 450 } 451 452 static void 453 parse_args(int argc, char *argv[]) { 454 int argch; 455 char *ep; 456 457 while (-1 != (argch = getopt(argc, argv, "l:n"))) { 458 switch (argch) { 459 case 'l': 460 errno = 0; 461 batch_maxload = strtod(optarg, &ep); 462 if (*ep != '\0' || ep == optarg || errno == ERANGE || 463 batch_maxload < 0) { 464 fprintf(stderr, "Illegal load average: %s\n", 465 optarg); 466 usage(); 467 } 468 break; 469 case 'n': 470 NoFork = 1; 471 break; 472 default: 473 usage(); 474 } 475 } 476 } 477