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