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 | */
|
---|