No empty .Rs/.Re
[netbsd-mini2440.git] / libexec / cron / entry.c
blob3ac2d884b7efd97336ebe3629120d54a07fbbdb6
1 /* Copyright 1988,1990,1993 by Paul Vixie
2 * All rights reserved
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
11 * user.
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 $";
20 #endif
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]
29 #include "cron.h"
30 #include "externs.h"
31 #include <pwd.h>
34 typedef enum ecode {
35 e_none, e_minute, e_hour, e_dom, e_month, e_dow,
36 e_cmd, e_timespec, e_username
37 } ecode_e;
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[] =
46 "no error",
47 "bad minute",
48 "bad hour",
49 "bad day-of-month",
50 "bad month",
51 "bad day-of-week",
52 "bad command",
53 "bad time specifier",
54 "bad username",
58 void
59 free_entry(e)
60 entry *e;
62 free(e->cmd);
63 free(e);
67 entry *
68 load_entry(file, error_func, syscron)
69 FILE *file;
70 void (*error_func)();
71 int 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.
79 * syntax:
80 * user crontab:
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;
87 entry *e;
88 int ch;
89 char cmd[MAX_COMMAND];
91 e = (entry *) calloc(sizeof(entry), sizeof(char));
93 Debug(DPARS, ("load_entry()...about to eat comments\n"))
95 skip_comments(file);
97 ch = get_char(file);
98 if (ch == EOF)
99 return NULL;
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.
106 if (ch == '@') {
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)
117 * HINT
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);
124 bit_set(e->hour, 0);
125 bit_set(e->dom, 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);
130 bit_set(e->hour, 0);
131 bit_set(e->dom, 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);
136 bit_set(e->hour, 0);
137 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
138 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
139 bit_set(e->dow, 0);
140 } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
141 bit_set(e->minute, 0);
142 bit_set(e->hour, 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));
152 } else {
153 ecode = e_timespec;
154 goto eof;
156 } else {
157 Debug(DPARS, ("load_entry()...about to parse numerics\n"))
159 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
160 PPC_NULL, ch, file);
161 if (ch == EOF) {
162 ecode = e_minute;
163 goto eof;
166 /* hours
169 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
170 PPC_NULL, ch, file);
171 if (ch == EOF) {
172 ecode = e_hour;
173 goto eof;
176 /* DOM (days of month)
179 if (ch == '*')
180 e->flags |= DOM_STAR;
181 ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
182 PPC_NULL, ch, file);
183 if (ch == EOF) {
184 ecode = e_dom;
185 goto eof;
188 /* month
191 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
192 MonthNames, ch, file);
193 if (ch == EOF) {
194 ecode = e_month;
195 goto eof;
198 /* DOW (days of week)
201 if (ch == '*')
202 e->flags |= DOW_STAR;
203 ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
204 DowNames, ch, file);
205 if (ch == EOF) {
206 ecode = e_dow;
207 goto eof;
211 /* make sundays equivilent */
212 if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
213 bit_set(e->dow, 0);
214 bit_set(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 */
221 if (syscron) {
222 struct passwd *pw;
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))
229 if (ch == EOF) {
230 ecode = e_cmd;
231 goto eof;
234 pw = getpwnam(username);
235 if (pw == NULL) {
236 ecode = e_username;
237 goto eof;
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...
253 if (ch == EOF) {
254 ecode = e_cmd;
255 goto eof;
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...
266 return e;
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)
285 (void) free(e);
287 if (feof(file))
288 return NULL;
290 if (error_func)
291 (*error_func)(ecodes[(int)ecode]);
292 do {ch = get_char(file);}
293 while (ch != EOF && ch != '\n');
294 if (ch == EOF)
295 return NULL;
296 return load_entry(file, error_func, 0);
300 static char
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 */
308 register int done;
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
327 done = FALSE;
328 while (!done) {
329 ch = get_range(bits, low, high, names, ch, file);
330 if (ch == ',')
331 ch = get_char(file);
332 else
333 done = TRUE;
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))
343 return ch;
347 static char
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 ]
358 register int i;
359 auto int num1, num2, num3;
361 Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
363 if (ch == '*') {
364 /* '*' means "first-last" but can still be modified by /step
366 num1 = low;
367 num2 = high;
368 ch = get_char(file);
369 if (ch == EOF)
370 return EOF;
371 } else {
372 if (EOF == (ch = get_number(&num1, low, names, ch, file)))
373 return EOF;
375 if (ch != '-') {
376 /* not a range, it's a single number.
378 if (EOF == set_element(bits, low, high, num1))
379 return EOF;
380 return ch;
381 } else {
382 /* eat the dash
384 ch = get_char(file);
385 if (ch == EOF)
386 return EOF;
388 /* get the number following the dash
390 ch = get_number(&num2, low, names, ch, file);
391 if (ch == EOF)
392 return EOF;
396 /* check for step size
398 if (ch == '/') {
399 /* eat the slash
401 ch = get_char(file);
402 if (ch == EOF)
403 return EOF;
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);
411 if (ch == EOF)
412 return EOF;
413 } else {
414 /* no step. default==1.
416 num3 = 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))
426 return EOF;
428 return ch;
432 static char
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
445 pc = temp;
446 len = 0;
447 all_digits = TRUE;
448 while (isalnum(ch)) {
449 if (++len >= MAX_TEMPSTR)
450 return EOF;
452 *pc++ = ch;
454 if (!isdigit(ch))
455 all_digits = FALSE;
457 ch = get_char(file);
459 *pc = '\0';
461 /* try to find the name in the name list
463 if (names) {
464 for (i = 0; names[i] != NULL; i++) {
465 Debug(DPARS|DEXT,
466 ("get_num, compare(%s,%s)\n", names[i], temp))
467 if (!strcasecmp(names[i], temp)) {
468 *numptr = i+low;
469 return ch;
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.
478 if (all_digits) {
479 *numptr = atoi(temp);
480 return ch;
483 return EOF;
487 static int
488 set_element(bits, low, high, number)
489 bitstr_t *bits; /* one bit per flag, default=FALSE */
490 int low;
491 int high;
492 int number;
494 Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
496 if (number < low || number > high)
497 return EOF;
499 bit_set(bits, (number-low));
500 return OK;