00001
00002
00003
00004
00005
00006
00007
00008 #define nil ((void*)0)
00009 #include <sys/types.h>
00010 #include <assert.h>
00011 #include <stdio.h>
00012 #include <unistd.h>
00013 #include <fcntl.h>
00014 #include <stdlib.h>
00015 #include <string.h>
00016 #include <errno.h>
00017 #include <limits.h>
00018 #include <time.h>
00019 #include <dirent.h>
00020 #include <sys/stat.h>
00021 #include "misc.h"
00022 #include "tab.h"
00023
00024 static int nextbit(bitmap_t map, int bit)
00025
00026 {
00027 for (;;) {
00028 bit= (bit+1) & 63;
00029 if (bit_isset(map, bit)) break;
00030 }
00031 return bit;
00032 }
00033
00034 void tab_reschedule(cronjob_t *job)
00035
00036
00037 {
00038 struct tm prevtm, nexttm, tmptm;
00039 time_t nodst_rtime, dst_rtime;
00040
00041
00042 if (job->atjob) { job->rtime= NEVER; return; }
00043
00044
00045 if (job->late) job->rtime= now;
00046
00047 prevtm= *localtime(&job->rtime);
00048 prevtm.tm_sec= 0;
00049
00050 nexttm= prevtm;
00051 nexttm.tm_min++;
00052
00053 for (;;)
00054 {
00055 if (nexttm.tm_min > 59)
00056 {
00057 nexttm.tm_min= 0;
00058 nexttm.tm_hour++;
00059 }
00060 if (nexttm.tm_hour > 23)
00061 {
00062 nexttm.tm_min= 0;
00063 nexttm.tm_hour= 0;
00064 nexttm.tm_mday++;
00065 }
00066 if (nexttm.tm_mday > 31)
00067 {
00068 nexttm.tm_hour= nexttm.tm_min= 0;
00069 nexttm.tm_mday= 1;
00070 nexttm.tm_mon++;
00071 }
00072 if (nexttm.tm_mon >= 12)
00073 {
00074 nexttm.tm_hour= nexttm.tm_min= 0;
00075 nexttm.tm_mday= 1;
00076 nexttm.tm_mon= 0;
00077 nexttm.tm_year++;
00078 }
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089 if (nexttm.tm_year-prevtm.tm_year > 2*4)
00090 {
00091 job->rtime= NEVER;
00092 return;
00093 }
00094
00095 if (!job->do_wday)
00096 {
00097
00098
00099
00100
00101 if (!bit_isset(job->mon, nexttm.tm_mon))
00102 {
00103
00104 nexttm.tm_mday= 1;
00105 nexttm.tm_hour= nexttm.tm_min= 0;
00106
00107 nexttm.tm_mon++;
00108 continue;
00109 }
00110
00111
00112 if (!bit_isset(job->mday, nexttm.tm_mday))
00113 {
00114
00115 nexttm.tm_hour= nexttm.tm_min= 0;
00116
00117 nexttm.tm_mday++;
00118 continue;
00119 }
00120 }
00121
00122
00123 if (!bit_isset(job->hour, nexttm.tm_hour))
00124 {
00125
00126 nexttm.tm_min= 0;
00127
00128 nexttm.tm_hour++;
00129 continue;
00130 }
00131
00132
00133 if (!bit_isset(job->min, nexttm.tm_min))
00134 {
00135 nexttm.tm_min++;
00136 continue;
00137 }
00138
00139
00140
00141 tmptm= nexttm;
00142 tmptm.tm_isdst= 0;
00143 #if 0
00144 fprintf(stderr,
00145 "tab_reschedule: trying %04d-%02d-%02d %02d:%02d:%02d isdst=0\n",
00146 1900+nexttm.tm_year, nexttm.tm_mon+1,
00147 nexttm.tm_mday, nexttm.tm_hour,
00148 nexttm.tm_min, nexttm.tm_sec);
00149 #endif
00150 nodst_rtime= job->rtime= mktime(&tmptm);
00151 if (job->rtime == -1) {
00152
00153 log(LOG_ERR,
00154 "mktime failed for %04d-%02d-%02d %02d:%02d:%02d",
00155 1900+nexttm.tm_year, nexttm.tm_mon+1,
00156 nexttm.tm_mday, nexttm.tm_hour,
00157 nexttm.tm_min, nexttm.tm_sec);
00158 job->rtime= NEVER;
00159 return;
00160 }
00161 tmptm= *localtime(&job->rtime);
00162 if (tmptm.tm_hour != nexttm.tm_hour ||
00163 tmptm.tm_min != nexttm.tm_min)
00164 {
00165 assert(tmptm.tm_isdst);
00166 tmptm= nexttm;
00167 tmptm.tm_isdst= 1;
00168 #if 0
00169 fprintf(stderr,
00170 "tab_reschedule: trying %04d-%02d-%02d %02d:%02d:%02d isdst=1\n",
00171 1900+nexttm.tm_year, nexttm.tm_mon+1,
00172 nexttm.tm_mday, nexttm.tm_hour,
00173 nexttm.tm_min, nexttm.tm_sec);
00174 #endif
00175 dst_rtime= job->rtime= mktime(&tmptm);
00176 if (job->rtime == -1) {
00177
00178 log(LOG_ERR,
00179 "mktime failed for %04d-%02d-%02d %02d:%02d:%02d\n",
00180 1900+nexttm.tm_year, nexttm.tm_mon+1,
00181 nexttm.tm_mday, nexttm.tm_hour,
00182 nexttm.tm_min, nexttm.tm_sec);
00183 job->rtime= NEVER;
00184 return;
00185 }
00186 tmptm= *localtime(&job->rtime);
00187 if (tmptm.tm_hour != nexttm.tm_hour ||
00188 tmptm.tm_min != nexttm.tm_min)
00189 {
00190 assert(!tmptm.tm_isdst);
00191
00192
00193
00194
00195
00196 assert(nodst_rtime > dst_rtime);
00197 job->rtime= nodst_rtime;
00198 #if 0
00199 fprintf(stderr,
00200 "During DST trans. %04d-%02d-%02d %02d:%02d:%02d\n",
00201 1900+nexttm.tm_year, nexttm.tm_mon+1,
00202 nexttm.tm_mday, nexttm.tm_hour,
00203 nexttm.tm_min, nexttm.tm_sec);
00204 #endif
00205 }
00206 }
00207
00208
00209 if (tmptm.tm_mday != nexttm.tm_mday ||
00210 tmptm.tm_mon != nexttm.tm_mon ||
00211 tmptm.tm_year != nexttm.tm_year)
00212 {
00213
00214 #if 0
00215 fprintf(stderr, "Wrong day\n");
00216 #endif
00217 nexttm.tm_hour= nexttm.tm_min= 0;
00218 nexttm.tm_mday++;
00219 continue;
00220 }
00221
00222
00223 if (job->do_wday && bit_isset(job->wday, tmptm.tm_wday))
00224 {
00225
00226 break;
00227 }
00228
00229
00230 if (job->do_mday && bit_isset(job->mon, tmptm.tm_mon) &&
00231 bit_isset(job->mday, tmptm.tm_mday))
00232 {
00233
00234 break;
00235 }
00236
00237 if (!job->do_wday && !job->do_mday)
00238 {
00239
00240 break;
00241 }
00242
00243
00244 #if 0
00245 fprintf(stderr, "Wrong mon+mday or wday\n");
00246 #endif
00247 nexttm.tm_hour= nexttm.tm_min= 0;
00248 nexttm.tm_mday++;
00249 }
00250 #if 0
00251 fprintf(stderr, "Using %04d-%02d-%02d %02d:%02d:%02d \n",
00252 1900+nexttm.tm_year, nexttm.tm_mon+1, nexttm.tm_mday,
00253 nexttm.tm_hour, nexttm.tm_min, nexttm.tm_sec);
00254 tmptm= *localtime(&job->rtime);
00255 fprintf(stderr, "Act. %04d-%02d-%02d %02d:%02d:%02d isdst=%d\n",
00256 1900+tmptm.tm_year, tmptm.tm_mon+1, tmptm.tm_mday,
00257 tmptm.tm_hour, tmptm.tm_min, tmptm.tm_sec,
00258 tmptm.tm_isdst);
00259 #endif
00260
00261
00262
00263 job->late= (job->rtime < now);
00264
00265
00266 if (job->rtime < next) next= job->rtime;
00267 }
00268
00269 #define isdigit(c) ((unsigned) ((c) - '0') < 10)
00270
00271 static char *get_token(char **ptr)
00272
00273
00274
00275
00276 {
00277 char *start, *p;
00278
00279 p= *ptr;
00280 while (*p == ' ' || *p == '\t') p++;
00281
00282 start= p;
00283 while (*p != ' ' && *p != '\t' && *p != '\n' && *p != 0) p++;
00284 *ptr= p;
00285 return start;
00286 }
00287
00288 static int range_parse(char *file, char *data, bitmap_t map,
00289 int min, int max, int *wildcard)
00290
00291
00292
00293
00294
00295
00296
00297 {
00298 char *p;
00299 int end;
00300 int n, m;
00301
00302
00303 for (n= 0; n < 8; n++) map[n]= 0;
00304
00305 p= data;
00306 while (*p != ' ' && *p != '\t' && *p != '\n' && *p != 0) p++;
00307 end= *p;
00308 *p= 0;
00309 p= data;
00310
00311 if (*p == 0) {
00312 log(LOG_ERR, "%s: not enough time fields\n", file);
00313 return 0;
00314 }
00315
00316
00317 if (p[0] == '*' && p[1] == 0) {
00318 for (n= min; n <= max; n++) bit_set(map, n);
00319 p[1]= end;
00320 *wildcard= 1;
00321 return 1;
00322 }
00323 *wildcard= 0;
00324
00325
00326 for (;;) {
00327 if (*p == '?' && max == 59 && p[1] != '-') {
00328 n= localtime(&now)->tm_min;
00329 p++;
00330 } else {
00331 if (!isdigit(*p)) goto syntax;
00332 n= 0;
00333 do {
00334 n= 10 * n + (*p++ - '0');
00335 if (n > max) goto range;
00336 } while (isdigit(*p));
00337 }
00338 if (n < min) goto range;
00339
00340 if (*p == '-') {
00341 p++;
00342 if (!isdigit(*p)) goto syntax;
00343 m= 0;
00344 do {
00345 m= 10 * m + (*p++ - '0');
00346 if (m > max) goto range;
00347 } while (isdigit(*p));
00348 if (m < n) goto range;
00349 do {
00350 bit_set(map, n);
00351 } while (++n <= m);
00352 } else
00353 if (*p == ':') {
00354 p++;
00355 if (!isdigit(*p)) goto syntax;
00356 m= 0;
00357 do {
00358 m= 10 * m + (*p++ - '0');
00359 if (m > (max-min+1)) goto range;
00360 } while (isdigit(*p));
00361 if (m == 0) goto range;
00362 while (n >= min) n-= m;
00363 while ((n+= m) <= max) bit_set(map, n);
00364 } else {
00365
00366 bit_set(map, n);
00367 }
00368 if (*p == 0) break;
00369 if (*p++ != ',') goto syntax;
00370 }
00371 *p= end;
00372 return 1;
00373 syntax:
00374 log(LOG_ERR, "%s: field '%s': bad syntax for a %d-%d time field\n",
00375 file, data, min, max);
00376 return 0;
00377 range:
00378 log(LOG_ERR, "%s: field '%s': values out of the %d-%d allowed range\n",
00379 file, data, min, max);
00380 return 0;
00381 }
00382
00383 void tab_parse(char *file, char *user)
00384
00385
00386
00387 {
00388 crontab_t **atab, *tab;
00389 cronjob_t **ajob, *job;
00390 int fd;
00391 struct stat st;
00392 char *p, *q;
00393 size_t n;
00394 ssize_t r;
00395 int ok, wc;
00396
00397 for (atab= &crontabs; (tab= *atab) != nil; atab= &tab->next) {
00398 if (strcmp(file, tab->file) == 0) break;
00399 }
00400
00401
00402 if ((fd= open(file, O_RDONLY)) < 0 || fstat(fd, &st) < 0) {
00403 if (errno != ENOENT) {
00404 log(LOG_ERR, "%s: %s\n", file, strerror(errno));
00405 }
00406 if (fd != -1) close(fd);
00407 return;
00408 }
00409
00410
00411 if (st.st_size > TAB_MAX) {
00412 log(LOG_ERR, "%s: %lu bytes is bigger than my %lu limit\n",
00413 file,
00414 (unsigned long) st.st_size,
00415 (unsigned long) TAB_MAX);
00416 return;
00417 }
00418
00419
00420 if (tab != nil && st.st_mtime == tab->mtime) {
00421 close(fd);
00422 tab->current= 1;
00423 return;
00424 }
00425
00426
00427 tab= allocate(sizeof(*tab));
00428 tab->file= allocate((strlen(file) + 1) * sizeof(tab->file[0]));
00429 strcpy(tab->file, file);
00430 tab->user= nil;
00431 if (user != nil) {
00432 tab->user= allocate((strlen(user) + 1) * sizeof(tab->user[0]));
00433 strcpy(tab->user, user);
00434 }
00435 tab->data= allocate((st.st_size + 1) * sizeof(tab->data[0]));
00436 tab->jobs= nil;
00437 tab->mtime= st.st_mtime;
00438 tab->current= 0;
00439 tab->next= *atab;
00440 *atab= tab;
00441
00442
00443 n= 0;
00444 while (n < st.st_size) {
00445 if ((r = read(fd, tab->data + n, st.st_size - n)) < 0) {
00446 log(LOG_CRIT, "%s: %s", file, strerror(errno));
00447 close(fd);
00448 return;
00449 }
00450 if (r == 0) break;
00451 n+= r;
00452 }
00453 close(fd);
00454 tab->data[n]= 0;
00455 if (strlen(tab->data) < n) {
00456 log(LOG_ERR, "%s contains a null character\n", file);
00457 return;
00458 }
00459
00460
00461 ajob= &tab->jobs;
00462 p= tab->data;
00463 ok= 1;
00464 while (ok && *p != 0) {
00465 q= get_token(&p);
00466 if (*q == '#' || q == p) {
00467
00468 while (*p != 0 && *p++ != '\n') {}
00469 continue;
00470 }
00471
00472
00473 *ajob= job= allocate(sizeof(*job));
00474 *(ajob= &job->next)= nil;
00475 job->tab= tab;
00476
00477 if (!range_parse(file, q, job->min, 0, 59, &wc)) {
00478 ok= 0;
00479 break;
00480 }
00481
00482 q= get_token(&p);
00483 if (!range_parse(file, q, job->hour, 0, 23, &wc)) {
00484 ok= 0;
00485 break;
00486 }
00487
00488 q= get_token(&p);
00489 if (!range_parse(file, q, job->mday, 1, 31, &wc)) {
00490 ok= 0;
00491 break;
00492 }
00493 job->do_mday= !wc;
00494
00495 q= get_token(&p);
00496 if (!range_parse(file, q, job->mon, 1, 12, &wc)) {
00497 ok= 0;
00498 break;
00499 }
00500 job->do_mday |= !wc;
00501
00502 q= get_token(&p);
00503 if (!range_parse(file, q, job->wday, 0, 7, &wc)) {
00504 ok= 0;
00505 break;
00506 }
00507 job->do_wday= !wc;
00508
00509
00510
00511
00512 if (bit_isset(job->wday, 7)) {
00513 bit_clr(job->wday, 7);
00514 bit_set(job->wday, 0);
00515 }
00516
00517
00518 job->mon[0] >>= 1;
00519 if (bit_isset(job->mon, 8)) bit_set(job->mon, 7);
00520 job->mon[1] >>= 1;
00521
00522
00523 job->user= nil;
00524 while (q= get_token(&p), *q == '-') {
00525 q++;
00526 if (q[0] == '-' && q+1 == p) {
00527
00528 q= get_token(&p);
00529 break;
00530 }
00531 while (q < p) switch (*q++) {
00532 case 'u':
00533 if (q == p) q= get_token(&p);
00534 if (q == p) goto usage;
00535 memmove(q-1, q, p-q);
00536 p[-1]= 0;
00537 job->user= q-1;
00538 q= p;
00539 break;
00540 default:
00541 usage:
00542 log(LOG_ERR,
00543 "%s: bad option -%c, good options are: -u username\n",
00544 file, q[-1]);
00545 ok= 0;
00546 goto endtab;
00547 }
00548 }
00549
00550
00551 if (tab->user != nil) job->user= tab->user;
00552
00553
00554 job->cmd= q;
00555 if (q == p || *q == '#') {
00556
00557
00558
00559 while (*p != 0 && *p++ != '\n') {}
00560 if (*p++ != '\t') {
00561 log(LOG_ERR, "%s: contains an empty command\n",
00562 file);
00563 ok= 0;
00564 goto endtab;
00565 }
00566 while (*p != 0) {
00567 if ((*q = *p++) == '\n') {
00568 if (*p != '\t') break;
00569 p++;
00570 }
00571 q++;
00572 }
00573 } else {
00574
00575
00576
00577 p= q;
00578 while (*p != 0) {
00579 if ((*q = *p++) == '\n') break;
00580 if (*q == '%') *q= '\n';
00581 q++;
00582 }
00583 }
00584 *q= 0;
00585 job->rtime= now;
00586 job->late= 0;
00587 job->atjob= 0;
00588 job->pid= IDLE_PID;
00589 tab_reschedule(job);
00590 }
00591 endtab:
00592
00593 if (ok) tab->current= 1;
00594 }
00595
00596 void tab_find_atjob(char *atdir)
00597
00598
00599
00600
00601 {
00602 DIR *spool;
00603 struct dirent *entry;
00604 time_t t0, t1;
00605 struct tm tmnow, tmt1;
00606 static char template[] = "96.365.1546.00";
00607 char firstjob[sizeof(template)];
00608 int i;
00609 crontab_t *tab;
00610 cronjob_t *job;
00611
00612 if ((spool= opendir(atdir)) == nil) return;
00613
00614 tmnow= *localtime(&now);
00615 t0= NEVER;
00616
00617 while ((entry= readdir(spool)) != nil) {
00618
00619 for (i= 0; template[i] != 0; i++) {
00620 if (template[i] == '.') {
00621 if (entry->d_name[i] != '.') break;
00622 } else {
00623 if (!isdigit(entry->d_name[i])) break;
00624 }
00625 }
00626 if (template[i] != 0 || entry->d_name[i] != 0) continue;
00627
00628
00629 memset(&tmt1, 0, sizeof(tmt1));
00630 tmt1.tm_year= atoi(entry->d_name+0);
00631 while (tmt1.tm_year < tmnow.tm_year-10) tmt1.tm_year+= 100;
00632 tmt1.tm_mday= 1+atoi(entry->d_name+3);
00633 tmt1.tm_min= atoi(entry->d_name+7);
00634 tmt1.tm_hour= tmt1.tm_min / 100;
00635 tmt1.tm_min%= 100;
00636 tmt1.tm_isdst= -1;
00637 if ((t1= mktime(&tmt1)) == -1) {
00638
00639 tmt1.tm_isdst= 0;
00640 if ((t1= mktime(&tmt1)) == -1) continue;
00641 }
00642 if (t1 < t0) {
00643 t0= t1;
00644 strcpy(firstjob, entry->d_name);
00645 }
00646 }
00647 closedir(spool);
00648
00649 if (t0 == NEVER) return;
00650
00651
00652 tab= allocate(sizeof(*tab));
00653 tab->file= allocate((strlen(atdir) + 1 + sizeof(template))
00654 * sizeof(tab->file[0]));
00655 strcpy(tab->file, atdir);
00656 strcat(tab->file, "/");
00657 strcat(tab->file, firstjob);
00658 tab->data= allocate((strlen(atdir) + 6 + sizeof(template))
00659 * sizeof(tab->data[0]));
00660 strcpy(tab->data, atdir);
00661 strcat(tab->data, "/past/");
00662 strcat(tab->data, firstjob);
00663 tab->user= nil;
00664 tab->mtime= 0;
00665 tab->current= 1;
00666 tab->next= crontabs;
00667 crontabs= tab;
00668
00669 tab->jobs= job= allocate(sizeof(*job));
00670 job->next= nil;
00671 job->tab= tab;
00672 job->user= nil;
00673 job->cmd= tab->data + strlen(atdir) + 6;
00674 job->rtime= t0;
00675 job->late= 0;
00676 job->atjob= 1;
00677 job->pid= IDLE_PID;
00678
00679 if (job->rtime < next) next= job->rtime;
00680 }
00681
00682 void tab_purge(void)
00683
00684
00685 {
00686 crontab_t **atab, *tab;
00687 cronjob_t *job;
00688
00689 atab= &crontabs;
00690 while ((tab= *atab) != nil) {
00691 if (tab->current) {
00692
00693 tab->current= 0;
00694 atab= &tab->next;
00695 } else {
00696
00697 *atab= tab->next;
00698 while ((job= tab->jobs) != nil) {
00699 tab->jobs= job->next;
00700 deallocate(job);
00701 }
00702 deallocate(tab->data);
00703 deallocate(tab->file);
00704 deallocate(tab->user);
00705 deallocate(tab);
00706 }
00707 }
00708 }
00709
00710 static cronjob_t *reap_or_find(pid_t pid)
00711
00712 {
00713 crontab_t *tab;
00714 cronjob_t *job;
00715 cronjob_t *nextjob;
00716
00717 nextjob= nil;
00718 next= NEVER;
00719 for (tab= crontabs; tab != nil; tab= tab->next) {
00720 for (job= tab->jobs; job != nil; job= job->next) {
00721 if (job->pid == pid) {
00722 job->pid= IDLE_PID;
00723 tab_reschedule(job);
00724 }
00725 if (job->pid != IDLE_PID) continue;
00726 if (job->rtime < next) next= job->rtime;
00727 if (job->rtime <= now) nextjob= job;
00728 }
00729 }
00730 return nextjob;
00731 }
00732
00733 void tab_reap_job(pid_t pid)
00734
00735
00736
00737 {
00738 (void) reap_or_find(pid);
00739 }
00740
00741 cronjob_t *tab_nextjob(void)
00742
00743
00744
00745 {
00746 return reap_or_find(NO_PID);
00747 }
00748
00749 static void pr_map(FILE *fp, bitmap_t map)
00750 {
00751 int last_bit= -1, bit;
00752 char *sep;
00753
00754 sep= "";
00755 for (bit= 0; bit < 64; bit++) {
00756 if (bit_isset(map, bit)) {
00757 if (last_bit == -1) last_bit= bit;
00758 } else {
00759 if (last_bit != -1) {
00760 fprintf(fp, "%s%d", sep, last_bit);
00761 if (last_bit != bit-1) {
00762 fprintf(fp, "-%d", bit-1);
00763 }
00764 last_bit= -1;
00765 sep= ",";
00766 }
00767 }
00768 }
00769 }
00770
00771 void tab_print(FILE *fp)
00772
00773 {
00774 crontab_t *tab;
00775 cronjob_t *job;
00776 char *p;
00777 struct tm tm;
00778
00779 for (tab= crontabs; tab != nil; tab= tab->next) {
00780 fprintf(fp, "tab->file = \"%s\"\n", tab->file);
00781 fprintf(fp, "tab->user = \"%s\"\n",
00782 tab->user == nil ? "(root)" : tab->user);
00783 fprintf(fp, "tab->mtime = %s", ctime(&tab->mtime));
00784
00785 for (job= tab->jobs; job != nil; job= job->next) {
00786 if (job->atjob) {
00787 fprintf(fp, "AT job");
00788 } else {
00789 pr_map(fp, job->min); fputc(' ', fp);
00790 pr_map(fp, job->hour); fputc(' ', fp);
00791 pr_map(fp, job->mday); fputc(' ', fp);
00792 pr_map(fp, job->mon); fputc(' ', fp);
00793 pr_map(fp, job->wday);
00794 }
00795 if (job->user != nil && job->user != tab->user) {
00796 fprintf(fp, " -u %s", job->user);
00797 }
00798 fprintf(fp, "\n\t");
00799 for (p= job->cmd; *p != 0; p++) {
00800 fputc(*p, fp);
00801 if (*p == '\n') fputc('\t', fp);
00802 }
00803 fputc('\n', fp);
00804 tm= *localtime(&job->rtime);
00805 fprintf(fp, " rtime = %.24s%s\n", asctime(&tm),
00806 tm.tm_isdst ? " (DST)" : "");
00807 if (job->pid != IDLE_PID) {
00808 fprintf(fp, " pid = %ld\n", (long) job->pid);
00809 }
00810 }
00811 }
00812 }
00813
00814
00815
00816