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 cronlog(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 cronlog(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 cronlog(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 cronlog(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 cronlog(LOG_ERR, 379 "%s: field '%s': values out of the %d-%d allowed range\n", 380 file, data, min, max); 381 return 0; 382 } 383 384 void tab_parse(char *file, char *user) 385 /* Parse a crontab file and add its data to the tables. Handle errors by 386 * yourself. Table is owned by 'user' if non-null. 387 */ 388 { 389 crontab_t **atab, *tab; 390 cronjob_t **ajob, *job; 391 int fd; 392 struct stat st; 393 char *p, *q; 394 size_t n; 395 ssize_t r; 396 int ok, wc; 397 398 for (atab= &crontabs; (tab= *atab) != nil; atab= &tab->next) { 399 if (strcmp(file, tab->file) == 0) break; 400 } 401 402 /* Try to open the file. */ 403 if ((fd= open(file, O_RDONLY)) < 0 || fstat(fd, &st) < 0) { 404 if (errno != ENOENT) { 405 cronlog(LOG_ERR, "%s: %s\n", file, strerror(errno)); 406 } 407 if (fd != -1) close(fd); 408 return; 409 } 410 411 /* Forget it if the file is awfully big. */ 412 if (st.st_size > TAB_MAX) { 413 cronlog(LOG_ERR, "%s: %lu bytes is bigger than my %lu limit\n", 414 file, 415 (unsigned long) st.st_size, 416 (unsigned long) TAB_MAX); 417 return; 418 } 419 420 /* If the file is the same as before then don't bother. */ 421 if (tab != nil && st.st_mtime == tab->mtime) { 422 close(fd); 423 tab->current= 1; 424 return; 425 } 426 427 /* Create a new table structure. */ 428 tab= allocate(sizeof(*tab)); 429 tab->file= allocate((strlen(file) + 1) * sizeof(tab->file[0])); 430 strcpy(tab->file, file); 431 tab->user= nil; 432 if (user != nil) { 433 tab->user= allocate((strlen(user) + 1) * sizeof(tab->user[0])); 434 strcpy(tab->user, user); 435 } 436 tab->data= allocate((st.st_size + 1) * sizeof(tab->data[0])); 437 tab->jobs= nil; 438 tab->mtime= st.st_mtime; 439 tab->current= 0; 440 tab->next= *atab; 441 *atab= tab; 442 443 /* Pull a new table in core. */ 444 n= 0; 445 while (n < st.st_size) { 446 if ((r = read(fd, tab->data + n, st.st_size - n)) < 0) { 447 cronlog(LOG_CRIT, "%s: %s", file, strerror(errno)); 448 close(fd); 449 return; 450 } 451 if (r == 0) break; 452 n+= r; 453 } 454 close(fd); 455 tab->data[n]= 0; 456 if (strlen(tab->data) < n) { 457 cronlog(LOG_ERR, "%s contains a null character\n", file); 458 return; 459 } 460 461 /* Parse the file. */ 462 ajob= &tab->jobs; 463 p= tab->data; 464 ok= 1; 465 while (ok && *p != 0) { 466 q= get_token(&p); 467 if (*q == '#' || q == p) { 468 /* Comment or empty. */ 469 while (*p != 0 && *p++ != '\n') {} 470 continue; 471 } 472 473 /* One new job coming up. */ 474 *ajob= job= allocate(sizeof(*job)); 475 *(ajob= &job->next)= nil; 476 job->tab= tab; 477 478 if (!range_parse(file, q, job->min, 0, 59, &wc)) { 479 ok= 0; 480 break; 481 } 482 483 q= get_token(&p); 484 if (!range_parse(file, q, job->hour, 0, 23, &wc)) { 485 ok= 0; 486 break; 487 } 488 489 q= get_token(&p); 490 if (!range_parse(file, q, job->mday, 1, 31, &wc)) { 491 ok= 0; 492 break; 493 } 494 job->do_mday= !wc; 495 496 q= get_token(&p); 497 if (!range_parse(file, q, job->mon, 1, 12, &wc)) { 498 ok= 0; 499 break; 500 } 501 job->do_mday |= !wc; 502 503 q= get_token(&p); 504 if (!range_parse(file, q, job->wday, 0, 7, &wc)) { 505 ok= 0; 506 break; 507 } 508 job->do_wday= !wc; 509 510 /* 7 is Sunday, but 0 is a common mistake because it is in the 511 * tm_wday range. We allow and even prefer it internally. 512 */ 513 if (bit_isset(job->wday, 7)) { 514 bit_clr(job->wday, 7); 515 bit_set(job->wday, 0); 516 } 517 518 /* The month range is 1-12, but tm_mon likes 0-11. */ 519 job->mon[0] >>= 1; 520 if (bit_isset(job->mon, 8)) bit_set(job->mon, 7); 521 job->mon[1] >>= 1; 522 523 /* Scan for options. */ 524 job->user= nil; 525 while (q= get_token(&p), *q == '-') { 526 q++; 527 if (q[0] == '-' && q+1 == p) { 528 /* -- */ 529 q= get_token(&p); 530 break; 531 } 532 while (q < p) switch (*q++) { 533 case 'u': 534 if (q == p) q= get_token(&p); 535 if (q == p) goto usage; 536 memmove(q-1, q, p-q); /* gross... */ 537 p[-1]= 0; 538 job->user= q-1; 539 q= p; 540 break; 541 default: 542 usage: 543 cronlog(LOG_ERR, 544 "%s: bad option -%c, good options are: -u username\n", 545 file, q[-1]); 546 ok= 0; 547 goto endtab; 548 } 549 } 550 551 /* A crontab owned by a user can only do things as that user. */ 552 if (tab->user != nil) job->user= tab->user; 553 554 /* Inspect the first character of the command. */ 555 job->cmd= q; 556 if (q == p || *q == '#') { 557 /* Rest of the line is empty, i.e. the commands are on 558 * the following lines indented by one tab. 559 */ 560 while (*p != 0 && *p++ != '\n') {} 561 if (*p++ != '\t') { 562 cronlog(LOG_ERR, 563 "%s: contains an empty command\n", 564 file); 565 ok= 0; 566 goto endtab; 567 } 568 while (*p != 0) { 569 if ((*q = *p++) == '\n') { 570 if (*p != '\t') break; 571 p++; 572 } 573 q++; 574 } 575 } else { 576 /* The command is on this line. Alas we must now be 577 * backwards compatible and change %'s to newlines. 578 */ 579 p= q; 580 while (*p != 0) { 581 if ((*q = *p++) == '\n') break; 582 if (*q == '%') *q= '\n'; 583 q++; 584 } 585 } 586 *q= 0; 587 job->rtime= now; 588 job->late= 0; /* It is on time. */ 589 job->atjob= 0; /* True cron job. */ 590 job->pid= IDLE_PID; /* Not running yet. */ 591 tab_reschedule(job); /* Compute next time to run. */ 592 } 593 endtab: 594 595 if (ok) tab->current= 1; 596 } 597 598 void tab_find_atjob(char *atdir) 599 /* Find the first to be executed AT job and kludge up an crontab job for it. 600 * We set tab->file to "atdir/jobname", tab->data to "atdir/past/jobname", 601 * and job->cmd to "jobname". 602 */ 603 { 604 DIR *spool; 605 struct dirent *entry; 606 time_t t0, t1; 607 struct tm tmnow, tmt1; 608 static char template[] = "96.365.1546.00"; 609 char firstjob[sizeof(template)]; 610 int i; 611 crontab_t *tab; 612 cronjob_t *job; 613 614 if ((spool= opendir(atdir)) == nil) return; 615 616 tmnow= *localtime(&now); 617 t0= NEVER; 618 619 while ((entry= readdir(spool)) != nil) { 620 /* Check if the name fits the template. */ 621 for (i= 0; template[i] != 0; i++) { 622 if (template[i] == '.') { 623 if (entry->d_name[i] != '.') break; 624 } else { 625 if (!isdigit(entry->d_name[i])) break; 626 } 627 } 628 if (template[i] != 0 || entry->d_name[i] != 0) continue; 629 630 /* Convert the name to a time. Careful with the century. */ 631 memset(&tmt1, 0, sizeof(tmt1)); 632 tmt1.tm_year= atoi(entry->d_name+0); 633 while (tmt1.tm_year < tmnow.tm_year-10) tmt1.tm_year+= 100; 634 tmt1.tm_mday= 1+atoi(entry->d_name+3); 635 tmt1.tm_min= atoi(entry->d_name+7); 636 tmt1.tm_hour= tmt1.tm_min / 100; 637 tmt1.tm_min%= 100; 638 tmt1.tm_isdst= -1; 639 if ((t1= mktime(&tmt1)) == -1) { 640 /* Illegal time? Try in winter time. */ 641 tmt1.tm_isdst= 0; 642 if ((t1= mktime(&tmt1)) == -1) continue; 643 } 644 if (t1 < t0) { 645 t0= t1; 646 strcpy(firstjob, entry->d_name); 647 } 648 } 649 closedir(spool); 650 651 if (t0 == NEVER) return; /* AT job spool is empty. */ 652 653 /* Create new table and job structures. */ 654 tab= allocate(sizeof(*tab)); 655 tab->file= allocate((strlen(atdir) + 1 + sizeof(template)) 656 * sizeof(tab->file[0])); 657 strcpy(tab->file, atdir); 658 strcat(tab->file, "/"); 659 strcat(tab->file, firstjob); 660 tab->data= allocate((strlen(atdir) + 6 + sizeof(template)) 661 * sizeof(tab->data[0])); 662 strcpy(tab->data, atdir); 663 strcat(tab->data, "/past/"); 664 strcat(tab->data, firstjob); 665 tab->user= nil; 666 tab->mtime= 0; 667 tab->current= 1; 668 tab->next= crontabs; 669 crontabs= tab; 670 671 tab->jobs= job= allocate(sizeof(*job)); 672 job->next= nil; 673 job->tab= tab; 674 job->user= nil; 675 job->cmd= tab->data + strlen(atdir) + 6; 676 job->rtime= t0; 677 job->late= 0; 678 job->atjob= 1; /* One AT job disguised as a cron job. */ 679 job->pid= IDLE_PID; 680 681 if (job->rtime < next) next= job->rtime; 682 } 683 684 void tab_purge(void) 685 /* Remove table data that is no longer current. E.g. a crontab got removed. 686 */ 687 { 688 crontab_t **atab, *tab; 689 cronjob_t *job; 690 691 atab= &crontabs; 692 while ((tab= *atab) != nil) { 693 if (tab->current) { 694 /* Table is fine. */ 695 tab->current= 0; 696 atab= &tab->next; 697 } else { 698 /* Table is not, or no longer valid; delete. */ 699 *atab= tab->next; 700 while ((job= tab->jobs) != nil) { 701 tab->jobs= job->next; 702 deallocate(job); 703 } 704 deallocate(tab->data); 705 deallocate(tab->file); 706 deallocate(tab->user); 707 deallocate(tab); 708 } 709 } 710 } 711 712 static cronjob_t *reap_or_find(pid_t pid) 713 /* Find a finished job or search for the next one to run. */ 714 { 715 crontab_t *tab; 716 cronjob_t *job; 717 cronjob_t *nextjob; 718 719 nextjob= nil; 720 next= NEVER; 721 for (tab= crontabs; tab != nil; tab= tab->next) { 722 for (job= tab->jobs; job != nil; job= job->next) { 723 if (job->pid == pid) { 724 job->pid= IDLE_PID; 725 tab_reschedule(job); 726 } 727 if (job->pid != IDLE_PID) continue; 728 if (job->rtime < next) next= job->rtime; 729 if (job->rtime <= now) nextjob= job; 730 } 731 } 732 return nextjob; 733 } 734 735 void tab_reap_job(pid_t pid) 736 /* A job has finished. Try to find it among the crontab data and reschedule 737 * it. Recompute time next to run a job. 738 */ 739 { 740 (void) reap_or_find(pid); 741 } 742 743 cronjob_t *tab_nextjob(void) 744 /* Find a job that should be run now. If none are found return null. 745 * Update 'next'. 746 */ 747 { 748 return reap_or_find(NO_PID); 749 } 750 751 static void pr_map(FILE *fp, bitmap_t map) 752 { 753 int last_bit= -1, bit; 754 char *sep; 755 756 sep= ""; 757 for (bit= 0; bit < 64; bit++) { 758 if (bit_isset(map, bit)) { 759 if (last_bit == -1) last_bit= bit; 760 } else { 761 if (last_bit != -1) { 762 fprintf(fp, "%s%d", sep, last_bit); 763 if (last_bit != bit-1) { 764 fprintf(fp, "-%d", bit-1); 765 } 766 last_bit= -1; 767 sep= ","; 768 } 769 } 770 } 771 } 772 773 void tab_print(FILE *fp) 774 /* Print out a stored crontab file for debugging purposes. */ 775 { 776 crontab_t *tab; 777 cronjob_t *job; 778 char *p; 779 struct tm tm; 780 781 for (tab= crontabs; tab != nil; tab= tab->next) { 782 fprintf(fp, "tab->file = \"%s\"\n", tab->file); 783 fprintf(fp, "tab->user = \"%s\"\n", 784 tab->user == nil ? "(root)" : tab->user); 785 fprintf(fp, "tab->mtime = %s", ctime(&tab->mtime)); 786 787 for (job= tab->jobs; job != nil; job= job->next) { 788 if (job->atjob) { 789 fprintf(fp, "AT job"); 790 } else { 791 pr_map(fp, job->min); fputc(' ', fp); 792 pr_map(fp, job->hour); fputc(' ', fp); 793 pr_map(fp, job->mday); fputc(' ', fp); 794 pr_map(fp, job->mon); fputc(' ', fp); 795 pr_map(fp, job->wday); 796 } 797 if (job->user != nil && job->user != tab->user) { 798 fprintf(fp, " -u %s", job->user); 799 } 800 fprintf(fp, "\n\t"); 801 for (p= job->cmd; *p != 0; p++) { 802 fputc(*p, fp); 803 if (*p == '\n') fputc('\t', fp); 804 } 805 fputc('\n', fp); 806 tm= *localtime(&job->rtime); 807 fprintf(fp, " rtime = %.24s%s\n", asctime(&tm), 808 tm.tm_isdst ? " (DST)" : ""); 809 if (job->pid != IDLE_PID) { 810 fprintf(fp, " pid = %ld\n", (long) job->pid); 811 } 812 } 813 } 814 } 815 816 /* 817 * $PchId: tab.c,v 1.5 2000/07/25 22:07:51 philip Exp $ 818 */ 819