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 * $FreeBSD: src/usr.sbin/cron/cron/cron.c,v 1.9.2.2 2001/05/28 23:37:26 babkin Exp $ 18 * $DragonFly: src/usr.sbin/cron/cron/cron.c,v 1.3 2003/11/03 19:31:36 eirikn Exp $ 19 */ 20 21 #define MAIN_PROGRAM 22 23 24 #include "cron.h" 25 #include <sys/signal.h> 26 #if SYS_TIME_H 27 # include <sys/time.h> 28 #else 29 # include <time.h> 30 #endif 31 32 33 static void usage(void), 34 run_reboot_jobs(cron_db *), 35 cron_tick(cron_db *), 36 cron_sync(void), 37 cron_sleep(cron_db *), 38 cron_clean(cron_db *), 39 #ifdef USE_SIGCHLD 40 sigchld_handler(int), 41 #endif 42 sighup_handler(int), 43 parse_args(int c, char *v[]); 44 45 static time_t last_time = 0; 46 static int dst_enabled = 0; 47 48 static void 49 usage() { 50 char **dflags; 51 52 fprintf(stderr, "usage: cron [-s] [-o] [-x debugflag[,...]]\n"); 53 fprintf(stderr, "\ndebugflags: "); 54 55 for(dflags = DebugFlagNames; *dflags; dflags++) { 56 fprintf(stderr, "%s ", *dflags); 57 } 58 fprintf(stderr, "\n"); 59 60 exit(ERROR_EXIT); 61 } 62 63 64 int 65 main(argc, argv) 66 int argc; 67 char *argv[]; 68 { 69 cron_db database; 70 71 ProgramName = argv[0]; 72 73 #if defined(BSD) 74 setlinebuf(stdout); 75 setlinebuf(stderr); 76 #endif 77 78 parse_args(argc, argv); 79 80 #ifdef USE_SIGCHLD 81 (void) signal(SIGCHLD, sigchld_handler); 82 #else 83 (void) signal(SIGCLD, SIG_IGN); 84 #endif 85 (void) signal(SIGHUP, sighup_handler); 86 87 acquire_daemonlock(0); 88 set_cron_uid(); 89 set_cron_cwd(); 90 91 #if defined(POSIX) 92 setenv("PATH", _PATH_DEFPATH, 1); 93 #endif 94 95 /* if there are no debug flags turned on, fork as a daemon should. 96 */ 97 # if DEBUGGING 98 if (DebugFlags) { 99 # else 100 if (0) { 101 # endif 102 (void) fprintf(stderr, "[%d] cron started\n", getpid()); 103 } else { 104 if (daemon(1, 0) == -1) { 105 log_it("CRON",getpid(),"DEATH","can't become daemon"); 106 exit(0); 107 } 108 } 109 110 acquire_daemonlock(0); 111 database.head = NULL; 112 database.tail = NULL; 113 database.mtime = (time_t) 0; 114 load_database(&database); 115 run_reboot_jobs(&database); 116 cron_sync(); 117 while (TRUE) { 118 # if DEBUGGING 119 /* if (!(DebugFlags & DTEST)) */ 120 # endif /*DEBUGGING*/ 121 cron_sleep(&database); 122 123 load_database(&database); 124 125 /* do this iteration 126 */ 127 cron_tick(&database); 128 129 /* sleep 1 minute 130 */ 131 TargetTime += 60; 132 } 133 } 134 135 136 static void 137 run_reboot_jobs(db) 138 cron_db *db; 139 { 140 register user *u; 141 register entry *e; 142 143 for (u = db->head; u != NULL; u = u->next) { 144 for (e = u->crontab; e != NULL; e = e->next) { 145 if (e->flags & WHEN_REBOOT) { 146 job_add(e, u); 147 } 148 } 149 } 150 (void) job_runqueue(); 151 } 152 153 154 static void 155 cron_tick(db) 156 cron_db *db; 157 { 158 static struct tm lasttm; 159 static time_t diff = 0, /* time difference in seconds from the last offset change */ 160 difflimit = 0; /* end point for the time zone correction */ 161 struct tm otztm; /* time in the old time zone */ 162 int otzminute, otzhour, otzdom, otzmonth, otzdow; 163 register struct tm *tm = localtime(&TargetTime); 164 register int minute, hour, dom, month, dow; 165 register user *u; 166 register entry *e; 167 168 /* make 0-based values out of these so we can use them as indicies 169 */ 170 minute = tm->tm_min -FIRST_MINUTE; 171 hour = tm->tm_hour -FIRST_HOUR; 172 dom = tm->tm_mday -FIRST_DOM; 173 month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 174 dow = tm->tm_wday -FIRST_DOW; 175 176 Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n", 177 getpid(), minute, hour, dom, month, dow)) 178 179 if (dst_enabled && last_time != 0 180 && TargetTime > last_time /* exclude stepping back */ 181 && tm->tm_gmtoff != lasttm.tm_gmtoff ) { 182 183 diff = tm->tm_gmtoff - lasttm.tm_gmtoff; 184 185 if ( diff > 0 ) { /* ST->DST */ 186 /* mark jobs for an earlier run */ 187 difflimit = TargetTime + diff; 188 for (u = db->head; u != NULL; u = u->next) { 189 for (e = u->crontab; e != NULL; e = e->next) { 190 e->flags &= ~NOT_UNTIL; 191 if ( e->lastrun >= TargetTime ) 192 e->lastrun = 0; 193 /* not include the ends of hourly ranges */ 194 if ( e->lastrun < TargetTime - 3600 ) 195 e->flags |= RUN_AT; 196 else 197 e->flags &= ~RUN_AT; 198 } 199 } 200 } else { /* diff < 0 : DST->ST */ 201 /* mark jobs for skipping */ 202 difflimit = TargetTime - diff; 203 for (u = db->head; u != NULL; u = u->next) { 204 for (e = u->crontab; e != NULL; e = e->next) { 205 e->flags |= NOT_UNTIL; 206 e->flags &= ~RUN_AT; 207 } 208 } 209 } 210 } 211 212 if (diff != 0) { 213 /* if the time was reset of the end of special zone is reached */ 214 if (last_time == 0 || TargetTime >= difflimit) { 215 /* disable the TZ switch checks */ 216 diff = 0; 217 difflimit = 0; 218 for (u = db->head; u != NULL; u = u->next) { 219 for (e = u->crontab; e != NULL; e = e->next) { 220 e->flags &= ~(RUN_AT|NOT_UNTIL); 221 } 222 } 223 } else { 224 /* get the time in the old time zone */ 225 time_t difftime = TargetTime + tm->tm_gmtoff - diff; 226 gmtime_r(&difftime, &otztm); 227 228 /* make 0-based values out of these so we can use them as indicies 229 */ 230 otzminute = otztm.tm_min -FIRST_MINUTE; 231 otzhour = otztm.tm_hour -FIRST_HOUR; 232 otzdom = otztm.tm_mday -FIRST_DOM; 233 otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; 234 otzdow = otztm.tm_wday -FIRST_DOW; 235 } 236 } 237 238 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the 239 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* 240 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this 241 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. 242 * like many bizarre things, it's the standard. 243 */ 244 for (u = db->head; u != NULL; u = u->next) { 245 for (e = u->crontab; e != NULL; e = e->next) { 246 Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n", 247 env_get("LOGNAME", e->envp), 248 e->uid, e->gid, e->cmd)) 249 250 if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) { 251 if (bit_test(e->minute, otzminute) 252 && bit_test(e->hour, otzhour) 253 && bit_test(e->month, otzmonth) 254 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 255 ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom)) 256 : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom)) 257 ) 258 ) { 259 if ( e->flags & RUN_AT ) { 260 e->flags &= ~RUN_AT; 261 e->lastrun = TargetTime; 262 job_add(e, u); 263 continue; 264 } else 265 e->flags &= ~NOT_UNTIL; 266 } else if ( e->flags & NOT_UNTIL ) 267 continue; 268 } 269 270 if (bit_test(e->minute, minute) 271 && bit_test(e->hour, hour) 272 && bit_test(e->month, month) 273 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) 274 ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) 275 : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) 276 ) 277 ) { 278 e->flags &= ~RUN_AT; 279 e->lastrun = TargetTime; 280 job_add(e, u); 281 } 282 } 283 } 284 285 last_time = TargetTime; 286 lasttm = *tm; 287 } 288 289 290 /* the task here is to figure out how long it's going to be until :00 of the 291 * following minute and initialize TargetTime to this value. TargetTime 292 * will subsequently slide 60 seconds at a time, with correction applied 293 * implicitly in cron_sleep(). it would be nice to let cron execute in 294 * the "current minute" before going to sleep, but by restarting cron you 295 * could then get it to execute a given minute's jobs more than once. 296 * instead we have the chance of missing a minute's jobs completely, but 297 * that's something sysadmin's know to expect what with crashing computers.. 298 */ 299 static void 300 cron_sync() { 301 register struct tm *tm; 302 303 TargetTime = time((time_t*)0); 304 tm = localtime(&TargetTime); 305 TargetTime += (60 - tm->tm_sec); 306 } 307 308 309 static void 310 cron_sleep(db) 311 cron_db *db; 312 { 313 int seconds_to_wait = 0; 314 315 /* 316 * Loop until we reach the top of the next minute, sleep when possible. 317 */ 318 319 for (;;) { 320 seconds_to_wait = (int) (TargetTime - time((time_t*)0)); 321 322 /* 323 * If the seconds_to_wait value is insane, jump the cron 324 */ 325 326 if (seconds_to_wait < -600 || seconds_to_wait > 600) { 327 cron_clean(db); 328 cron_sync(); 329 continue; 330 } 331 332 Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n", 333 getpid(), (long)TargetTime, seconds_to_wait)) 334 335 /* 336 * If we've run out of wait time or there are no jobs left 337 * to run, break 338 */ 339 340 if (seconds_to_wait <= 0) 341 break; 342 if (job_runqueue() == 0) { 343 Debug(DSCH, ("[%d] sleeping for %d seconds\n", 344 getpid(), seconds_to_wait)) 345 346 sleep(seconds_to_wait); 347 } 348 } 349 } 350 351 352 /* if the time was changed abruptly, clear the flags related 353 * to the daylight time switch handling to avoid strange effects 354 */ 355 356 static void 357 cron_clean(db) 358 cron_db *db; 359 { 360 user *u; 361 entry *e; 362 363 last_time = 0; 364 365 for (u = db->head; u != NULL; u = u->next) { 366 for (e = u->crontab; e != NULL; e = e->next) { 367 e->flags &= ~(RUN_AT|NOT_UNTIL); 368 } 369 } 370 } 371 372 #ifdef USE_SIGCHLD 373 static void 374 sigchld_handler(x) { 375 WAIT_T waiter; 376 PID_T pid; 377 378 for (;;) { 379 #ifdef POSIX 380 pid = waitpid(-1, &waiter, WNOHANG); 381 #else 382 pid = wait3(&waiter, WNOHANG, (struct rusage *)0); 383 #endif 384 switch (pid) { 385 case -1: 386 Debug(DPROC, 387 ("[%d] sigchld...no children\n", getpid())) 388 return; 389 case 0: 390 Debug(DPROC, 391 ("[%d] sigchld...no dead kids\n", getpid())) 392 return; 393 default: 394 Debug(DPROC, 395 ("[%d] sigchld...pid #%d died, stat=%d\n", 396 getpid(), pid, WEXITSTATUS(waiter))) 397 } 398 } 399 } 400 #endif /*USE_SIGCHLD*/ 401 402 403 static void 404 sighup_handler(x) { 405 log_close(); 406 } 407 408 409 static void 410 parse_args(argc, argv) 411 int argc; 412 char *argv[]; 413 { 414 int argch; 415 416 while ((argch = getopt(argc, argv, "osx:")) != -1) { 417 switch (argch) { 418 case 'o': 419 dst_enabled = 0; 420 break; 421 case 's': 422 dst_enabled = 1; 423 break; 424 case 'x': 425 if (!set_debug_flags(optarg)) 426 usage(); 427 break; 428 default: 429 usage(); 430 } 431 } 432 } 433 434