1 /* tab.c - process crontabs and create in-core crontab data 2 * Author: Kees J. Bot 3 * 7 Dec 1996 4 * Changes: 5 * 17 Jul 2000 by Philip Homburg 6 * - Tab_reschedule() rewritten (and fixed). 7 */ 8 #define nil ((void*)0) 9 #include <sys/types.h> 10 #include <assert.h> 11 #include <stdio.h> 12 #include <unistd.h> 13 #include <fcntl.h> 14 #include <stdlib.h> 15 #include <string.h> 16 #include <errno.h> 17 #include <limits.h> 18 #include <time.h> 19 #include <dirent.h> 20 #include <sys/stat.h> 21 #include "misc.h" 22 #include "tab.h" 23 24 static int nextbit(bitmap_t map, int bit) 25 /* Return the next bit set in 'map' from 'bit' onwards, cyclic. */ 26 { 27 for (;;) { 28 bit= (bit+1) & 63; 29 if (bit_isset(map, bit)) break; 30 } 31 return bit; 32 } 33 34 void tab_reschedule(cronjob_t *job) 35 /* Reschedule one job. Compute the next time to run the job in job->rtime. 36 */ 37 { 38 struct tm prevtm, nexttm, tmptm; 39 time_t nodst_rtime, dst_rtime; 40 41 /* AT jobs are only run once. */ 42 if (job->atjob) { job->rtime= NEVER; return; } 43 44 /* Was the job scheduled late last time? */ 45 if (job->late) job->rtime= now; 46 47 prevtm= *localtime(&job->rtime); 48 prevtm.tm_sec= 0; 49 50 nexttm= prevtm; 51 nexttm.tm_min++; /* Minimal increment */ 52 53 for (;;) 54 { 55 if (nexttm.tm_min > 59) 56 { 57 nexttm.tm_min= 0; 58 nexttm.tm_hour++; 59 } 60 if (nexttm.tm_hour > 23) 61 { 62 nexttm.tm_min= 0; 63 nexttm.tm_hour= 0; 64 nexttm.tm_mday++; 65 } 66 if (nexttm.tm_mday > 31) 67 { 68 nexttm.tm_hour= nexttm.tm_min= 0; 69 nexttm.tm_mday= 1; 70 nexttm.tm_mon++; 71 } 72 if (nexttm.tm_mon >= 12) 73 { 74 nexttm.tm_hour= nexttm.tm_min= 0; 75 nexttm.tm_mday= 1; 76 nexttm.tm_mon= 0; 77 nexttm.tm_year++; 78 } 79 80 /* Verify tm_year. A crontab entry cannot restrict tm_year 81 * directly. However, certain dates (such as Feb, 29th) do 82 * not occur every year. We limit the difference between 83 * nexttm.tm_year and prevtm.tm_year to detect impossible dates 84 * (e.g, Feb, 31st). In theory every date occurs within a 85 * period of 4 years. However, some years at the end of a 86 * century are not leap years (e.g, the year 2100). An extra 87 * factor of 2 should be enough. 88 */ 89 if (nexttm.tm_year-prevtm.tm_year > 2*4) 90 { 91 job->rtime= NEVER; 92 return; /* Impossible combination */ 93 } 94 95 if (!job->do_wday) 96 { 97 /* Verify the mon and mday fields. If do_wday and 98 * do_mday are both true we have to merge both 99 * schedules. This is done after the call to mktime. 100 */ 101 if (!bit_isset(job->mon, nexttm.tm_mon)) 102 { 103 /* Clear other fields */ 104 nexttm.tm_mday= 1; 105 nexttm.tm_hour= nexttm.tm_min= 0; 106 107 nexttm.tm_mon++; 108 continue; 109 } 110 111 /* Verify mday */ 112 if (!bit_isset(job->mday, nexttm.tm_mday)) 113 { 114 /* Clear other fields */ 115 nexttm.tm_hour= nexttm.tm_min= 0; 116 117 nexttm.tm_mday++; 118 continue; 119 } 120 } 121 122 /* Verify hour */ 123 if (!bit_isset(job->hour, nexttm.tm_hour)) 124 { 125 /* Clear tm_min field */ 126 nexttm.tm_min= 0; 127 128 nexttm.tm_hour++; 129 continue; 130 } 131 132 /* Verify min */ 133 if (!bit_isset(job->min, nexttm.tm_min)) 134 { 135 nexttm.tm_min++; 136 continue; 137 } 138 139 /* Verify that we don't have any problem with DST. Try 140 * tm_isdst=0 first. */ 141 tmptm= nexttm; 142 tmptm.tm_isdst= 0; 143 #if 0 144 fprintf(stderr, 145 "tab_reschedule: trying %04d-%02d-%02d %02d:%02d:%02d isdst=0\n", 146 1900+nexttm.tm_year, nexttm.tm_mon+1, 147 nexttm.tm_mday, nexttm.tm_hour, 148 nexttm.tm_min, nexttm.tm_sec); 149 #endif 150 nodst_rtime= job->rtime= mktime(&tmptm); 151 if (job->rtime == -1) { 152 /* This should not happen. */ 153 log(LOG_ERR, 154 "mktime failed for %04d-%02d-%02d %02d:%02d:%02d", 155 1900+nexttm.tm_year, nexttm.tm_mon+1, 156 nexttm.tm_mday, nexttm.tm_hour, 157 nexttm.tm_min, nexttm.tm_sec); 158 job->rtime= NEVER; 159 return; 160 } 161 tmptm= *localtime(&job->rtime); 162 if (tmptm.tm_hour != nexttm.tm_hour || 163 tmptm.tm_min != nexttm.tm_min) 164 { 165 assert(tmptm.tm_isdst); 166 tmptm= nexttm; 167 tmptm.tm_isdst= 1; 168 #if 0 169 fprintf(stderr, 170 "tab_reschedule: trying %04d-%02d-%02d %02d:%02d:%02d isdst=1\n", 171 1900+nexttm.tm_year, nexttm.tm_mon+1, 172 nexttm.tm_mday, nexttm.tm_hour, 173 nexttm.tm_min, nexttm.tm_sec); 174 #endif 175 dst_rtime= job->rtime= mktime(&tmptm); 176 if (job->rtime == -1) { 177 /* This should not happen. */ 178 log(LOG_ERR, 179 "mktime failed for %04d-%02d-%02d %02d:%02d:%02d\n", 180 1900+nexttm.tm_year, nexttm.tm_mon+1, 181 nexttm.tm_mday, nexttm.tm_hour, 182 nexttm.tm_min, nexttm.tm_sec); 183 job->rtime= NEVER; 184 return; 185 } 186 tmptm= *localtime(&job->rtime); 187 if (tmptm.tm_hour != nexttm.tm_hour || 188 tmptm.tm_min != nexttm.tm_min) 189 { 190 assert(!tmptm.tm_isdst); 191 /* We have a problem. This time neither 192 * exists with DST nor without DST. 193 * Use the latest time, which should be 194 * nodst_rtime. 195 */ 196 assert(nodst_rtime > dst_rtime); 197 job->rtime= nodst_rtime; 198 #if 0 199 fprintf(stderr, 200 "During DST trans. %04d-%02d-%02d %02d:%02d:%02d\n", 201 1900+nexttm.tm_year, nexttm.tm_mon+1, 202 nexttm.tm_mday, nexttm.tm_hour, 203 nexttm.tm_min, nexttm.tm_sec); 204 #endif 205 } 206 } 207 208 /* Verify this the combination (tm_year, tm_mon, tm_mday). */ 209 if (tmptm.tm_mday != nexttm.tm_mday || 210 tmptm.tm_mon != nexttm.tm_mon || 211 tmptm.tm_year != nexttm.tm_year) 212 { 213 /* Wrong day */ 214 #if 0 215 fprintf(stderr, "Wrong day\n"); 216 #endif 217 nexttm.tm_hour= nexttm.tm_min= 0; 218 nexttm.tm_mday++; 219 continue; 220 } 221 222 /* Check tm_wday */ 223 if (job->do_wday && bit_isset(job->wday, tmptm.tm_wday)) 224 { 225 /* OK, wday matched */ 226 break; 227 } 228 229 /* Check tm_mday */ 230 if (job->do_mday && bit_isset(job->mon, tmptm.tm_mon) && 231 bit_isset(job->mday, tmptm.tm_mday)) 232 { 233 /* OK, mon and mday matched */ 234 break; 235 } 236 237 if (!job->do_wday && !job->do_mday) 238 { 239 /* No need to match wday and mday */ 240 break; 241 } 242 243 /* Wrong day */ 244 #if 0 245 fprintf(stderr, "Wrong mon+mday or wday\n"); 246 #endif 247 nexttm.tm_hour= nexttm.tm_min= 0; 248 nexttm.tm_mday++; 249 } 250 #if 0 251 fprintf(stderr, "Using %04d-%02d-%02d %02d:%02d:%02d \n", 252 1900+nexttm.tm_year, nexttm.tm_mon+1, nexttm.tm_mday, 253 nexttm.tm_hour, nexttm.tm_min, nexttm.tm_sec); 254 tmptm= *localtime(&job->rtime); 255 fprintf(stderr, "Act. %04d-%02d-%02d %02d:%02d:%02d isdst=%d\n", 256 1900+tmptm.tm_year, tmptm.tm_mon+1, tmptm.tm_mday, 257 tmptm.tm_hour, tmptm.tm_min, tmptm.tm_sec, 258 tmptm.tm_isdst); 259 #endif 260 261 262 /* Is job issuing lagging behind with the progress of time? */ 263 job->late= (job->rtime < now); 264 265 /* The result is in job->rtime. */ 266 if (job->rtime < next) next= job->rtime; 267 } 268 269 #define isdigit(c) ((unsigned) ((c) - '0') < 10) 270 271 static char *get_token(char **ptr) 272 /* Return a pointer to the next token in a string. Move *ptr to the end of 273 * the token, and return a pointer to the start. If *ptr == start of token 274 * then we're stuck against a newline or end of string. 275 */ 276 { 277 char *start, *p; 278 279 p= *ptr; 280 while (*p == ' ' || *p == '\t') p++; 281 282 start= p; 283 while (*p != ' ' && *p != '\t' && *p != '\n' && *p != 0) p++; 284 *ptr= p; 285 return start; 286 } 287 288 static int range_parse(char *file, char *data, bitmap_t map, 289 int min, int max, int *wildcard) 290 /* Parse a comma separated series of 'n', 'n-m' or 'n:m' numbers. 'n' 291 * includes number 'n' in the bit map, 'n-m' includes all numbers between 292 * 'n' and 'm' inclusive, and 'n:m' includes 'n+k*m' for any k if in range. 293 * Numbers must fall between 'min' and 'max'. A '*' means all numbers. A 294 * '?' is allowed as a synonym for the current minute, which only makes sense 295 * in the minute field, i.e. max must be 59. Return true iff parsed ok. 296 */ 297 { 298 char *p; 299 int end; 300 int n, m; 301 302 /* Clear all bits. */ 303 for (n= 0; n < 8; n++) map[n]= 0; 304 305 p= data; 306 while (*p != ' ' && *p != '\t' && *p != '\n' && *p != 0) p++; 307 end= *p; 308 *p= 0; 309 p= data; 310 311 if (*p == 0) { 312 log(LOG_ERR, "%s: not enough time fields\n", file); 313 return 0; 314 } 315 316 /* Is it a '*'? */ 317 if (p[0] == '*' && p[1] == 0) { 318 for (n= min; n <= max; n++) bit_set(map, n); 319 p[1]= end; 320 *wildcard= 1; 321 return 1; 322 } 323 *wildcard= 0; 324 325 /* Parse a comma separated series of numbers or ranges. */ 326 for (;;) { 327 if (*p == '?' && max == 59 && p[1] != '-') { 328 n= localtime(&now)->tm_min; 329 p++; 330 } else { 331 if (!isdigit(*p)) goto syntax; 332 n= 0; 333 do { 334 n= 10 * n + (*p++ - '0'); 335 if (n > max) goto range; 336 } while (isdigit(*p)); 337 } 338 if (n < min) goto range; 339 340 if (*p == '-') { /* A range of the form 'n-m'? */ 341 p++; 342 if (!isdigit(*p)) goto syntax; 343 m= 0; 344 do { 345 m= 10 * m + (*p++ - '0'); 346 if (m > max) goto range; 347 } while (isdigit(*p)); 348 if (m < n) goto range; 349 do { 350 bit_set(map, n); 351 } while (++n <= m); 352 } else 353 if (*p == ':') { /* A repeat of the form 'n:m'? */ 354 p++; 355 if (!isdigit(*p)) goto syntax; 356 m= 0; 357 do { 358 m= 10 * m + (*p++ - '0'); 359 if (m > (max-min+1)) goto range; 360 } while (isdigit(*p)); 361 if (m == 0) goto range; 362 while (n >= min) n-= m; 363 while ((n+= m) <= max) bit_set(map, n); 364 } else { 365 /* Simply a number */ 366 bit_set(map, n); 367 } 368 if (*p == 0) break; 369 if (*p++ != ',') goto syntax; 370 } 371 *p= end; 372 return 1; 373 syntax: 374 log(LOG_ERR, "%s: field '%s': bad syntax for a %d-%d time field\n", 375 file, data, min, max); 376 return 0; 377 range: 378 log(LOG_ERR, "%s: field '%s': values out of the %d-%d allowed range\n", 379 file, data, min, max); 380 return 0; 381 } 382 383 void tab_parse(char *file, char *user) 384 /* Parse a crontab file and add its data to the tables. Handle errors by 385 * yourself. Table is owned by 'user' if non-null. 386 */ 387 { 388 crontab_t **atab, *tab; 389 cronjob_t **ajob, *job; 390 int fd; 391 struct stat st; 392 char *p, *q; 393 size_t n; 394 ssize_t r; 395 int ok, wc; 396 397 for (atab= &crontabs; (tab= *atab) != nil; atab= &tab->next) { 398 if (strcmp(file, tab->file) == 0) break; 399 } 400 401 /* Try to open the file. */ 402 if ((fd= open(file, O_RDONLY)) < 0 || fstat(fd, &st) < 0) { 403 if (errno != ENOENT) { 404 log(LOG_ERR, "%s: %s\n", file, strerror(errno)); 405 } 406 if (fd != -1) close(fd); 407 return; 408 } 409 410 /* Forget it if the file is awfully big. */ 411 if (st.st_size > TAB_MAX) { 412 log(LOG_ERR, "%s: %lu bytes is bigger than my %lu limit\n", 413 file, 414 (unsigned long) st.st_size, 415 (unsigned long) TAB_MAX); 416 return; 417 } 418 419 /* If the file is the same as before then don't bother. */ 420 if (tab != nil && st.st_mtime == tab->mtime) { 421 close(fd); 422 tab->current= 1; 423 return; 424 } 425 426 /* Create a new table structure. */ 427 tab= allocate(sizeof(*tab)); 428 tab->file= allocate((strlen(file) + 1) * sizeof(tab->file[0])); 429 strcpy(tab->file, file); 430 tab->user= nil; 431 if (user != nil) { 432 tab->user= allocate((strlen(user) + 1) * sizeof(tab->user[0])); 433 strcpy(tab->user, user); 434 } 435 tab->data= allocate((st.st_size + 1) * sizeof(tab->data[0])); 436 tab->jobs= nil; 437 tab->mtime= st.st_mtime; 438 tab->current= 0; 439 tab->next= *atab; 440 *atab= tab; 441 442 /* Pull a new table in core. */ 443 n= 0; 444 while (n < st.st_size) { 445 if ((r = read(fd, tab->data + n, st.st_size - n)) < 0) { 446 log(LOG_CRIT, "%s: %s", file, strerror(errno)); 447 close(fd); 448 return; 449 } 450 if (r == 0) break; 451 n+= r; 452 } 453 close(fd); 454 tab->data[n]= 0; 455 if (strlen(tab->data) < n) { 456 log(LOG_ERR, "%s contains a null character\n", file); 457 return; 458 } 459 460 /* Parse the file. */ 461 ajob= &tab->jobs; 462 p= tab->data; 463 ok= 1; 464 while (ok && *p != 0) { 465 q= get_token(&p); 466 if (*q == '#' || q == p) { 467 /* Comment or empty. */ 468 while (*p != 0 && *p++ != '\n') {} 469 continue; 470 } 471 472 /* One new job coming up. */ 473 *ajob= job= allocate(sizeof(*job)); 474 *(ajob= &job->next)= nil; 475 job->tab= tab; 476 477 if (!range_parse(file, q, job->min, 0, 59, &wc)) { 478 ok= 0; 479 break; 480 } 481 482 q= get_token(&p); 483 if (!range_parse(file, q, job->hour, 0, 23, &wc)) { 484 ok= 0; 485 break; 486 } 487 488 q= get_token(&p); 489 if (!range_parse(file, q, job->mday, 1, 31, &wc)) { 490 ok= 0; 491 break; 492 } 493 job->do_mday= !wc; 494 495 q= get_token(&p); 496 if (!range_parse(file, q, job->mon, 1, 12, &wc)) { 497 ok= 0; 498 break; 499 } 500 job->do_mday |= !wc; 501 502 q= get_token(&p); 503 if (!range_parse(file, q, job->wday, 0, 7, &wc)) { 504 ok= 0; 505 break; 506 } 507 job->do_wday= !wc; 508 509 /* 7 is Sunday, but 0 is a common mistake because it is in the 510 * tm_wday range. We allow and even prefer it internally. 511 */ 512 if (bit_isset(job->wday, 7)) { 513 bit_clr(job->wday, 7); 514 bit_set(job->wday, 0); 515 } 516 517 /* The month range is 1-12, but tm_mon likes 0-11. */ 518 job->mon[0] >>= 1; 519 if (bit_isset(job->mon, 8)) bit_set(job->mon, 7); 520 job->mon[1] >>= 1; 521 522 /* Scan for options. */ 523 job->user= nil; 524 while (q= get_token(&p), *q == '-') { 525 q++; 526 if (q[0] == '-' && q+1 == p) { 527 /* -- */ 528 q= get_token(&p); 529 break; 530 } 531 while (q < p) switch (*q++) { 532 case 'u': 533 if (q == p) q= get_token(&p); 534 if (q == p) goto usage; 535 memmove(q-1, q, p-q); /* gross... */ 536 p[-1]= 0; 537 job->user= q-1; 538 q= p; 539 break; 540 default: 541 usage: 542 log(LOG_ERR, 543 "%s: bad option -%c, good options are: -u username\n", 544 file, q[-1]); 545 ok= 0; 546 goto endtab; 547 } 548 } 549 550 /* A crontab owned by a user can only do things as that user. */ 551 if (tab->user != nil) job->user= tab->user; 552 553 /* Inspect the first character of the command. */ 554 job->cmd= q; 555 if (q == p || *q == '#') { 556 /* Rest of the line is empty, i.e. the commands are on 557 * the following lines indented by one tab. 558 */ 559 while (*p != 0 && *p++ != '\n') {} 560 if (*p++ != '\t') { 561 log(LOG_ERR, "%s: contains an empty command\n", 562 file); 563 ok= 0; 564 goto endtab; 565 } 566 while (*p != 0) { 567 if ((*q = *p++) == '\n') { 568 if (*p != '\t') break; 569 p++; 570 } 571 q++; 572 } 573 } else { 574 /* The command is on this line. Alas we must now be 575 * backwards compatible and change %'s to newlines. 576 */ 577 p= q; 578 while (*p != 0) { 579 if ((*q = *p++) == '\n') break; 580 if (*q == '%') *q= '\n'; 581 q++; 582 } 583 } 584 *q= 0; 585 job->rtime= now; 586 job->late= 0; /* It is on time. */ 587 job->atjob= 0; /* True cron job. */ 588 job->pid= IDLE_PID; /* Not running yet. */ 589 tab_reschedule(job); /* Compute next time to run. */ 590 } 591 endtab: 592 593 if (ok) tab->current= 1; 594 } 595 596 void tab_find_atjob(char *atdir) 597 /* Find the first to be executed AT job and kludge up an crontab job for it. 598 * We set tab->file to "atdir/jobname", tab->data to "atdir/past/jobname", 599 * and job->cmd to "jobname". 600 */ 601 { 602 DIR *spool; 603 struct dirent *entry; 604 time_t t0, t1; 605 struct tm tmnow, tmt1; 606 static char template[] = "96.365.1546.00"; 607 char firstjob[sizeof(template)]; 608 int i; 609 crontab_t *tab; 610 cronjob_t *job; 611 612 if ((spool= opendir(atdir)) == nil) return; 613 614 tmnow= *localtime(&now); 615 t0= NEVER; 616 617 while ((entry= readdir(spool)) != nil) { 618 /* Check if the name fits the template. */ 619 for (i= 0; template[i] != 0; i++) { 620 if (template[i] == '.') { 621 if (entry->d_name[i] != '.') break; 622 } else { 623 if (!isdigit(entry->d_name[i])) break; 624 } 625 } 626 if (template[i] != 0 || entry->d_name[i] != 0) continue; 627 628 /* Convert the name to a time. Careful with the century. */ 629 memset(&tmt1, 0, sizeof(tmt1)); 630 tmt1.tm_year= atoi(entry->d_name+0); 631 while (tmt1.tm_year < tmnow.tm_year-10) tmt1.tm_year+= 100; 632 tmt1.tm_mday= 1+atoi(entry->d_name+3); 633 tmt1.tm_min= atoi(entry->d_name+7); 634 tmt1.tm_hour= tmt1.tm_min / 100; 635 tmt1.tm_min%= 100; 636 tmt1.tm_isdst= -1; 637 if ((t1= mktime(&tmt1)) == -1) { 638 /* Illegal time? Try in winter time. */ 639 tmt1.tm_isdst= 0; 640 if ((t1= mktime(&tmt1)) == -1) continue; 641 } 642 if (t1 < t0) { 643 t0= t1; 644 strcpy(firstjob, entry->d_name); 645 } 646 } 647 closedir(spool); 648 649 if (t0 == NEVER) return; /* AT job spool is empty. */ 650 651 /* Create new table and job structures. */ 652 tab= allocate(sizeof(*tab)); 653 tab->file= allocate((strlen(atdir) + 1 + sizeof(template)) 654 * sizeof(tab->file[0])); 655 strcpy(tab->file, atdir); 656 strcat(tab->file, "/"); 657 strcat(tab->file, firstjob); 658 tab->data= allocate((strlen(atdir) + 6 + sizeof(template)) 659 * sizeof(tab->data[0])); 660 strcpy(tab->data, atdir); 661 strcat(tab->data, "/past/"); 662 strcat(tab->data, firstjob); 663 tab->user= nil; 664 tab->mtime= 0; 665 tab->current= 1; 666 tab->next= crontabs; 667 crontabs= tab; 668 669 tab->jobs= job= allocate(sizeof(*job)); 670 job->next= nil; 671 job->tab= tab; 672 job->user= nil; 673 job->cmd= tab->data + strlen(atdir) + 6; 674 job->rtime= t0; 675 job->late= 0; 676 job->atjob= 1; /* One AT job disguised as a cron job. */ 677 job->pid= IDLE_PID; 678 679 if (job->rtime < next) next= job->rtime; 680 } 681 682 void tab_purge(void) 683 /* Remove table data that is no longer current. E.g. a crontab got removed. 684 */ 685 { 686 crontab_t **atab, *tab; 687 cronjob_t *job; 688 689 atab= &crontabs; 690 while ((tab= *atab) != nil) { 691 if (tab->current) { 692 /* Table is fine. */ 693 tab->current= 0; 694 atab= &tab->next; 695 } else { 696 /* Table is not, or no longer valid; delete. */ 697 *atab= tab->next; 698 while ((job= tab->jobs) != nil) { 699 tab->jobs= job->next; 700 deallocate(job); 701 } 702 deallocate(tab->data); 703 deallocate(tab->file); 704 deallocate(tab->user); 705 deallocate(tab); 706 } 707 } 708 } 709 710 static cronjob_t *reap_or_find(pid_t pid) 711 /* Find a finished job or search for the next one to run. */ 712 { 713 crontab_t *tab; 714 cronjob_t *job; 715 cronjob_t *nextjob; 716 717 nextjob= nil; 718 next= NEVER; 719 for (tab= crontabs; tab != nil; tab= tab->next) { 720 for (job= tab->jobs; job != nil; job= job->next) { 721 if (job->pid == pid) { 722 job->pid= IDLE_PID; 723 tab_reschedule(job); 724 } 725 if (job->pid != IDLE_PID) continue; 726 if (job->rtime < next) next= job->rtime; 727 if (job->rtime <= now) nextjob= job; 728 } 729 } 730 return nextjob; 731 } 732 733 void tab_reap_job(pid_t pid) 734 /* A job has finished. Try to find it among the crontab data and reschedule 735 * it. Recompute time next to run a job. 736 */ 737 { 738 (void) reap_or_find(pid); 739 } 740 741 cronjob_t *tab_nextjob(void) 742 /* Find a job that should be run now. If none are found return null. 743 * Update 'next'. 744 */ 745 { 746 return reap_or_find(NO_PID); 747 } 748 749 static void pr_map(FILE *fp, bitmap_t map) 750 { 751 int last_bit= -1, bit; 752 char *sep; 753 754 sep= ""; 755 for (bit= 0; bit < 64; bit++) { 756 if (bit_isset(map, bit)) { 757 if (last_bit == -1) last_bit= bit; 758 } else { 759 if (last_bit != -1) { 760 fprintf(fp, "%s%d", sep, last_bit); 761 if (last_bit != bit-1) { 762 fprintf(fp, "-%d", bit-1); 763 } 764 last_bit= -1; 765 sep= ","; 766 } 767 } 768 } 769 } 770 771 void tab_print(FILE *fp) 772 /* Print out a stored crontab file for debugging purposes. */ 773 { 774 crontab_t *tab; 775 cronjob_t *job; 776 char *p; 777 struct tm tm; 778 779 for (tab= crontabs; tab != nil; tab= tab->next) { 780 fprintf(fp, "tab->file = \"%s\"\n", tab->file); 781 fprintf(fp, "tab->user = \"%s\"\n", 782 tab->user == nil ? "(root)" : tab->user); 783 fprintf(fp, "tab->mtime = %s", ctime(&tab->mtime)); 784 785 for (job= tab->jobs; job != nil; job= job->next) { 786 if (job->atjob) { 787 fprintf(fp, "AT job"); 788 } else { 789 pr_map(fp, job->min); fputc(' ', fp); 790 pr_map(fp, job->hour); fputc(' ', fp); 791 pr_map(fp, job->mday); fputc(' ', fp); 792 pr_map(fp, job->mon); fputc(' ', fp); 793 pr_map(fp, job->wday); 794 } 795 if (job->user != nil && job->user != tab->user) { 796 fprintf(fp, " -u %s", job->user); 797 } 798 fprintf(fp, "\n\t"); 799 for (p= job->cmd; *p != 0; p++) { 800 fputc(*p, fp); 801 if (*p == '\n') fputc('\t', fp); 802 } 803 fputc('\n', fp); 804 tm= *localtime(&job->rtime); 805 fprintf(fp, " rtime = %.24s%s\n", asctime(&tm), 806 tm.tm_isdst ? " (DST)" : ""); 807 if (job->pid != IDLE_PID) { 808 fprintf(fp, " pid = %ld\n", (long) job->pid); 809 } 810 } 811 } 812 } 813 814 /* 815 * $PchId: tab.c,v 1.5 2000/07/25 22:07:51 philip Exp $ 816 */ 817