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