1 /* Copyright 1988,1990,1993 by Paul Vixie
4 * Distribute freely, except: don't remove my name from the source or
5 * documentation (don't take credit for my work), mark your changes (don't
6 * get me blamed for your possible bugs), don't alter or remove this
7 * notice. May be sold if buildable source is provided to buyer. No
8 * warrantee of any kind, express or implied, is included with this
9 * software; use at your own risk, responsibility for damages (if any) to
10 * anyone resulting from the use of this software rests entirely with the
13 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
14 * I'll try to keep a version up to date. I can be reached as follows:
15 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
18 #if !defined(lint) && !defined(LINT)
19 static char rcsid
[] = "$Id: entry.c,v 1.1 1994/01/05 20:40:14 jtc Exp $";
22 /* vix 26jan87 [RCS'd; rest of log is in RCS file]
23 * vix 01jan87 [added line-level error recovery]
24 * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
25 * vix 30dec86 [written]
35 e_none
, e_minute
, e_hour
, e_dom
, e_month
, e_dow
,
36 e_cmd
, e_timespec
, e_username
39 static char get_list
__P((bitstr_t
*, int, int, char *[], int, FILE *)),
40 get_range
__P((bitstr_t
*, int, int, char *[], int, FILE *)),
41 get_number
__P((int *, int, char *[], int, FILE *));
42 static int set_element
__P((bitstr_t
*, int, int, int));
44 static char *ecodes
[] =
68 load_entry(file
, error_func
, syscron
)
73 /* this function reads one crontab entry -- the next -- from a file.
74 * it skips any leading blank lines, ignores comments, and returns
75 * EOF if for any reason the entry can't be read and parsed.
77 * the entry is also parsed here.
81 * minutes hours doms months dows cmd\n
82 * system crontab (/etc/crontab):
83 * minutes hours doms months dows USERNAME cmd\n
86 ecode_e ecode
= e_none
;
89 char cmd
[MAX_COMMAND
];
91 e
= (entry
*) calloc(sizeof(entry
), sizeof(char));
93 Debug(DPARS
, ("load_entry()...about to eat comments\n"))
101 /* ch is now the first useful character of a useful line.
102 * it may be an @special or it may be the first character
103 * of a list of minutes.
107 /* all of these should be flagged and load-limited; i.e.,
108 * instead of @hourly meaning "0 * * * *" it should mean
109 * "close to the front of every hour but not 'til the
110 * system load is low". Problems are: how do you know
111 * what "low" means? (save me from /etc/cron.conf!) and:
112 * how to guarantee low variance (how low is low?), which
113 * means how to we run roughly every hour -- seems like
114 * we need to keep a history or let the first hour set
115 * the schedule, which means we aren't load-limited
116 * anymore. too much for my overloaded brain. (vix, jan90)
119 ch
= get_string(cmd
, MAX_COMMAND
, file
, " \t\n");
120 if (!strcmp("reboot", cmd
)) {
121 e
->flags
|= WHEN_REBOOT
;
122 } else if (!strcmp("yearly", cmd
) || !strcmp("annually", cmd
)){
123 bit_set(e
->minute
, 0);
126 bit_set(e
->month
, 0);
127 bit_nset(e
->dow
, 0, (LAST_DOW
-FIRST_DOW
+1));
128 } else if (!strcmp("monthly", cmd
)) {
129 bit_set(e
->minute
, 0);
132 bit_nset(e
->month
, 0, (LAST_MONTH
-FIRST_MONTH
+1));
133 bit_nset(e
->dow
, 0, (LAST_DOW
-FIRST_DOW
+1));
134 } else if (!strcmp("weekly", cmd
)) {
135 bit_set(e
->minute
, 0);
137 bit_nset(e
->dom
, 0, (LAST_DOM
-FIRST_DOM
+1));
138 bit_nset(e
->month
, 0, (LAST_MONTH
-FIRST_MONTH
+1));
140 } else if (!strcmp("daily", cmd
) || !strcmp("midnight", cmd
)) {
141 bit_set(e
->minute
, 0);
143 bit_nset(e
->dom
, 0, (LAST_DOM
-FIRST_DOM
+1));
144 bit_nset(e
->month
, 0, (LAST_MONTH
-FIRST_MONTH
+1));
145 bit_nset(e
->dow
, 0, (LAST_DOW
-FIRST_DOW
+1));
146 } else if (!strcmp("hourly", cmd
)) {
147 bit_set(e
->minute
, 0);
148 bit_set(e
->hour
, (LAST_HOUR
-FIRST_HOUR
+1));
149 bit_nset(e
->dom
, 0, (LAST_DOM
-FIRST_DOM
+1));
150 bit_nset(e
->month
, 0, (LAST_MONTH
-FIRST_MONTH
+1));
151 bit_nset(e
->dow
, 0, (LAST_DOW
-FIRST_DOW
+1));
157 Debug(DPARS
, ("load_entry()...about to parse numerics\n"))
159 ch
= get_list(e
->minute
, FIRST_MINUTE
, LAST_MINUTE
,
169 ch
= get_list(e
->hour
, FIRST_HOUR
, LAST_HOUR
,
176 /* DOM (days of month)
180 e
->flags
|= DOM_STAR
;
181 ch
= get_list(e
->dom
, FIRST_DOM
, LAST_DOM
,
191 ch
= get_list(e
->month
, FIRST_MONTH
, LAST_MONTH
,
192 MonthNames
, ch
, file
);
198 /* DOW (days of week)
202 e
->flags
|= DOW_STAR
;
203 ch
= get_list(e
->dow
, FIRST_DOW
, LAST_DOW
,
211 /* make sundays equivilent */
212 if (bit_test(e
->dow
, 0) || bit_test(e
->dow
, 7)) {
217 /* ch is the first character of a command, or a username */
218 unget_char(ch
, file
);
220 e
->exec_uid
= 0; /* default to user */
223 char *username
= cmd
; /* temp buffer */
225 Debug(DPARS
, ("load_entry()...about to parse username\n"))
226 ch
= get_string(username
, MAX_COMMAND
, file
, " \t");
228 Debug(DPARS
, ("load_entry()...got %s\n",username
))
234 pw
= getpwnam(username
);
239 Debug(DPARS
, ("load_entry()...id %d\n",pw
->pw_uid
))
240 e
->exec_uid
= pw
->pw_uid
;
243 Debug(DPARS
, ("load_entry()...about to parse command\n"))
245 /* Everything up to the next \n or EOF is part of the command...
246 * too bad we don't know in advance how long it will be, since we
247 * need to malloc a string for it... so, we limit it to MAX_COMMAND
249 ch
= get_string(cmd
, MAX_COMMAND
, file
, "\n");
251 /* a file without a \n before the EOF is rude, so we'll complain...
258 /* got the command in the 'cmd' string; save it in *e.
260 e
->cmd
= strdup(cmd
);
262 Debug(DPARS
, ("load_entry()...returning successfully\n"))
264 /* success, fini, return pointer to the entry we just created...
268 eof
: /* if we want to return EOF, we have to jump down here and
269 * free the entry we've been building.
271 * now, in some cases, a parse routine will have returned EOF to
272 * indicate an error, but the file is not actually done. since, in
273 * that case, we only want to skip the line with the error on it,
274 * we'll do that here.
276 * many, including the author, see what's below as evil programming
277 * practice: since I didn't want to change the structure of this
278 * whole function to support this error recovery, I recurse. Cursed!
279 * (At least it's tail-recursion, as if it matters in C - vix/8feb88)
280 * I'm seriously considering using (another) GOTO... argh!
281 * (this does not get less disgusting over time. vix/15nov88)
282 * (indeed not. vix/20dec93)
291 (*error_func
)(ecodes
[(int)ecode
]);
292 do {ch
= get_char(file
);}
293 while (ch
!= EOF
&& ch
!= '\n');
296 return load_entry(file
, error_func
, 0);
301 get_list(bits
, low
, high
, names
, ch
, file
)
302 bitstr_t
*bits
; /* one bit per flag, default=FALSE */
303 int low
, high
; /* bounds, impl. offset for bitstr */
304 char *names
[]; /* NULL or *[] of names for these elements */
305 int ch
; /* current character being processed */
306 FILE *file
; /* file being read */
310 /* we know that we point to a non-blank character here;
311 * must do a Skip_Blanks before we exit, so that the
312 * next call (or the code that picks up the cmd) can
313 * assume the same thing.
316 Debug(DPARS
|DEXT
, ("get_list()...entered\n"))
318 /* list = range {"," range}
321 /* clear the bit string, since the default is 'off'.
323 bit_nclear(bits
, 0, (high
-low
+1));
325 /* process all ranges
329 ch
= get_range(bits
, low
, high
, names
, ch
, file
);
336 /* exiting. skip to some blanks, then skip over the blanks.
338 Skip_Nonblanks(ch
, file
)
339 Skip_Blanks(ch
, file
)
341 Debug(DPARS
|DEXT
, ("get_list()...exiting w/ %02x\n", ch
))
348 get_range(bits
, low
, high
, names
, ch
, file
)
349 bitstr_t
*bits
; /* one bit per flag, default=FALSE */
350 int low
, high
; /* bounds, impl. offset for bitstr */
351 char *names
[]; /* NULL or names of elements */
352 int ch
; /* current character being processed */
353 FILE *file
; /* file being read */
355 /* range = number | number "-" number [ "/" number ]
359 auto int num1
, num2
, num3
;
361 Debug(DPARS
|DEXT
, ("get_range()...entering, exit won't show\n"))
364 /* '*' means "first-last" but can still be modified by /step
372 if (EOF
== (ch
= get_number(&num1
, low
, names
, ch
, file
)))
376 /* not a range, it's a single number.
378 if (EOF
== set_element(bits
, low
, high
, num1
))
388 /* get the number following the dash
390 ch
= get_number(&num2
, low
, names
, ch
, file
);
396 /* check for step size
405 /* get the step size -- note: we don't pass the
406 * names here, because the number is not an
407 * element id, it's a step size. 'low' is
408 * sent as a 0 since there is no offset either.
410 ch
= get_number(&num3
, 0, PPC_NULL
, ch
, file
);
414 /* no step. default==1.
419 /* range. set all elements from num1 to num2, stepping
420 * by num3. (the step is a downward-compatible extension
421 * proposed conceptually by bob@acornrc, syntactically
422 * designed then implmented by paul vixie).
424 for (i
= num1
; i
<= num2
; i
+= num3
)
425 if (EOF
== set_element(bits
, low
, high
, i
))
433 get_number(numptr
, low
, names
, ch
, file
)
434 int *numptr
; /* where does the result go? */
435 int low
; /* offset applied to result if symbolic enum used */
436 char *names
[]; /* symbolic names, if any, for enums */
437 int ch
; /* current character */
438 FILE *file
; /* source */
440 char temp
[MAX_TEMPSTR
], *pc
;
441 int len
, i
, all_digits
;
443 /* collect alphanumerics into our fixed-size temp array
448 while (isalnum(ch
)) {
449 if (++len
>= MAX_TEMPSTR
)
461 /* try to find the name in the name list
464 for (i
= 0; names
[i
] != NULL
; i
++) {
466 ("get_num, compare(%s,%s)\n", names
[i
], temp
))
467 if (!strcasecmp(names
[i
], temp
)) {
474 /* no name list specified, or there is one and our string isn't
475 * in it. either way: if it's all digits, use its magnitude.
476 * otherwise, it's an error.
479 *numptr
= atoi(temp
);
488 set_element(bits
, low
, high
, number
)
489 bitstr_t
*bits
; /* one bit per flag, default=FALSE */
494 Debug(DPARS
|DEXT
, ("set_element(?,%d,%d,%d)\n", low
, high
, number
))
496 if (number
< low
|| number
> high
)
499 bit_set(bits
, (number
-low
));