1/* $NetBSD: cron.c,v 1.7 2010/10/02 12:22:20 tron 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#include <sys/cdefs.h> 24#if !defined(lint) && !defined(LINT) 25#if 0 26static char rcsid[] = "Id: cron.c,v 1.12 2004/01/23 18:56:42 vixie Exp"; 27#else 28__RCSID("$NetBSD: cron.c,v 1.7 2010/10/02 12:22:20 tron Exp $"); 29#endif 30#endif 31 32#define MAIN_PROGRAM 33 34#include "cron.h" 35 36enum timejump { negative, small, medium, large }; 37 38static void usage(void), 39 run_reboot_jobs(cron_db *), 40 find_jobs(time_t, cron_db *, int, int), 41 set_time(int), 42 cron_sleep(int), 43 sigchld_handler(int), 44 sighup_handler(int), 45 sigchld_reaper(void), 46 quit(int), 47 parse_args(int c, char *v[]); 48 49static volatile sig_atomic_t got_sighup, got_sigchld; 50static time_t timeRunning, virtualTime, clockTime; 51static long GMToff; 52 53static void 54usage(void) { 55 const char **dflags; 56 57 (void)fprintf(stderr, "usage: %s [-n] [-x [", getprogname()); 58 for (dflags = DebugFlagNames; *dflags; dflags++) 59 (void)fprintf(stderr, "%s%s", *dflags, dflags[1] ? "," : "]"); 60 (void)fprintf(stderr, "]\n"); 61 exit(ERROR_EXIT); 62} 63 64int 65main(int argc, char *argv[]) { 66 struct sigaction sact; 67 cron_db database; 68 69 setprogname(argv[0]); 70 (void)setlocale(LC_ALL, ""); 71 72 (void)setvbuf(stdout, NULL, _IOLBF, 0); 73 (void)setvbuf(stderr, NULL, _IOLBF, 0); 74 75 NoFork = 0; 76 parse_args(argc, argv); 77 78 (void)memset(&sact, 0, sizeof sact); 79 (void)sigemptyset(&sact.sa_mask); 80 sact.sa_flags = 0; 81#ifdef SA_RESTART 82 sact.sa_flags |= SA_RESTART; 83#endif 84 sact.sa_handler = sigchld_handler; 85 (void) sigaction(SIGCHLD, &sact, NULL); 86 sact.sa_handler = sighup_handler; 87 (void) sigaction(SIGHUP, &sact, NULL); 88 sact.sa_handler = quit; 89 (void) sigaction(SIGINT, &sact, NULL); 90 (void) sigaction(SIGTERM, &sact, NULL); 91 92 acquire_daemonlock(0); 93 set_cron_uid(); 94 set_cron_cwd(); 95 96 if (setenv("PATH", _PATH_DEFPATH, 1) < 0) { 97 log_it("CRON", getpid(), "DEATH", "can't malloc"); 98 exit(1); 99 } 100 101 /* if there are no debug flags turned on, fork as a daemon should. 102 */ 103 if (DebugFlags) { 104#if DEBUGGING 105 (void)fprintf(stderr, "[%ld] cron started\n", (long)getpid()); 106#endif 107 } else if (NoFork == 0) { 108 if (daemon(1, 0)) { 109 log_it("CRON",getpid(),"DEATH","can't fork"); 110 exit(1); 111 } 112 } 113 114 acquire_daemonlock(0); 115 database.head = NULL; 116 database.tail = NULL; 117 database.mtime = (time_t) 0; 118 load_database(&database); 119 set_time(TRUE); 120 run_reboot_jobs(&database); 121 timeRunning = virtualTime = clockTime; 122 123 /* 124 * Too many clocks, not enough time (Al. Einstein) 125 * These clocks are in minutes since the epoch, adjusted for timezone. 126 * virtualTime: is the time it *would* be if we woke up 127 * promptly and nobody ever changed the clock. It is 128 * monotonically increasing... unless a timejump happens. 129 * At the top of the loop, all jobs for 'virtualTime' have run. 130 * timeRunning: is the time we last awakened. 131 * clockTime: is the time when set_time was last called. 132 */ 133 for (;;) { 134 int timeDiff; 135 enum timejump wakeupKind; 136 137 /* ... wait for the time (in minutes) to change ... */ 138 do { 139 cron_sleep(timeRunning + 1); 140 set_time(FALSE); 141 } while (clockTime == timeRunning); 142 timeRunning = clockTime; 143 144 /* 145 * Calculate how the current time differs from our virtual 146 * clock. Classify the change into one of 4 cases. 147 */ 148 timeDiff = timeRunning - virtualTime; 149 150 /* shortcut for the most common case */ 151 if (timeDiff == 1) { 152 virtualTime = timeRunning; 153 find_jobs(virtualTime, &database, TRUE, TRUE); 154 } else { 155 if (timeDiff > (3*MINUTE_COUNT) || 156 timeDiff < -(3*MINUTE_COUNT)) 157 wakeupKind = large; 158 else if (timeDiff > 5) 159 wakeupKind = medium; 160 else if (timeDiff > 0) 161 wakeupKind = small; 162 else 163 wakeupKind = negative; 164 165 switch (wakeupKind) { 166 case small: 167 /* 168 * case 1: timeDiff is a small positive number 169 * (wokeup late) run jobs for each virtual 170 * minute until caught up. 171 */ 172 Debug(DSCH, ("[%ld], normal case %d minutes to go\n", 173 (long)getpid(), timeDiff)); 174 do { 175 if (job_runqueue()) 176 (void)sleep(10); 177 virtualTime++; 178 find_jobs(virtualTime, &database, 179 TRUE, TRUE); 180 } while (virtualTime < timeRunning); 181 break; 182 183 case medium: 184 /* 185 * case 2: timeDiff is a medium-sized positive 186 * number, for example because we went to DST 187 * run wildcard jobs once, then run any 188 * fixed-time jobs that would otherwise be 189 * skipped if we use up our minute (possible, 190 * if there are a lot of jobs to run) go 191 * around the loop again so that wildcard jobs 192 * have a chance to run, and we do our 193 * housekeeping. 194 */ 195 Debug(DSCH, ("[%ld], DST begins %d minutes to go\n", 196 (long)getpid(), timeDiff)); 197 /* run wildcard jobs for current minute */ 198 find_jobs(timeRunning, &database, TRUE, FALSE); 199 200 /* run fixed-time jobs for each minute missed */ 201 do { 202 if (job_runqueue()) 203 (void)sleep(10); 204 virtualTime++; 205 find_jobs(virtualTime, &database, 206 FALSE, TRUE); 207 set_time(FALSE); 208 } while (virtualTime < timeRunning && 209 clockTime == timeRunning); 210 break; 211 212 case negative: 213 /* 214 * case 3: timeDiff is a small or medium-sized 215 * negative num, eg. because of DST ending. 216 * Just run the wildcard jobs. The fixed-time 217 * jobs probably have already run, and should 218 * not be repeated. Virtual time does not 219 * change until we are caught up. 220 */ 221 Debug(DSCH, ("[%ld], DST ends %d minutes to go\n", 222 (long)getpid(), timeDiff)); 223 find_jobs(timeRunning, &database, TRUE, FALSE); 224 break; 225 default: 226 /* 227 * other: time has changed a *lot*, 228 * jump virtual time, and run everything 229 */ 230 Debug(DSCH, ("[%ld], clock jumped\n", 231 (long)getpid())); 232 virtualTime = timeRunning; 233 find_jobs(timeRunning, &database, TRUE, TRUE); 234 } 235 } 236 237 /* Jobs to be run (if any) are loaded; clear the queue. */ 238 (void)job_runqueue(); 239 240 /* Check to see if we received a signal while running jobs. */ 241 if (got_sighup) { 242 got_sighup = 0; 243 log_close(); 244 } 245 if (got_sigchld) { 246 got_sigchld = 0; 247 sigchld_reaper(); 248 } 249 load_database(&database); 250 } 251} 252 253static void 254run_reboot_jobs(cron_db *db) { 255 user *u; 256 entry *e; 257 258 for (u = db->head; u != NULL; u = u->next) { 259 for (e = u->crontab; e != NULL; e = e->next) { 260 if (e->flags & WHEN_REBOOT) 261 job_add(e, u, StartTime); 262 } 263 } 264 (void) job_runqueue(); 265} 266 267static const char * 268bitprint(const bitstr_t *b, size_t l) 269{ 270 size_t i, set, clear; 271 static char result[1024]; 272 char tmp[1024]; 273 274 result[0] = '\0'; 275 for (i = 0;;) { 276 for (; i < l; i++) 277 if (bit_test(b, i)) 278 break; 279 if (i == l) 280 return result; 281 set = i; 282 for (; i < l; i++) 283 if (!bit_test(b, i)) 284 break; 285 clear = i; 286 if (set == 0 && clear == l) { 287 snprintf(result, sizeof(result), "*"); 288 return result; 289 } 290 if (clear == l || set == clear - 1) 291 snprintf(tmp, sizeof(tmp), ",%zu", set); 292 else 293 snprintf(tmp, sizeof(tmp), ",%zu-%zu", set, clear - 1); 294 if (result[0] == '\0') 295 strlcpy(result, tmp + 1, sizeof(result)); 296 else 297 strlcat(result, tmp, sizeof(result)); 298 } 299} 300 301static const char * 302flagsprint(int flags) 303{ 304 static char result[1024]; 305 306 result[0] = '\0'; 307 if (flags & MIN_STAR) 308 strlcat(result, ",min", sizeof(result)); 309 if (flags & HR_STAR) 310 strlcat(result, ",hr", sizeof(result)); 311 if (flags & DOM_STAR) 312 strlcat(result, ",dom", sizeof(result)); 313 if (flags & DOW_STAR) 314 strlcat(result, ",dow", sizeof(result)); 315 if (flags & WHEN_REBOOT) 316 strlcat(result, ",reboot", sizeof(result)); 317 if (flags & DONT_LOG) 318 strlcat(result, ",nolog", sizeof(result)); 319 320 return result + (result[0] == ','); 321} 322 323static void 324printone(char *res, size_t len, const char *b) 325{ 326 int comma = strchr(b, ',') != NULL; 327 if (comma) 328 strlcat(res, "[", len); 329 strlcat(res, b, len); 330 strlcat(res, "]," + (comma ? 0 : 1), len); 331} 332 333static const char * 334tick(const entry *e) 335{ 336 static char result[1024]; 337 338 result[0] = '\0'; 339 printone(result, sizeof(result), bitprint(e->minute, MINUTE_COUNT)); 340 printone(result, sizeof(result), bitprint(e->hour, HOUR_COUNT)); 341 printone(result, sizeof(result), bitprint(e->dom, DOM_COUNT)); 342 printone(result, sizeof(result), bitprint(e->month, MONTH_COUNT)); 343 printone(result, sizeof(result), bitprint(e->dow, DOW_COUNT)); 344 strlcat(result, "flags=", sizeof(result)); 345 strlcat(result, flagsprint(e->flags), sizeof(result)); 346 return result; 347} 348 349static void 350find_jobs(time_t vtime, cron_db *db, int doWild, int doNonWild) { 351 time_t virtualSecond = vtime * SECONDS_PER_MINUTE; 352 struct tm *tm; 353 int minute, hour, dom, month, dow; 354 user *u; 355 entry *e; 356#ifndef CRON_LOCALTIME 357 char *orig_tz, *job_tz; 358 359#define maketime(tz1, tz2) do { \ 360 char *t = tz1; \ 361 if (t != NULL && *t != '\0') \ 362 setenv("TZ", t, 1); \ 363 else if ((tz2) != NULL) \ 364 setenv("TZ", (tz2), 1); \ 365 else \ 366 unsetenv("TZ"); \ 367 tzset(); \ 368 tm = localtime(&virtualSecond); \ 369 minute = tm->tm_min -FIRST_MINUTE; \ 370 hour = tm->tm_hour -FIRST_HOUR; \ 371 dom = tm->tm_mday -FIRST_DOM; \ 372 month = tm->tm_mon + 1 /* 0..11 -> 1..12 */ -FIRST_MONTH; \ 373 dow = tm->tm_wday -FIRST_DOW; \ 374 } while (/*CONSTCOND*/0) 375 376 orig_tz = getenv("TZ"); 377 maketime(NULL, orig_tz); 378#else 379 tm = gmtime(&virtualSecond); 380#endif 381 382 Debug(DSCH, ("[%ld] tick(%d,%d,%d,%d,%d) %s %s\n", 383 (long)getpid(), minute, hour, dom, month, dow, 384 doWild?" ":"No wildcard",doNonWild?" ":"Wildcard only")); 385 386 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the 387 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* 388 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this 389 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. 390 * like many bizarre things, it's the standard. 391 */ 392 for (u = db->head; u != NULL; u = u->next) { 393 for (e = u->crontab; e != NULL; e = e->next) { 394#ifndef CRON_LOCALTIME 395 job_tz = env_get("CRON_TZ", e->envp); 396 maketime(job_tz, orig_tz); 397#else 398#define job_tz "N/A" 399#define orig_tz "N/A" 400#endif 401 Debug(DSCH|DEXT, ("user [%s:%ld:%ld:...] " 402 "[jobtz=%s, origtz=%s] " 403 "tick(%s), cmd=\"%s\"\n", 404 e->pwd->pw_name, (long)e->pwd->pw_uid, 405 (long)e->pwd->pw_gid, job_tz, orig_tz, 406 tick(e), e->cmd)); 407 if (bit_test(e->minute, minute) && 408 bit_test(e->hour, hour) && 409 bit_test(e->month, month) && 410 ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 411 ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) 412 : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) 413 ) 414 ) { 415 if ((doNonWild && 416 !(e->flags & (MIN_STAR|HR_STAR))) || 417 (doWild && (e->flags & (MIN_STAR|HR_STAR)))) 418 job_add(e, u, StartTime); 419 } 420 } 421 } 422#ifndef CRON_LOCALTIME 423 if (orig_tz != NULL) 424 setenv("TZ", orig_tz, 1); 425 else 426 unsetenv("TZ"); 427#endif 428} 429 430/* 431 * Set StartTime and clockTime to the current time. 432 * These are used for computing what time it really is right now. 433 * Note that clockTime is a unix wallclock time converted to minutes. 434 */ 435static void 436set_time(int initialize) { 437 438#ifdef CRON_LOCALTIME 439 struct tm tm; 440 static int isdst; 441 442 StartTime = time(NULL); 443 /* We adjust the time to GMT so we can catch DST changes. */ 444 tm = *localtime(&StartTime); 445 if (initialize || tm.tm_isdst != isdst) { 446 isdst = tm.tm_isdst; 447 GMToff = get_gmtoff(&StartTime, &tm); 448 Debug(DSCH, ("[%ld] GMToff=%ld\n", 449 (long)getpid(), (long)GMToff)); 450 } 451#else 452 StartTime = time(NULL); 453#endif 454 clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE; 455} 456 457/* 458 * Try to just hit the next minute. 459 */ 460static void 461cron_sleep(int target) { 462 time_t t1, t2; 463 int seconds_to_wait; 464 465 t1 = time(NULL) + GMToff; 466 seconds_to_wait = (int)(target * SECONDS_PER_MINUTE - t1); 467 Debug(DSCH, ("[%ld] Target time=%lld, sec-to-wait=%d\n", 468 (long)getpid(), (long long)target*SECONDS_PER_MINUTE, 469 seconds_to_wait)); 470 471 while (seconds_to_wait > 0 && seconds_to_wait < 65) { 472 (void)sleep((unsigned int) seconds_to_wait); 473 474 /* 475 * Check to see if we were interrupted by a signal. 476 * If so, service the signal(s) then continue sleeping 477 * where we left off. 478 */ 479 if (got_sighup) { 480 got_sighup = 0; 481 log_close(); 482 } 483 if (got_sigchld) { 484 got_sigchld = 0; 485 sigchld_reaper(); 486 } 487 t2 = time(NULL) + GMToff; 488 seconds_to_wait -= (int)(t2 - t1); 489 t1 = t2; 490 } 491} 492 493/*ARGSUSED*/ 494static void 495sighup_handler(int x __unused) { 496 got_sighup = 1; 497} 498 499/*ARGSUSED*/ 500static void 501sigchld_handler(int x __unused) { 502 got_sigchld = 1; 503} 504 505/*ARGSUSED*/ 506static void 507quit(int x __unused) { 508 (void) unlink(_PATH_CRON_PID); 509 _exit(0); 510} 511 512static void 513sigchld_reaper(void) { 514 WAIT_T waiter; 515 PID_T pid; 516 517 do { 518 pid = waitpid(-1, &waiter, WNOHANG); 519 switch (pid) { 520 case -1: 521 if (errno == EINTR) 522 continue; 523 Debug(DPROC, 524 ("[%ld] sigchld...no children\n", 525 (long)getpid())); 526 break; 527 case 0: 528 Debug(DPROC, 529 ("[%ld] sigchld...no dead kids\n", 530 (long)getpid())); 531 break; 532 default: 533 Debug(DPROC, 534 ("[%ld] sigchld...pid #%ld died, stat=%d\n", 535 (long)getpid(), (long)pid, WEXITSTATUS(waiter))); 536 break; 537 } 538 } while (pid > 0); 539} 540 541static void 542parse_args(int argc, char *argv[]) { 543 int argch; 544 545 while (-1 != (argch = getopt(argc, argv, "nx:"))) { 546 switch (argch) { 547 default: 548 usage(); 549 break; 550 case 'x': 551 if (!set_debug_flags(optarg)) 552 usage(); 553 break; 554 case 'n': 555 NoFork = 1; 556 break; 557 } 558 } 559} 560