1/* Copyright 1988,1990,1993,1994 by Paul Vixie 2 * All rights reserved 3 * 4 * Distribute freely, except: don't remove my name from the source or 5 * documentation (don't take credit for my work), mark your changes (don't 6 * get me blamed for your possible bugs), don't alter or remove this 7 * notice. May be sold if buildable source is provided to buyer. No 8 * warrantee of any kind, express or implied, is included with this 9 * software; use at your own risk, responsibility for damages (if any) to 10 * anyone resulting from the use of this software rests entirely with the 11 * user. 12 * 13 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 14 * I'll try to keep a version up to date. I can be reached as follows: 15 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 16 */ 17 18#if !defined(lint) && !defined(LINT) 19static const char rcsid[] = 20 "$FreeBSD$"; 21#endif 22 23#define MAIN_PROGRAM 24 25 26#include "cron.h" 27#include <sys/mman.h> 28#include <sys/signal.h> 29#if SYS_TIME_H 30# include <sys/time.h> 31#else 32# include <time.h> 33#endif 34 35 36static void usage(void), 37 run_reboot_jobs(cron_db *), 38 cron_tick(cron_db *), 39 cron_sync(void), 40 cron_sleep(cron_db *), 41 cron_clean(cron_db *), 42#ifdef USE_SIGCHLD 43 sigchld_handler(int), 44#endif 45 sighup_handler(int), 46 parse_args(int c, char *v[]); 47 48static time_t last_time = 0; 49static int dst_enabled = 0; 50struct pidfh *pfh; 51 52static void 53usage() { 54 char **dflags; 55 56 fprintf(stderr, "usage: cron [-j jitter] [-J rootjitter] " 57 "[-m mailto] [-s] [-o] [-x debugflag[,...]]\n"); 58 fprintf(stderr, "\ndebugflags: "); 59 60 for(dflags = DebugFlagNames; *dflags; dflags++) { 61 fprintf(stderr, "%s ", *dflags); 62 } 63 fprintf(stderr, "\n"); 64 65 exit(ERROR_EXIT); 66} 67 68static void 69open_pidfile(void) 70{ 71 char pidfile[MAX_FNAME]; 72 char buf[MAX_TEMPSTR]; 73 int otherpid; 74 75 (void) snprintf(pidfile, sizeof(pidfile), PIDFILE, PIDDIR); 76 pfh = pidfile_open(pidfile, 0600, &otherpid); 77 if (pfh == NULL) { 78 if (errno == EEXIST) { 79 snprintf(buf, sizeof(buf), 80 "cron already running, pid: %d", otherpid); 81 } else { 82 snprintf(buf, sizeof(buf), 83 "can't open or create %s: %s", pidfile, 84 strerror(errno)); 85 } 86 log_it("CRON", getpid(), "DEATH", buf); 87 errx(ERROR_EXIT, "%s", buf); 88 } 89} 90 91int 92main(argc, argv) 93 int argc; 94 char *argv[]; 95{ 96 cron_db database; 97 98 ProgramName = argv[0]; 99 100#if defined(BSD) 101 setlinebuf(stdout); 102 setlinebuf(stderr); 103#endif 104 105 parse_args(argc, argv); 106 107#ifdef USE_SIGCHLD 108 (void) signal(SIGCHLD, sigchld_handler); 109#else 110 (void) signal(SIGCLD, SIG_IGN); 111#endif 112 (void) signal(SIGHUP, sighup_handler); 113 114 open_pidfile(); 115 set_cron_uid(); 116 set_cron_cwd(); 117 118#if defined(POSIX) 119 setenv("PATH", _PATH_DEFPATH, 1); 120#endif 121 122 /* if there are no debug flags turned on, fork as a daemon should. 123 */ 124# if DEBUGGING 125 if (DebugFlags) { 126# else 127 if (0) { 128# endif 129 (void) fprintf(stderr, "[%d] cron started\n", getpid()); 130 } else { 131 if (daemon(1, 0) == -1) { 132 pidfile_remove(pfh); 133 log_it("CRON",getpid(),"DEATH","can't become daemon"); 134 exit(0); 135 } 136 } 137 138 if (madvise(NULL, 0, MADV_PROTECT) != 0) 139 log_it("CRON", getpid(), "WARNING", "madvise() failed"); 140 141 pidfile_write(pfh); 142 database.head = NULL; 143 database.tail = NULL; 144 database.mtime = (time_t) 0; 145 load_database(&database); 146 run_reboot_jobs(&database); 147 cron_sync(); 148 while (TRUE) { 149# if DEBUGGING 150 /* if (!(DebugFlags & DTEST)) */ 151# endif /*DEBUGGING*/ 152 cron_sleep(&database); 153 154 load_database(&database); 155 156 /* do this iteration 157 */ 158 cron_tick(&database); 159 160 /* sleep 1 minute 161 */ 162 TargetTime += 60; 163 } 164} 165 166 167static void 168run_reboot_jobs(db) 169 cron_db *db; 170{ 171 register user *u; 172 register entry *e; 173 174 for (u = db->head; u != NULL; u = u->next) { 175 for (e = u->crontab; e != NULL; e = e->next) { 176 if (e->flags & WHEN_REBOOT) { 177 job_add(e, u); 178 } 179 } 180 } 181 (void) job_runqueue(); 182} 183 184 185static void 186cron_tick(db) 187 cron_db *db; 188{ 189 static struct tm lasttm; 190 static time_t diff = 0, /* time difference in seconds from the last offset change */ 191 difflimit = 0; /* end point for the time zone correction */ 192 struct tm otztm; /* time in the old time zone */ 193 int otzminute, otzhour, otzdom, otzmonth, otzdow; 194 register struct tm *tm = localtime(&TargetTime); 195 register int minute, hour, dom, month, dow; 196 register user *u; 197 register entry *e; 198 199 /* make 0-based values out of these so we can use them as indicies 200 */ 201 minute = tm->tm_min -FIRST_MINUTE; 202 hour = tm->tm_hour -FIRST_HOUR; 203 dom = tm->tm_mday -FIRST_DOM; 204 month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 205 dow = tm->tm_wday -FIRST_DOW; 206 207 Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n", 208 getpid(), minute, hour, dom, month, dow)) 209 210 if (dst_enabled && last_time != 0 211 && TargetTime > last_time /* exclude stepping back */ 212 && tm->tm_gmtoff != lasttm.tm_gmtoff ) { 213 214 diff = tm->tm_gmtoff - lasttm.tm_gmtoff; 215 216 if ( diff > 0 ) { /* ST->DST */ 217 /* mark jobs for an earlier run */ 218 difflimit = TargetTime + diff; 219 for (u = db->head; u != NULL; u = u->next) { 220 for (e = u->crontab; e != NULL; e = e->next) { 221 e->flags &= ~NOT_UNTIL; 222 if ( e->lastrun >= TargetTime ) 223 e->lastrun = 0; 224 /* not include the ends of hourly ranges */ 225 if ( e->lastrun < TargetTime - 3600 ) 226 e->flags |= RUN_AT; 227 else 228 e->flags &= ~RUN_AT; 229 } 230 } 231 } else { /* diff < 0 : DST->ST */ 232 /* mark jobs for skipping */ 233 difflimit = TargetTime - diff; 234 for (u = db->head; u != NULL; u = u->next) { 235 for (e = u->crontab; e != NULL; e = e->next) { 236 e->flags |= NOT_UNTIL; 237 e->flags &= ~RUN_AT; 238 } 239 } 240 } 241 } 242 243 if (diff != 0) { 244 /* if the time was reset of the end of special zone is reached */ 245 if (last_time == 0 || TargetTime >= difflimit) { 246 /* disable the TZ switch checks */ 247 diff = 0; 248 difflimit = 0; 249 for (u = db->head; u != NULL; u = u->next) { 250 for (e = u->crontab; e != NULL; e = e->next) { 251 e->flags &= ~(RUN_AT|NOT_UNTIL); 252 } 253 } 254 } else { 255 /* get the time in the old time zone */ 256 time_t difftime = TargetTime + tm->tm_gmtoff - diff; 257 gmtime_r(&difftime, &otztm); 258 259 /* make 0-based values out of these so we can use them as indicies 260 */ 261 otzminute = otztm.tm_min -FIRST_MINUTE; 262 otzhour = otztm.tm_hour -FIRST_HOUR; 263 otzdom = otztm.tm_mday -FIRST_DOM; 264 otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 265 otzdow = otztm.tm_wday -FIRST_DOW; 266 } 267 } 268 269 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the 270 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* 271 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this 272 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. 273 * like many bizarre things, it's the standard. 274 */ 275 for (u = db->head; u != NULL; u = u->next) { 276 for (e = u->crontab; e != NULL; e = e->next) { 277 Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n", 278 env_get("LOGNAME", e->envp), 279 e->uid, e->gid, e->cmd)) 280 281 if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) { 282 if (bit_test(e->minute, otzminute) 283 && bit_test(e->hour, otzhour) 284 && bit_test(e->month, otzmonth) 285 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 286 ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom)) 287 : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom)) 288 ) 289 ) { 290 if ( e->flags & RUN_AT ) { 291 e->flags &= ~RUN_AT; 292 e->lastrun = TargetTime; 293 job_add(e, u); 294 continue; 295 } else 296 e->flags &= ~NOT_UNTIL; 297 } else if ( e->flags & NOT_UNTIL ) 298 continue; 299 } 300 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 e->flags &= ~RUN_AT; 310 e->lastrun = TargetTime; 311 job_add(e, u); 312 } 313 } 314 } 315 316 last_time = TargetTime; 317 lasttm = *tm; 318} 319 320 321/* the task here is to figure out how long it's going to be until :00 of the 322 * following minute and initialize TargetTime to this value. TargetTime 323 * will subsequently slide 60 seconds at a time, with correction applied 324 * implicitly in cron_sleep(). it would be nice to let cron execute in 325 * the "current minute" before going to sleep, but by restarting cron you 326 * could then get it to execute a given minute's jobs more than once. 327 * instead we have the chance of missing a minute's jobs completely, but 328 * that's something sysadmin's know to expect what with crashing computers.. 329 */ 330static void 331cron_sync() { 332 register struct tm *tm; 333 334 TargetTime = time((time_t*)0); 335 tm = localtime(&TargetTime); 336 TargetTime += (60 - tm->tm_sec); 337} 338 339 340static void 341cron_sleep(db) 342 cron_db *db; 343{ 344 int seconds_to_wait = 0; 345 346 /* 347 * Loop until we reach the top of the next minute, sleep when possible. 348 */ 349 350 for (;;) { 351 seconds_to_wait = (int) (TargetTime - time((time_t*)0)); 352 353 /* 354 * If the seconds_to_wait value is insane, jump the cron 355 */ 356 357 if (seconds_to_wait < -600 || seconds_to_wait > 600) { 358 cron_clean(db); 359 cron_sync(); 360 continue; 361 } 362 363 Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n", 364 getpid(), (long)TargetTime, seconds_to_wait)) 365 366 /* 367 * If we've run out of wait time or there are no jobs left 368 * to run, break 369 */ 370 371 if (seconds_to_wait <= 0) 372 break; 373 if (job_runqueue() == 0) { 374 Debug(DSCH, ("[%d] sleeping for %d seconds\n", 375 getpid(), seconds_to_wait)) 376 377 sleep(seconds_to_wait); 378 } 379 } 380} 381 382 383/* if the time was changed abruptly, clear the flags related 384 * to the daylight time switch handling to avoid strange effects 385 */ 386 387static void 388cron_clean(db) 389 cron_db *db; 390{ 391 user *u; 392 entry *e; 393 394 last_time = 0; 395 396 for (u = db->head; u != NULL; u = u->next) { 397 for (e = u->crontab; e != NULL; e = e->next) { 398 e->flags &= ~(RUN_AT|NOT_UNTIL); 399 } 400 } 401} 402 403#ifdef USE_SIGCHLD 404static void 405sigchld_handler(int x) 406{ 407 WAIT_T waiter; 408 PID_T pid; 409 410 for (;;) { 411#ifdef POSIX 412 pid = waitpid(-1, &waiter, WNOHANG); 413#else 414 pid = wait3(&waiter, WNOHANG, (struct rusage *)0); 415#endif 416 switch (pid) { 417 case -1: 418 Debug(DPROC, 419 ("[%d] sigchld...no children\n", getpid())) 420 return; 421 case 0: 422 Debug(DPROC, 423 ("[%d] sigchld...no dead kids\n", getpid())) 424 return; 425 default: 426 Debug(DPROC, 427 ("[%d] sigchld...pid #%d died, stat=%d\n", 428 getpid(), pid, WEXITSTATUS(waiter))) 429 } 430 } 431} 432#endif /*USE_SIGCHLD*/ 433 434 435static void 436sighup_handler(int x) 437{ 438 log_close(); 439} 440 441 442static void 443parse_args(argc, argv) 444 int argc; 445 char *argv[]; 446{ 447 int argch; 448 char *endp; 449 450 while ((argch = getopt(argc, argv, "j:J:m:osx:")) != -1) { 451 switch (argch) { 452 case 'j': 453 Jitter = strtoul(optarg, &endp, 10); 454 if (*optarg == '\0' || *endp != '\0' || Jitter > 60) 455 errx(ERROR_EXIT, 456 "bad value for jitter: %s", optarg); 457 break; 458 case 'J': 459 RootJitter = strtoul(optarg, &endp, 10); 460 if (*optarg == '\0' || *endp != '\0' || RootJitter > 60) 461 errx(ERROR_EXIT, 462 "bad value for root jitter: %s", optarg); 463 break; 464 case 'm': 465 defmailto = optarg; 466 break; 467 case 'o': 468 dst_enabled = 0; 469 break; 470 case 's': 471 dst_enabled = 1; 472 break; 473 case 'x': 474 if (!set_debug_flags(optarg)) 475 usage(); 476 break; 477 default: 478 usage(); 479 } 480 } 481} 482 483