1433d6423SLionel Sambuc /* tab.c - process crontabs and create in-core crontab data
2433d6423SLionel Sambuc * Author: Kees J. Bot
3433d6423SLionel Sambuc * 7 Dec 1996
4433d6423SLionel Sambuc * Changes:
5433d6423SLionel Sambuc * 17 Jul 2000 by Philip Homburg
6433d6423SLionel Sambuc * - Tab_reschedule() rewritten (and fixed).
7433d6423SLionel Sambuc */
8433d6423SLionel Sambuc #define nil ((void*)0)
9433d6423SLionel Sambuc #include <sys/types.h>
10433d6423SLionel Sambuc #include <assert.h>
11433d6423SLionel Sambuc #include <stdio.h>
12433d6423SLionel Sambuc #include <unistd.h>
13433d6423SLionel Sambuc #include <fcntl.h>
14433d6423SLionel Sambuc #include <stdlib.h>
15433d6423SLionel Sambuc #include <string.h>
16433d6423SLionel Sambuc #include <errno.h>
17433d6423SLionel Sambuc #include <limits.h>
18433d6423SLionel Sambuc #include <time.h>
19433d6423SLionel Sambuc #include <dirent.h>
20433d6423SLionel Sambuc #include <sys/stat.h>
21433d6423SLionel Sambuc #include "misc.h"
22433d6423SLionel Sambuc #include "tab.h"
23433d6423SLionel Sambuc
nextbit(bitmap_t map,int bit)24433d6423SLionel Sambuc static int nextbit(bitmap_t map, int bit)
25433d6423SLionel Sambuc /* Return the next bit set in 'map' from 'bit' onwards, cyclic. */
26433d6423SLionel Sambuc {
27433d6423SLionel Sambuc for (;;) {
28433d6423SLionel Sambuc bit= (bit+1) & 63;
29433d6423SLionel Sambuc if (bit_isset(map, bit)) break;
30433d6423SLionel Sambuc }
31433d6423SLionel Sambuc return bit;
32433d6423SLionel Sambuc }
33433d6423SLionel Sambuc
tab_reschedule(cronjob_t * job)34433d6423SLionel Sambuc void tab_reschedule(cronjob_t *job)
35433d6423SLionel Sambuc /* Reschedule one job. Compute the next time to run the job in job->rtime.
36433d6423SLionel Sambuc */
37433d6423SLionel Sambuc {
38433d6423SLionel Sambuc struct tm prevtm, nexttm, tmptm;
39433d6423SLionel Sambuc time_t nodst_rtime, dst_rtime;
40433d6423SLionel Sambuc
41433d6423SLionel Sambuc /* AT jobs are only run once. */
42433d6423SLionel Sambuc if (job->atjob) { job->rtime= NEVER; return; }
43433d6423SLionel Sambuc
44433d6423SLionel Sambuc /* Was the job scheduled late last time? */
45433d6423SLionel Sambuc if (job->late) job->rtime= now;
46433d6423SLionel Sambuc
47433d6423SLionel Sambuc prevtm= *localtime(&job->rtime);
48433d6423SLionel Sambuc prevtm.tm_sec= 0;
49433d6423SLionel Sambuc
50433d6423SLionel Sambuc nexttm= prevtm;
51433d6423SLionel Sambuc nexttm.tm_min++; /* Minimal increment */
52433d6423SLionel Sambuc
53433d6423SLionel Sambuc for (;;)
54433d6423SLionel Sambuc {
55433d6423SLionel Sambuc if (nexttm.tm_min > 59)
56433d6423SLionel Sambuc {
57433d6423SLionel Sambuc nexttm.tm_min= 0;
58433d6423SLionel Sambuc nexttm.tm_hour++;
59433d6423SLionel Sambuc }
60433d6423SLionel Sambuc if (nexttm.tm_hour > 23)
61433d6423SLionel Sambuc {
62433d6423SLionel Sambuc nexttm.tm_min= 0;
63433d6423SLionel Sambuc nexttm.tm_hour= 0;
64433d6423SLionel Sambuc nexttm.tm_mday++;
65433d6423SLionel Sambuc }
66433d6423SLionel Sambuc if (nexttm.tm_mday > 31)
67433d6423SLionel Sambuc {
68433d6423SLionel Sambuc nexttm.tm_hour= nexttm.tm_min= 0;
69433d6423SLionel Sambuc nexttm.tm_mday= 1;
70433d6423SLionel Sambuc nexttm.tm_mon++;
71433d6423SLionel Sambuc }
72433d6423SLionel Sambuc if (nexttm.tm_mon >= 12)
73433d6423SLionel Sambuc {
74433d6423SLionel Sambuc nexttm.tm_hour= nexttm.tm_min= 0;
75433d6423SLionel Sambuc nexttm.tm_mday= 1;
76433d6423SLionel Sambuc nexttm.tm_mon= 0;
77433d6423SLionel Sambuc nexttm.tm_year++;
78433d6423SLionel Sambuc }
79433d6423SLionel Sambuc
80433d6423SLionel Sambuc /* Verify tm_year. A crontab entry cannot restrict tm_year
81433d6423SLionel Sambuc * directly. However, certain dates (such as Feb, 29th) do
82433d6423SLionel Sambuc * not occur every year. We limit the difference between
83433d6423SLionel Sambuc * nexttm.tm_year and prevtm.tm_year to detect impossible dates
84433d6423SLionel Sambuc * (e.g, Feb, 31st). In theory every date occurs within a
85433d6423SLionel Sambuc * period of 4 years. However, some years at the end of a
86433d6423SLionel Sambuc * century are not leap years (e.g, the year 2100). An extra
87433d6423SLionel Sambuc * factor of 2 should be enough.
88433d6423SLionel Sambuc */
89433d6423SLionel Sambuc if (nexttm.tm_year-prevtm.tm_year > 2*4)
90433d6423SLionel Sambuc {
91433d6423SLionel Sambuc job->rtime= NEVER;
92433d6423SLionel Sambuc return; /* Impossible combination */
93433d6423SLionel Sambuc }
94433d6423SLionel Sambuc
95433d6423SLionel Sambuc if (!job->do_wday)
96433d6423SLionel Sambuc {
97433d6423SLionel Sambuc /* Verify the mon and mday fields. If do_wday and
98433d6423SLionel Sambuc * do_mday are both true we have to merge both
99433d6423SLionel Sambuc * schedules. This is done after the call to mktime.
100433d6423SLionel Sambuc */
101433d6423SLionel Sambuc if (!bit_isset(job->mon, nexttm.tm_mon))
102433d6423SLionel Sambuc {
103433d6423SLionel Sambuc /* Clear other fields */
104433d6423SLionel Sambuc nexttm.tm_mday= 1;
105433d6423SLionel Sambuc nexttm.tm_hour= nexttm.tm_min= 0;
106433d6423SLionel Sambuc
107433d6423SLionel Sambuc nexttm.tm_mon++;
108433d6423SLionel Sambuc continue;
109433d6423SLionel Sambuc }
110433d6423SLionel Sambuc
111433d6423SLionel Sambuc /* Verify mday */
112433d6423SLionel Sambuc if (!bit_isset(job->mday, nexttm.tm_mday))
113433d6423SLionel Sambuc {
114433d6423SLionel Sambuc /* Clear other fields */
115433d6423SLionel Sambuc nexttm.tm_hour= nexttm.tm_min= 0;
116433d6423SLionel Sambuc
117433d6423SLionel Sambuc nexttm.tm_mday++;
118433d6423SLionel Sambuc continue;
119433d6423SLionel Sambuc }
120433d6423SLionel Sambuc }
121433d6423SLionel Sambuc
122433d6423SLionel Sambuc /* Verify hour */
123433d6423SLionel Sambuc if (!bit_isset(job->hour, nexttm.tm_hour))
124433d6423SLionel Sambuc {
125433d6423SLionel Sambuc /* Clear tm_min field */
126433d6423SLionel Sambuc nexttm.tm_min= 0;
127433d6423SLionel Sambuc
128433d6423SLionel Sambuc nexttm.tm_hour++;
129433d6423SLionel Sambuc continue;
130433d6423SLionel Sambuc }
131433d6423SLionel Sambuc
132433d6423SLionel Sambuc /* Verify min */
133433d6423SLionel Sambuc if (!bit_isset(job->min, nexttm.tm_min))
134433d6423SLionel Sambuc {
135433d6423SLionel Sambuc nexttm.tm_min++;
136433d6423SLionel Sambuc continue;
137433d6423SLionel Sambuc }
138433d6423SLionel Sambuc
139433d6423SLionel Sambuc /* Verify that we don't have any problem with DST. Try
140433d6423SLionel Sambuc * tm_isdst=0 first. */
141433d6423SLionel Sambuc tmptm= nexttm;
142433d6423SLionel Sambuc tmptm.tm_isdst= 0;
143433d6423SLionel Sambuc #if 0
144433d6423SLionel Sambuc fprintf(stderr,
145433d6423SLionel Sambuc "tab_reschedule: trying %04d-%02d-%02d %02d:%02d:%02d isdst=0\n",
146433d6423SLionel Sambuc 1900+nexttm.tm_year, nexttm.tm_mon+1,
147433d6423SLionel Sambuc nexttm.tm_mday, nexttm.tm_hour,
148433d6423SLionel Sambuc nexttm.tm_min, nexttm.tm_sec);
149433d6423SLionel Sambuc #endif
150433d6423SLionel Sambuc nodst_rtime= job->rtime= mktime(&tmptm);
151433d6423SLionel Sambuc if (job->rtime == -1) {
152433d6423SLionel Sambuc /* This should not happen. */
153*d0055759SDavid van Moolenbroek cronlog(LOG_ERR,
154433d6423SLionel Sambuc "mktime failed for %04d-%02d-%02d %02d:%02d:%02d",
155433d6423SLionel Sambuc 1900+nexttm.tm_year, nexttm.tm_mon+1,
156433d6423SLionel Sambuc nexttm.tm_mday, nexttm.tm_hour,
157433d6423SLionel Sambuc nexttm.tm_min, nexttm.tm_sec);
158433d6423SLionel Sambuc job->rtime= NEVER;
159433d6423SLionel Sambuc return;
160433d6423SLionel Sambuc }
161433d6423SLionel Sambuc tmptm= *localtime(&job->rtime);
162433d6423SLionel Sambuc if (tmptm.tm_hour != nexttm.tm_hour ||
163433d6423SLionel Sambuc tmptm.tm_min != nexttm.tm_min)
164433d6423SLionel Sambuc {
165433d6423SLionel Sambuc assert(tmptm.tm_isdst);
166433d6423SLionel Sambuc tmptm= nexttm;
167433d6423SLionel Sambuc tmptm.tm_isdst= 1;
168433d6423SLionel Sambuc #if 0
169433d6423SLionel Sambuc fprintf(stderr,
170433d6423SLionel Sambuc "tab_reschedule: trying %04d-%02d-%02d %02d:%02d:%02d isdst=1\n",
171433d6423SLionel Sambuc 1900+nexttm.tm_year, nexttm.tm_mon+1,
172433d6423SLionel Sambuc nexttm.tm_mday, nexttm.tm_hour,
173433d6423SLionel Sambuc nexttm.tm_min, nexttm.tm_sec);
174433d6423SLionel Sambuc #endif
175433d6423SLionel Sambuc dst_rtime= job->rtime= mktime(&tmptm);
176433d6423SLionel Sambuc if (job->rtime == -1) {
177433d6423SLionel Sambuc /* This should not happen. */
178*d0055759SDavid van Moolenbroek cronlog(LOG_ERR,
179433d6423SLionel Sambuc "mktime failed for %04d-%02d-%02d %02d:%02d:%02d\n",
180433d6423SLionel Sambuc 1900+nexttm.tm_year, nexttm.tm_mon+1,
181433d6423SLionel Sambuc nexttm.tm_mday, nexttm.tm_hour,
182433d6423SLionel Sambuc nexttm.tm_min, nexttm.tm_sec);
183433d6423SLionel Sambuc job->rtime= NEVER;
184433d6423SLionel Sambuc return;
185433d6423SLionel Sambuc }
186433d6423SLionel Sambuc tmptm= *localtime(&job->rtime);
187433d6423SLionel Sambuc if (tmptm.tm_hour != nexttm.tm_hour ||
188433d6423SLionel Sambuc tmptm.tm_min != nexttm.tm_min)
189433d6423SLionel Sambuc {
190433d6423SLionel Sambuc assert(!tmptm.tm_isdst);
191433d6423SLionel Sambuc /* We have a problem. This time neither
192433d6423SLionel Sambuc * exists with DST nor without DST.
193433d6423SLionel Sambuc * Use the latest time, which should be
194433d6423SLionel Sambuc * nodst_rtime.
195433d6423SLionel Sambuc */
196433d6423SLionel Sambuc assert(nodst_rtime > dst_rtime);
197433d6423SLionel Sambuc job->rtime= nodst_rtime;
198433d6423SLionel Sambuc #if 0
199433d6423SLionel Sambuc fprintf(stderr,
200433d6423SLionel Sambuc "During DST trans. %04d-%02d-%02d %02d:%02d:%02d\n",
201433d6423SLionel Sambuc 1900+nexttm.tm_year, nexttm.tm_mon+1,
202433d6423SLionel Sambuc nexttm.tm_mday, nexttm.tm_hour,
203433d6423SLionel Sambuc nexttm.tm_min, nexttm.tm_sec);
204433d6423SLionel Sambuc #endif
205433d6423SLionel Sambuc }
206433d6423SLionel Sambuc }
207433d6423SLionel Sambuc
208433d6423SLionel Sambuc /* Verify this the combination (tm_year, tm_mon, tm_mday). */
209433d6423SLionel Sambuc if (tmptm.tm_mday != nexttm.tm_mday ||
210433d6423SLionel Sambuc tmptm.tm_mon != nexttm.tm_mon ||
211433d6423SLionel Sambuc tmptm.tm_year != nexttm.tm_year)
212433d6423SLionel Sambuc {
213433d6423SLionel Sambuc /* Wrong day */
214433d6423SLionel Sambuc #if 0
215433d6423SLionel Sambuc fprintf(stderr, "Wrong day\n");
216433d6423SLionel Sambuc #endif
217433d6423SLionel Sambuc nexttm.tm_hour= nexttm.tm_min= 0;
218433d6423SLionel Sambuc nexttm.tm_mday++;
219433d6423SLionel Sambuc continue;
220433d6423SLionel Sambuc }
221433d6423SLionel Sambuc
222433d6423SLionel Sambuc /* Check tm_wday */
223433d6423SLionel Sambuc if (job->do_wday && bit_isset(job->wday, tmptm.tm_wday))
224433d6423SLionel Sambuc {
225433d6423SLionel Sambuc /* OK, wday matched */
226433d6423SLionel Sambuc break;
227433d6423SLionel Sambuc }
228433d6423SLionel Sambuc
229433d6423SLionel Sambuc /* Check tm_mday */
230433d6423SLionel Sambuc if (job->do_mday && bit_isset(job->mon, tmptm.tm_mon) &&
231433d6423SLionel Sambuc bit_isset(job->mday, tmptm.tm_mday))
232433d6423SLionel Sambuc {
233433d6423SLionel Sambuc /* OK, mon and mday matched */
234433d6423SLionel Sambuc break;
235433d6423SLionel Sambuc }
236433d6423SLionel Sambuc
237433d6423SLionel Sambuc if (!job->do_wday && !job->do_mday)
238433d6423SLionel Sambuc {
239433d6423SLionel Sambuc /* No need to match wday and mday */
240433d6423SLionel Sambuc break;
241433d6423SLionel Sambuc }
242433d6423SLionel Sambuc
243433d6423SLionel Sambuc /* Wrong day */
244433d6423SLionel Sambuc #if 0
245433d6423SLionel Sambuc fprintf(stderr, "Wrong mon+mday or wday\n");
246433d6423SLionel Sambuc #endif
247433d6423SLionel Sambuc nexttm.tm_hour= nexttm.tm_min= 0;
248433d6423SLionel Sambuc nexttm.tm_mday++;
249433d6423SLionel Sambuc }
250433d6423SLionel Sambuc #if 0
251433d6423SLionel Sambuc fprintf(stderr, "Using %04d-%02d-%02d %02d:%02d:%02d \n",
252433d6423SLionel Sambuc 1900+nexttm.tm_year, nexttm.tm_mon+1, nexttm.tm_mday,
253433d6423SLionel Sambuc nexttm.tm_hour, nexttm.tm_min, nexttm.tm_sec);
254433d6423SLionel Sambuc tmptm= *localtime(&job->rtime);
255433d6423SLionel Sambuc fprintf(stderr, "Act. %04d-%02d-%02d %02d:%02d:%02d isdst=%d\n",
256433d6423SLionel Sambuc 1900+tmptm.tm_year, tmptm.tm_mon+1, tmptm.tm_mday,
257433d6423SLionel Sambuc tmptm.tm_hour, tmptm.tm_min, tmptm.tm_sec,
258433d6423SLionel Sambuc tmptm.tm_isdst);
259433d6423SLionel Sambuc #endif
260433d6423SLionel Sambuc
261433d6423SLionel Sambuc
262433d6423SLionel Sambuc /* Is job issuing lagging behind with the progress of time? */
263433d6423SLionel Sambuc job->late= (job->rtime < now);
264433d6423SLionel Sambuc
265433d6423SLionel Sambuc /* The result is in job->rtime. */
266433d6423SLionel Sambuc if (job->rtime < next) next= job->rtime;
267433d6423SLionel Sambuc }
268433d6423SLionel Sambuc
269433d6423SLionel Sambuc #define isdigit(c) ((unsigned) ((c) - '0') < 10)
270433d6423SLionel Sambuc
get_token(char ** ptr)271433d6423SLionel Sambuc static char *get_token(char **ptr)
272433d6423SLionel Sambuc /* Return a pointer to the next token in a string. Move *ptr to the end of
273433d6423SLionel Sambuc * the token, and return a pointer to the start. If *ptr == start of token
274433d6423SLionel Sambuc * then we're stuck against a newline or end of string.
275433d6423SLionel Sambuc */
276433d6423SLionel Sambuc {
277433d6423SLionel Sambuc char *start, *p;
278433d6423SLionel Sambuc
279433d6423SLionel Sambuc p= *ptr;
280433d6423SLionel Sambuc while (*p == ' ' || *p == '\t') p++;
281433d6423SLionel Sambuc
282433d6423SLionel Sambuc start= p;
283433d6423SLionel Sambuc while (*p != ' ' && *p != '\t' && *p != '\n' && *p != 0) p++;
284433d6423SLionel Sambuc *ptr= p;
285433d6423SLionel Sambuc return start;
286433d6423SLionel Sambuc }
287433d6423SLionel Sambuc
range_parse(char * file,char * data,bitmap_t map,int min,int max,int * wildcard)288433d6423SLionel Sambuc static int range_parse(char *file, char *data, bitmap_t map,
289433d6423SLionel Sambuc int min, int max, int *wildcard)
290433d6423SLionel Sambuc /* Parse a comma separated series of 'n', 'n-m' or 'n:m' numbers. 'n'
291433d6423SLionel Sambuc * includes number 'n' in the bit map, 'n-m' includes all numbers between
292433d6423SLionel Sambuc * 'n' and 'm' inclusive, and 'n:m' includes 'n+k*m' for any k if in range.
293433d6423SLionel Sambuc * Numbers must fall between 'min' and 'max'. A '*' means all numbers. A
294433d6423SLionel Sambuc * '?' is allowed as a synonym for the current minute, which only makes sense
295433d6423SLionel Sambuc * in the minute field, i.e. max must be 59. Return true iff parsed ok.
296433d6423SLionel Sambuc */
297433d6423SLionel Sambuc {
298433d6423SLionel Sambuc char *p;
299433d6423SLionel Sambuc int end;
300433d6423SLionel Sambuc int n, m;
301433d6423SLionel Sambuc
302433d6423SLionel Sambuc /* Clear all bits. */
303433d6423SLionel Sambuc for (n= 0; n < 8; n++) map[n]= 0;
304433d6423SLionel Sambuc
305433d6423SLionel Sambuc p= data;
306433d6423SLionel Sambuc while (*p != ' ' && *p != '\t' && *p != '\n' && *p != 0) p++;
307433d6423SLionel Sambuc end= *p;
308433d6423SLionel Sambuc *p= 0;
309433d6423SLionel Sambuc p= data;
310433d6423SLionel Sambuc
311433d6423SLionel Sambuc if (*p == 0) {
312*d0055759SDavid van Moolenbroek cronlog(LOG_ERR, "%s: not enough time fields\n", file);
313433d6423SLionel Sambuc return 0;
314433d6423SLionel Sambuc }
315433d6423SLionel Sambuc
316433d6423SLionel Sambuc /* Is it a '*'? */
317433d6423SLionel Sambuc if (p[0] == '*' && p[1] == 0) {
318433d6423SLionel Sambuc for (n= min; n <= max; n++) bit_set(map, n);
319433d6423SLionel Sambuc p[1]= end;
320433d6423SLionel Sambuc *wildcard= 1;
321433d6423SLionel Sambuc return 1;
322433d6423SLionel Sambuc }
323433d6423SLionel Sambuc *wildcard= 0;
324433d6423SLionel Sambuc
325433d6423SLionel Sambuc /* Parse a comma separated series of numbers or ranges. */
326433d6423SLionel Sambuc for (;;) {
327433d6423SLionel Sambuc if (*p == '?' && max == 59 && p[1] != '-') {
328433d6423SLionel Sambuc n= localtime(&now)->tm_min;
329433d6423SLionel Sambuc p++;
330433d6423SLionel Sambuc } else {
331433d6423SLionel Sambuc if (!isdigit(*p)) goto syntax;
332433d6423SLionel Sambuc n= 0;
333433d6423SLionel Sambuc do {
334433d6423SLionel Sambuc n= 10 * n + (*p++ - '0');
335433d6423SLionel Sambuc if (n > max) goto range;
336433d6423SLionel Sambuc } while (isdigit(*p));
337433d6423SLionel Sambuc }
338433d6423SLionel Sambuc if (n < min) goto range;
339433d6423SLionel Sambuc
340433d6423SLionel Sambuc if (*p == '-') { /* A range of the form 'n-m'? */
341433d6423SLionel Sambuc p++;
342433d6423SLionel Sambuc if (!isdigit(*p)) goto syntax;
343433d6423SLionel Sambuc m= 0;
344433d6423SLionel Sambuc do {
345433d6423SLionel Sambuc m= 10 * m + (*p++ - '0');
346433d6423SLionel Sambuc if (m > max) goto range;
347433d6423SLionel Sambuc } while (isdigit(*p));
348433d6423SLionel Sambuc if (m < n) goto range;
349433d6423SLionel Sambuc do {
350433d6423SLionel Sambuc bit_set(map, n);
351433d6423SLionel Sambuc } while (++n <= m);
352433d6423SLionel Sambuc } else
353433d6423SLionel Sambuc if (*p == ':') { /* A repeat of the form 'n:m'? */
354433d6423SLionel Sambuc p++;
355433d6423SLionel Sambuc if (!isdigit(*p)) goto syntax;
356433d6423SLionel Sambuc m= 0;
357433d6423SLionel Sambuc do {
358433d6423SLionel Sambuc m= 10 * m + (*p++ - '0');
359433d6423SLionel Sambuc if (m > (max-min+1)) goto range;
360433d6423SLionel Sambuc } while (isdigit(*p));
361433d6423SLionel Sambuc if (m == 0) goto range;
362433d6423SLionel Sambuc while (n >= min) n-= m;
363433d6423SLionel Sambuc while ((n+= m) <= max) bit_set(map, n);
364433d6423SLionel Sambuc } else {
365433d6423SLionel Sambuc /* Simply a number */
366433d6423SLionel Sambuc bit_set(map, n);
367433d6423SLionel Sambuc }
368433d6423SLionel Sambuc if (*p == 0) break;
369433d6423SLionel Sambuc if (*p++ != ',') goto syntax;
370433d6423SLionel Sambuc }
371433d6423SLionel Sambuc *p= end;
372433d6423SLionel Sambuc return 1;
373433d6423SLionel Sambuc syntax:
374*d0055759SDavid van Moolenbroek cronlog(LOG_ERR, "%s: field '%s': bad syntax for a %d-%d time field\n",
375433d6423SLionel Sambuc file, data, min, max);
376433d6423SLionel Sambuc return 0;
377433d6423SLionel Sambuc range:
378*d0055759SDavid van Moolenbroek cronlog(LOG_ERR,
379*d0055759SDavid van Moolenbroek "%s: field '%s': values out of the %d-%d allowed range\n",
380433d6423SLionel Sambuc file, data, min, max);
381433d6423SLionel Sambuc return 0;
382433d6423SLionel Sambuc }
383433d6423SLionel Sambuc
tab_parse(char * file,char * user)384433d6423SLionel Sambuc void tab_parse(char *file, char *user)
385433d6423SLionel Sambuc /* Parse a crontab file and add its data to the tables. Handle errors by
386433d6423SLionel Sambuc * yourself. Table is owned by 'user' if non-null.
387433d6423SLionel Sambuc */
388433d6423SLionel Sambuc {
389433d6423SLionel Sambuc crontab_t **atab, *tab;
390433d6423SLionel Sambuc cronjob_t **ajob, *job;
391433d6423SLionel Sambuc int fd;
392433d6423SLionel Sambuc struct stat st;
393433d6423SLionel Sambuc char *p, *q;
394433d6423SLionel Sambuc size_t n;
395433d6423SLionel Sambuc ssize_t r;
396433d6423SLionel Sambuc int ok, wc;
397433d6423SLionel Sambuc
398433d6423SLionel Sambuc for (atab= &crontabs; (tab= *atab) != nil; atab= &tab->next) {
399433d6423SLionel Sambuc if (strcmp(file, tab->file) == 0) break;
400433d6423SLionel Sambuc }
401433d6423SLionel Sambuc
402433d6423SLionel Sambuc /* Try to open the file. */
403433d6423SLionel Sambuc if ((fd= open(file, O_RDONLY)) < 0 || fstat(fd, &st) < 0) {
404433d6423SLionel Sambuc if (errno != ENOENT) {
405*d0055759SDavid van Moolenbroek cronlog(LOG_ERR, "%s: %s\n", file, strerror(errno));
406433d6423SLionel Sambuc }
407433d6423SLionel Sambuc if (fd != -1) close(fd);
408433d6423SLionel Sambuc return;
409433d6423SLionel Sambuc }
410433d6423SLionel Sambuc
411433d6423SLionel Sambuc /* Forget it if the file is awfully big. */
412433d6423SLionel Sambuc if (st.st_size > TAB_MAX) {
413*d0055759SDavid van Moolenbroek cronlog(LOG_ERR, "%s: %lu bytes is bigger than my %lu limit\n",
414433d6423SLionel Sambuc file,
415433d6423SLionel Sambuc (unsigned long) st.st_size,
416433d6423SLionel Sambuc (unsigned long) TAB_MAX);
417433d6423SLionel Sambuc return;
418433d6423SLionel Sambuc }
419433d6423SLionel Sambuc
420433d6423SLionel Sambuc /* If the file is the same as before then don't bother. */
421433d6423SLionel Sambuc if (tab != nil && st.st_mtime == tab->mtime) {
422433d6423SLionel Sambuc close(fd);
423433d6423SLionel Sambuc tab->current= 1;
424433d6423SLionel Sambuc return;
425433d6423SLionel Sambuc }
426433d6423SLionel Sambuc
427433d6423SLionel Sambuc /* Create a new table structure. */
428433d6423SLionel Sambuc tab= allocate(sizeof(*tab));
429433d6423SLionel Sambuc tab->file= allocate((strlen(file) + 1) * sizeof(tab->file[0]));
430433d6423SLionel Sambuc strcpy(tab->file, file);
431433d6423SLionel Sambuc tab->user= nil;
432433d6423SLionel Sambuc if (user != nil) {
433433d6423SLionel Sambuc tab->user= allocate((strlen(user) + 1) * sizeof(tab->user[0]));
434433d6423SLionel Sambuc strcpy(tab->user, user);
435433d6423SLionel Sambuc }
436433d6423SLionel Sambuc tab->data= allocate((st.st_size + 1) * sizeof(tab->data[0]));
437433d6423SLionel Sambuc tab->jobs= nil;
438433d6423SLionel Sambuc tab->mtime= st.st_mtime;
439433d6423SLionel Sambuc tab->current= 0;
440433d6423SLionel Sambuc tab->next= *atab;
441433d6423SLionel Sambuc *atab= tab;
442433d6423SLionel Sambuc
443433d6423SLionel Sambuc /* Pull a new table in core. */
444433d6423SLionel Sambuc n= 0;
445433d6423SLionel Sambuc while (n < st.st_size) {
446433d6423SLionel Sambuc if ((r = read(fd, tab->data + n, st.st_size - n)) < 0) {
447*d0055759SDavid van Moolenbroek cronlog(LOG_CRIT, "%s: %s", file, strerror(errno));
448433d6423SLionel Sambuc close(fd);
449433d6423SLionel Sambuc return;
450433d6423SLionel Sambuc }
451433d6423SLionel Sambuc if (r == 0) break;
452433d6423SLionel Sambuc n+= r;
453433d6423SLionel Sambuc }
454433d6423SLionel Sambuc close(fd);
455433d6423SLionel Sambuc tab->data[n]= 0;
456433d6423SLionel Sambuc if (strlen(tab->data) < n) {
457*d0055759SDavid van Moolenbroek cronlog(LOG_ERR, "%s contains a null character\n", file);
458433d6423SLionel Sambuc return;
459433d6423SLionel Sambuc }
460433d6423SLionel Sambuc
461433d6423SLionel Sambuc /* Parse the file. */
462433d6423SLionel Sambuc ajob= &tab->jobs;
463433d6423SLionel Sambuc p= tab->data;
464433d6423SLionel Sambuc ok= 1;
465433d6423SLionel Sambuc while (ok && *p != 0) {
466433d6423SLionel Sambuc q= get_token(&p);
467433d6423SLionel Sambuc if (*q == '#' || q == p) {
468433d6423SLionel Sambuc /* Comment or empty. */
469433d6423SLionel Sambuc while (*p != 0 && *p++ != '\n') {}
470433d6423SLionel Sambuc continue;
471433d6423SLionel Sambuc }
472433d6423SLionel Sambuc
473433d6423SLionel Sambuc /* One new job coming up. */
474433d6423SLionel Sambuc *ajob= job= allocate(sizeof(*job));
475433d6423SLionel Sambuc *(ajob= &job->next)= nil;
476433d6423SLionel Sambuc job->tab= tab;
477433d6423SLionel Sambuc
478433d6423SLionel Sambuc if (!range_parse(file, q, job->min, 0, 59, &wc)) {
479433d6423SLionel Sambuc ok= 0;
480433d6423SLionel Sambuc break;
481433d6423SLionel Sambuc }
482433d6423SLionel Sambuc
483433d6423SLionel Sambuc q= get_token(&p);
484433d6423SLionel Sambuc if (!range_parse(file, q, job->hour, 0, 23, &wc)) {
485433d6423SLionel Sambuc ok= 0;
486433d6423SLionel Sambuc break;
487433d6423SLionel Sambuc }
488433d6423SLionel Sambuc
489433d6423SLionel Sambuc q= get_token(&p);
490433d6423SLionel Sambuc if (!range_parse(file, q, job->mday, 1, 31, &wc)) {
491433d6423SLionel Sambuc ok= 0;
492433d6423SLionel Sambuc break;
493433d6423SLionel Sambuc }
494433d6423SLionel Sambuc job->do_mday= !wc;
495433d6423SLionel Sambuc
496433d6423SLionel Sambuc q= get_token(&p);
497433d6423SLionel Sambuc if (!range_parse(file, q, job->mon, 1, 12, &wc)) {
498433d6423SLionel Sambuc ok= 0;
499433d6423SLionel Sambuc break;
500433d6423SLionel Sambuc }
501433d6423SLionel Sambuc job->do_mday |= !wc;
502433d6423SLionel Sambuc
503433d6423SLionel Sambuc q= get_token(&p);
504433d6423SLionel Sambuc if (!range_parse(file, q, job->wday, 0, 7, &wc)) {
505433d6423SLionel Sambuc ok= 0;
506433d6423SLionel Sambuc break;
507433d6423SLionel Sambuc }
508433d6423SLionel Sambuc job->do_wday= !wc;
509433d6423SLionel Sambuc
510433d6423SLionel Sambuc /* 7 is Sunday, but 0 is a common mistake because it is in the
511433d6423SLionel Sambuc * tm_wday range. We allow and even prefer it internally.
512433d6423SLionel Sambuc */
513433d6423SLionel Sambuc if (bit_isset(job->wday, 7)) {
514433d6423SLionel Sambuc bit_clr(job->wday, 7);
515433d6423SLionel Sambuc bit_set(job->wday, 0);
516433d6423SLionel Sambuc }
517433d6423SLionel Sambuc
518433d6423SLionel Sambuc /* The month range is 1-12, but tm_mon likes 0-11. */
519433d6423SLionel Sambuc job->mon[0] >>= 1;
520433d6423SLionel Sambuc if (bit_isset(job->mon, 8)) bit_set(job->mon, 7);
521433d6423SLionel Sambuc job->mon[1] >>= 1;
522433d6423SLionel Sambuc
523433d6423SLionel Sambuc /* Scan for options. */
524433d6423SLionel Sambuc job->user= nil;
525433d6423SLionel Sambuc while (q= get_token(&p), *q == '-') {
526433d6423SLionel Sambuc q++;
527433d6423SLionel Sambuc if (q[0] == '-' && q+1 == p) {
528433d6423SLionel Sambuc /* -- */
529433d6423SLionel Sambuc q= get_token(&p);
530433d6423SLionel Sambuc break;
531433d6423SLionel Sambuc }
532433d6423SLionel Sambuc while (q < p) switch (*q++) {
533433d6423SLionel Sambuc case 'u':
534433d6423SLionel Sambuc if (q == p) q= get_token(&p);
535433d6423SLionel Sambuc if (q == p) goto usage;
536433d6423SLionel Sambuc memmove(q-1, q, p-q); /* gross... */
537433d6423SLionel Sambuc p[-1]= 0;
538433d6423SLionel Sambuc job->user= q-1;
539433d6423SLionel Sambuc q= p;
540433d6423SLionel Sambuc break;
541433d6423SLionel Sambuc default:
542433d6423SLionel Sambuc usage:
543*d0055759SDavid van Moolenbroek cronlog(LOG_ERR,
544433d6423SLionel Sambuc "%s: bad option -%c, good options are: -u username\n",
545433d6423SLionel Sambuc file, q[-1]);
546433d6423SLionel Sambuc ok= 0;
547433d6423SLionel Sambuc goto endtab;
548433d6423SLionel Sambuc }
549433d6423SLionel Sambuc }
550433d6423SLionel Sambuc
551433d6423SLionel Sambuc /* A crontab owned by a user can only do things as that user. */
552433d6423SLionel Sambuc if (tab->user != nil) job->user= tab->user;
553433d6423SLionel Sambuc
554433d6423SLionel Sambuc /* Inspect the first character of the command. */
555433d6423SLionel Sambuc job->cmd= q;
556433d6423SLionel Sambuc if (q == p || *q == '#') {
557433d6423SLionel Sambuc /* Rest of the line is empty, i.e. the commands are on
558433d6423SLionel Sambuc * the following lines indented by one tab.
559433d6423SLionel Sambuc */
560433d6423SLionel Sambuc while (*p != 0 && *p++ != '\n') {}
561433d6423SLionel Sambuc if (*p++ != '\t') {
562*d0055759SDavid van Moolenbroek cronlog(LOG_ERR,
563*d0055759SDavid van Moolenbroek "%s: contains an empty command\n",
564433d6423SLionel Sambuc file);
565433d6423SLionel Sambuc ok= 0;
566433d6423SLionel Sambuc goto endtab;
567433d6423SLionel Sambuc }
568433d6423SLionel Sambuc while (*p != 0) {
569433d6423SLionel Sambuc if ((*q = *p++) == '\n') {
570433d6423SLionel Sambuc if (*p != '\t') break;
571433d6423SLionel Sambuc p++;
572433d6423SLionel Sambuc }
573433d6423SLionel Sambuc q++;
574433d6423SLionel Sambuc }
575433d6423SLionel Sambuc } else {
576433d6423SLionel Sambuc /* The command is on this line. Alas we must now be
577433d6423SLionel Sambuc * backwards compatible and change %'s to newlines.
578433d6423SLionel Sambuc */
579433d6423SLionel Sambuc p= q;
580433d6423SLionel Sambuc while (*p != 0) {
581433d6423SLionel Sambuc if ((*q = *p++) == '\n') break;
582433d6423SLionel Sambuc if (*q == '%') *q= '\n';
583433d6423SLionel Sambuc q++;
584433d6423SLionel Sambuc }
585433d6423SLionel Sambuc }
586433d6423SLionel Sambuc *q= 0;
587433d6423SLionel Sambuc job->rtime= now;
588433d6423SLionel Sambuc job->late= 0; /* It is on time. */
589433d6423SLionel Sambuc job->atjob= 0; /* True cron job. */
590433d6423SLionel Sambuc job->pid= IDLE_PID; /* Not running yet. */
591433d6423SLionel Sambuc tab_reschedule(job); /* Compute next time to run. */
592433d6423SLionel Sambuc }
593433d6423SLionel Sambuc endtab:
594433d6423SLionel Sambuc
595433d6423SLionel Sambuc if (ok) tab->current= 1;
596433d6423SLionel Sambuc }
597433d6423SLionel Sambuc
tab_find_atjob(char * atdir)598433d6423SLionel Sambuc void tab_find_atjob(char *atdir)
599433d6423SLionel Sambuc /* Find the first to be executed AT job and kludge up an crontab job for it.
600433d6423SLionel Sambuc * We set tab->file to "atdir/jobname", tab->data to "atdir/past/jobname",
601433d6423SLionel Sambuc * and job->cmd to "jobname".
602433d6423SLionel Sambuc */
603433d6423SLionel Sambuc {
604433d6423SLionel Sambuc DIR *spool;
605433d6423SLionel Sambuc struct dirent *entry;
606433d6423SLionel Sambuc time_t t0, t1;
607433d6423SLionel Sambuc struct tm tmnow, tmt1;
608433d6423SLionel Sambuc static char template[] = "96.365.1546.00";
609433d6423SLionel Sambuc char firstjob[sizeof(template)];
610433d6423SLionel Sambuc int i;
611433d6423SLionel Sambuc crontab_t *tab;
612433d6423SLionel Sambuc cronjob_t *job;
613433d6423SLionel Sambuc
614433d6423SLionel Sambuc if ((spool= opendir(atdir)) == nil) return;
615433d6423SLionel Sambuc
616433d6423SLionel Sambuc tmnow= *localtime(&now);
617433d6423SLionel Sambuc t0= NEVER;
618433d6423SLionel Sambuc
619433d6423SLionel Sambuc while ((entry= readdir(spool)) != nil) {
620433d6423SLionel Sambuc /* Check if the name fits the template. */
621433d6423SLionel Sambuc for (i= 0; template[i] != 0; i++) {
622433d6423SLionel Sambuc if (template[i] == '.') {
623433d6423SLionel Sambuc if (entry->d_name[i] != '.') break;
624433d6423SLionel Sambuc } else {
625433d6423SLionel Sambuc if (!isdigit(entry->d_name[i])) break;
626433d6423SLionel Sambuc }
627433d6423SLionel Sambuc }
628433d6423SLionel Sambuc if (template[i] != 0 || entry->d_name[i] != 0) continue;
629433d6423SLionel Sambuc
630433d6423SLionel Sambuc /* Convert the name to a time. Careful with the century. */
631433d6423SLionel Sambuc memset(&tmt1, 0, sizeof(tmt1));
632433d6423SLionel Sambuc tmt1.tm_year= atoi(entry->d_name+0);
633433d6423SLionel Sambuc while (tmt1.tm_year < tmnow.tm_year-10) tmt1.tm_year+= 100;
634433d6423SLionel Sambuc tmt1.tm_mday= 1+atoi(entry->d_name+3);
635433d6423SLionel Sambuc tmt1.tm_min= atoi(entry->d_name+7);
636433d6423SLionel Sambuc tmt1.tm_hour= tmt1.tm_min / 100;
637433d6423SLionel Sambuc tmt1.tm_min%= 100;
638433d6423SLionel Sambuc tmt1.tm_isdst= -1;
639433d6423SLionel Sambuc if ((t1= mktime(&tmt1)) == -1) {
640433d6423SLionel Sambuc /* Illegal time? Try in winter time. */
641433d6423SLionel Sambuc tmt1.tm_isdst= 0;
642433d6423SLionel Sambuc if ((t1= mktime(&tmt1)) == -1) continue;
643433d6423SLionel Sambuc }
644433d6423SLionel Sambuc if (t1 < t0) {
645433d6423SLionel Sambuc t0= t1;
646433d6423SLionel Sambuc strcpy(firstjob, entry->d_name);
647433d6423SLionel Sambuc }
648433d6423SLionel Sambuc }
649433d6423SLionel Sambuc closedir(spool);
650433d6423SLionel Sambuc
651433d6423SLionel Sambuc if (t0 == NEVER) return; /* AT job spool is empty. */
652433d6423SLionel Sambuc
653433d6423SLionel Sambuc /* Create new table and job structures. */
654433d6423SLionel Sambuc tab= allocate(sizeof(*tab));
655433d6423SLionel Sambuc tab->file= allocate((strlen(atdir) + 1 + sizeof(template))
656433d6423SLionel Sambuc * sizeof(tab->file[0]));
657433d6423SLionel Sambuc strcpy(tab->file, atdir);
658433d6423SLionel Sambuc strcat(tab->file, "/");
659433d6423SLionel Sambuc strcat(tab->file, firstjob);
660433d6423SLionel Sambuc tab->data= allocate((strlen(atdir) + 6 + sizeof(template))
661433d6423SLionel Sambuc * sizeof(tab->data[0]));
662433d6423SLionel Sambuc strcpy(tab->data, atdir);
663433d6423SLionel Sambuc strcat(tab->data, "/past/");
664433d6423SLionel Sambuc strcat(tab->data, firstjob);
665433d6423SLionel Sambuc tab->user= nil;
666433d6423SLionel Sambuc tab->mtime= 0;
667433d6423SLionel Sambuc tab->current= 1;
668433d6423SLionel Sambuc tab->next= crontabs;
669433d6423SLionel Sambuc crontabs= tab;
670433d6423SLionel Sambuc
671433d6423SLionel Sambuc tab->jobs= job= allocate(sizeof(*job));
672433d6423SLionel Sambuc job->next= nil;
673433d6423SLionel Sambuc job->tab= tab;
674433d6423SLionel Sambuc job->user= nil;
675433d6423SLionel Sambuc job->cmd= tab->data + strlen(atdir) + 6;
676433d6423SLionel Sambuc job->rtime= t0;
677433d6423SLionel Sambuc job->late= 0;
678433d6423SLionel Sambuc job->atjob= 1; /* One AT job disguised as a cron job. */
679433d6423SLionel Sambuc job->pid= IDLE_PID;
680433d6423SLionel Sambuc
681433d6423SLionel Sambuc if (job->rtime < next) next= job->rtime;
682433d6423SLionel Sambuc }
683433d6423SLionel Sambuc
tab_purge(void)684433d6423SLionel Sambuc void tab_purge(void)
685433d6423SLionel Sambuc /* Remove table data that is no longer current. E.g. a crontab got removed.
686433d6423SLionel Sambuc */
687433d6423SLionel Sambuc {
688433d6423SLionel Sambuc crontab_t **atab, *tab;
689433d6423SLionel Sambuc cronjob_t *job;
690433d6423SLionel Sambuc
691433d6423SLionel Sambuc atab= &crontabs;
692433d6423SLionel Sambuc while ((tab= *atab) != nil) {
693433d6423SLionel Sambuc if (tab->current) {
694433d6423SLionel Sambuc /* Table is fine. */
695433d6423SLionel Sambuc tab->current= 0;
696433d6423SLionel Sambuc atab= &tab->next;
697433d6423SLionel Sambuc } else {
698433d6423SLionel Sambuc /* Table is not, or no longer valid; delete. */
699433d6423SLionel Sambuc *atab= tab->next;
700433d6423SLionel Sambuc while ((job= tab->jobs) != nil) {
701433d6423SLionel Sambuc tab->jobs= job->next;
702433d6423SLionel Sambuc deallocate(job);
703433d6423SLionel Sambuc }
704433d6423SLionel Sambuc deallocate(tab->data);
705433d6423SLionel Sambuc deallocate(tab->file);
706433d6423SLionel Sambuc deallocate(tab->user);
707433d6423SLionel Sambuc deallocate(tab);
708433d6423SLionel Sambuc }
709433d6423SLionel Sambuc }
710433d6423SLionel Sambuc }
711433d6423SLionel Sambuc
reap_or_find(pid_t pid)712433d6423SLionel Sambuc static cronjob_t *reap_or_find(pid_t pid)
713433d6423SLionel Sambuc /* Find a finished job or search for the next one to run. */
714433d6423SLionel Sambuc {
715433d6423SLionel Sambuc crontab_t *tab;
716433d6423SLionel Sambuc cronjob_t *job;
717433d6423SLionel Sambuc cronjob_t *nextjob;
718433d6423SLionel Sambuc
719433d6423SLionel Sambuc nextjob= nil;
720433d6423SLionel Sambuc next= NEVER;
721433d6423SLionel Sambuc for (tab= crontabs; tab != nil; tab= tab->next) {
722433d6423SLionel Sambuc for (job= tab->jobs; job != nil; job= job->next) {
723433d6423SLionel Sambuc if (job->pid == pid) {
724433d6423SLionel Sambuc job->pid= IDLE_PID;
725433d6423SLionel Sambuc tab_reschedule(job);
726433d6423SLionel Sambuc }
727433d6423SLionel Sambuc if (job->pid != IDLE_PID) continue;
728433d6423SLionel Sambuc if (job->rtime < next) next= job->rtime;
729433d6423SLionel Sambuc if (job->rtime <= now) nextjob= job;
730433d6423SLionel Sambuc }
731433d6423SLionel Sambuc }
732433d6423SLionel Sambuc return nextjob;
733433d6423SLionel Sambuc }
734433d6423SLionel Sambuc
tab_reap_job(pid_t pid)735433d6423SLionel Sambuc void tab_reap_job(pid_t pid)
736433d6423SLionel Sambuc /* A job has finished. Try to find it among the crontab data and reschedule
737433d6423SLionel Sambuc * it. Recompute time next to run a job.
738433d6423SLionel Sambuc */
739433d6423SLionel Sambuc {
740433d6423SLionel Sambuc (void) reap_or_find(pid);
741433d6423SLionel Sambuc }
742433d6423SLionel Sambuc
tab_nextjob(void)743433d6423SLionel Sambuc cronjob_t *tab_nextjob(void)
744433d6423SLionel Sambuc /* Find a job that should be run now. If none are found return null.
745433d6423SLionel Sambuc * Update 'next'.
746433d6423SLionel Sambuc */
747433d6423SLionel Sambuc {
748433d6423SLionel Sambuc return reap_or_find(NO_PID);
749433d6423SLionel Sambuc }
750433d6423SLionel Sambuc
pr_map(FILE * fp,bitmap_t map)751433d6423SLionel Sambuc static void pr_map(FILE *fp, bitmap_t map)
752433d6423SLionel Sambuc {
753433d6423SLionel Sambuc int last_bit= -1, bit;
754433d6423SLionel Sambuc char *sep;
755433d6423SLionel Sambuc
756433d6423SLionel Sambuc sep= "";
757433d6423SLionel Sambuc for (bit= 0; bit < 64; bit++) {
758433d6423SLionel Sambuc if (bit_isset(map, bit)) {
759433d6423SLionel Sambuc if (last_bit == -1) last_bit= bit;
760433d6423SLionel Sambuc } else {
761433d6423SLionel Sambuc if (last_bit != -1) {
762433d6423SLionel Sambuc fprintf(fp, "%s%d", sep, last_bit);
763433d6423SLionel Sambuc if (last_bit != bit-1) {
764433d6423SLionel Sambuc fprintf(fp, "-%d", bit-1);
765433d6423SLionel Sambuc }
766433d6423SLionel Sambuc last_bit= -1;
767433d6423SLionel Sambuc sep= ",";
768433d6423SLionel Sambuc }
769433d6423SLionel Sambuc }
770433d6423SLionel Sambuc }
771433d6423SLionel Sambuc }
772433d6423SLionel Sambuc
tab_print(FILE * fp)773433d6423SLionel Sambuc void tab_print(FILE *fp)
774433d6423SLionel Sambuc /* Print out a stored crontab file for debugging purposes. */
775433d6423SLionel Sambuc {
776433d6423SLionel Sambuc crontab_t *tab;
777433d6423SLionel Sambuc cronjob_t *job;
778433d6423SLionel Sambuc char *p;
779433d6423SLionel Sambuc struct tm tm;
780433d6423SLionel Sambuc
781433d6423SLionel Sambuc for (tab= crontabs; tab != nil; tab= tab->next) {
782433d6423SLionel Sambuc fprintf(fp, "tab->file = \"%s\"\n", tab->file);
783433d6423SLionel Sambuc fprintf(fp, "tab->user = \"%s\"\n",
784433d6423SLionel Sambuc tab->user == nil ? "(root)" : tab->user);
785433d6423SLionel Sambuc fprintf(fp, "tab->mtime = %s", ctime(&tab->mtime));
786433d6423SLionel Sambuc
787433d6423SLionel Sambuc for (job= tab->jobs; job != nil; job= job->next) {
788433d6423SLionel Sambuc if (job->atjob) {
789433d6423SLionel Sambuc fprintf(fp, "AT job");
790433d6423SLionel Sambuc } else {
791433d6423SLionel Sambuc pr_map(fp, job->min); fputc(' ', fp);
792433d6423SLionel Sambuc pr_map(fp, job->hour); fputc(' ', fp);
793433d6423SLionel Sambuc pr_map(fp, job->mday); fputc(' ', fp);
794433d6423SLionel Sambuc pr_map(fp, job->mon); fputc(' ', fp);
795433d6423SLionel Sambuc pr_map(fp, job->wday);
796433d6423SLionel Sambuc }
797433d6423SLionel Sambuc if (job->user != nil && job->user != tab->user) {
798433d6423SLionel Sambuc fprintf(fp, " -u %s", job->user);
799433d6423SLionel Sambuc }
800433d6423SLionel Sambuc fprintf(fp, "\n\t");
801433d6423SLionel Sambuc for (p= job->cmd; *p != 0; p++) {
802433d6423SLionel Sambuc fputc(*p, fp);
803433d6423SLionel Sambuc if (*p == '\n') fputc('\t', fp);
804433d6423SLionel Sambuc }
805433d6423SLionel Sambuc fputc('\n', fp);
806433d6423SLionel Sambuc tm= *localtime(&job->rtime);
807433d6423SLionel Sambuc fprintf(fp, " rtime = %.24s%s\n", asctime(&tm),
808433d6423SLionel Sambuc tm.tm_isdst ? " (DST)" : "");
809433d6423SLionel Sambuc if (job->pid != IDLE_PID) {
810433d6423SLionel Sambuc fprintf(fp, " pid = %ld\n", (long) job->pid);
811433d6423SLionel Sambuc }
812433d6423SLionel Sambuc }
813433d6423SLionel Sambuc }
814433d6423SLionel Sambuc }
815433d6423SLionel Sambuc
816433d6423SLionel Sambuc /*
817433d6423SLionel Sambuc * $PchId: tab.c,v 1.5 2000/07/25 22:07:51 philip Exp $
818433d6423SLionel Sambuc */
819