OSDN Git Service

Add MS7619SE
[uclinux-h8/uClinux-dist.git] / user / vixie-cron / entry.c
1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
2  * All rights reserved
3  *
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.
12  *
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
16  */
17
18 #if !defined(lint) && !defined(LINT)
19 static char rcsid[] = "$Id: entry.c,v 2.12 1994/01/17 03:20:37 vixie Exp $";
20 #endif
21
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]
26  */
27
28
29 #include "cron.h"
30
31
32 typedef enum ecode {
33         e_none, e_minute, e_hour, e_dom, e_month, e_dow,
34         e_cmd, e_timespec, e_username
35 } ecode_e;
36
37 static char     get_list __P((bitstr_t *, int, int, char *[], int, FILE *)),
38                 get_range __P((bitstr_t *, int, int, char *[], int, FILE *)),
39                 get_number __P((int *, int, char *[], int, FILE *));
40 static int      set_element __P((bitstr_t *, int, int, int));
41
42 static char *ecodes[] =
43         {
44                 "no error",
45                 "bad minute",
46                 "bad hour",
47                 "bad day-of-month",
48                 "bad month",
49                 "bad day-of-week",
50                 "bad command",
51                 "bad time specifier",
52                 "bad username",
53         };
54
55
56 void
57 free_entry(e)
58         entry   *e;
59 {
60         free(e->cmd);
61         env_free(e->envp);
62         free(e);
63 }
64
65
66 /* return NULL if eof or syntax error occurs;
67  * otherwise return a pointer to a new entry.
68  */
69 entry *
70 load_entry(file, error_func, pw, envp)
71         FILE            *file;
72         void            (*error_func)();
73         struct passwd   *pw;
74         char            **envp;
75 {
76         /* this function reads one crontab entry -- the next -- from a file.
77          * it skips any leading blank lines, ignores comments, and returns
78          * EOF if for any reason the entry can't be read and parsed.
79          *
80          * the entry is also parsed here.
81          *
82          * syntax:
83          *   user crontab:
84          *      minutes hours doms months dows cmd\n
85          *   system crontab (/etc/crontab):
86          *      minutes hours doms months dows USERNAME cmd\n
87          */
88
89         ecode_e ecode = e_none;
90         entry   *e;
91         int     ch;
92         char    cmd[MAX_COMMAND];
93         char    envstr[MAX_ENVSTR];
94
95         Debug(DPARS, ("load_entry()...about to eat comments\n"))
96
97         skip_comments(file);
98
99         ch = get_char(file);
100         if (ch == EOF)
101                 return NULL;
102
103         /* ch is now the first useful character of a useful line.
104          * it may be an @special or it may be the first character
105          * of a list of minutes.
106          */
107
108         e = (entry *) calloc(sizeof(entry), sizeof(char));
109
110         if (ch == '@') {
111                 /* all of these should be flagged and load-limited; i.e.,
112                  * instead of @hourly meaning "0 * * * *" it should mean
113                  * "close to the front of every hour but not 'til the
114                  * system load is low".  Problems are: how do you know
115                  * what "low" means? (save me from /etc/cron.conf!) and:
116                  * how to guarantee low variance (how low is low?), which
117                  * means how to we run roughly every hour -- seems like
118                  * we need to keep a history or let the first hour set
119                  * the schedule, which means we aren't load-limited
120                  * anymore.  too much for my overloaded brain. (vix, jan90)
121                  * HINT
122                  */
123                 ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
124                 if (!strcmp("reboot", cmd)) {
125                         e->flags |= WHEN_REBOOT;
126                 } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
127                         bit_set(e->minute, 0);
128                         bit_set(e->hour, 0);
129                         bit_set(e->dom, 0);
130                         bit_set(e->month, 0);
131                         bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
132                 } else if (!strcmp("monthly", cmd)) {
133                         bit_set(e->minute, 0);
134                         bit_set(e->hour, 0);
135                         bit_set(e->dom, 0);
136                         bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
137                         bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
138                 } else if (!strcmp("weekly", cmd)) {
139                         bit_set(e->minute, 0);
140                         bit_set(e->hour, 0);
141                         bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
142                         bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
143                         bit_set(e->dow, 0);
144                 } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
145                         bit_set(e->minute, 0);
146                         bit_set(e->hour, 0);
147                         bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
148                         bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
149                         bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
150                 } else if (!strcmp("hourly", cmd)) {
151                         bit_set(e->minute, 0);
152                         bit_set(e->hour, (LAST_HOUR-FIRST_HOUR+1));
153                         bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
154                         bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
155                         bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
156                 } else {
157                         ecode = e_timespec;
158                         goto eof;
159                 }
160         } else {
161                 Debug(DPARS, ("load_entry()...about to parse numerics\n"))
162
163                 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
164                               PPC_NULL, ch, file);
165                 if (ch == EOF) {
166                         ecode = e_minute;
167                         goto eof;
168                 }
169
170                 /* hours
171                  */
172
173                 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
174                               PPC_NULL, ch, file);
175                 if (ch == EOF) {
176                         ecode = e_hour;
177                         goto eof;
178                 }
179
180                 /* DOM (days of month)
181                  */
182
183                 if (ch == '*')
184                         e->flags |= DOM_STAR;
185                 ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
186                               PPC_NULL, ch, file);
187                 if (ch == EOF) {
188                         ecode = e_dom;
189                         goto eof;
190                 }
191
192                 /* month
193                  */
194
195                 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
196                               MonthNames, ch, file);
197                 if (ch == EOF) {
198                         ecode = e_month;
199                         goto eof;
200                 }
201
202                 /* DOW (days of week)
203                  */
204
205                 if (ch == '*')
206                         e->flags |= DOW_STAR;
207                 ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
208                               DowNames, ch, file);
209                 if (ch == EOF) {
210                         ecode = e_dow;
211                         goto eof;
212                 }
213         }
214
215         /* make sundays equivilent */
216         if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
217                 bit_set(e->dow, 0);
218                 bit_set(e->dow, 7);
219         }
220
221         /* ch is the first character of a command, or a username */
222         unget_char(ch, file);
223
224         if (!pw) {
225                 char            *username = cmd;        /* temp buffer */
226
227                 Debug(DPARS, ("load_entry()...about to parse username\n"))
228                 ch = get_string(username, MAX_COMMAND, file, " \t");
229
230                 Debug(DPARS, ("load_entry()...got %s\n",username))
231                 if (ch == EOF) {
232                         ecode = e_cmd;
233                         goto eof;
234                 }
235
236                 pw = getpwnam(username);
237                 if (pw == NULL) {
238                         ecode = e_username;
239                         goto eof;
240                 }
241                 Debug(DPARS, ("load_entry()...uid %d, gid %d\n",e->uid,e->gid))
242         }
243
244         e->uid = pw->pw_uid;
245         e->gid = pw->pw_gid;
246
247         /* copy and fix up environment.  some variables are just defaults and
248          * others are overrides.
249          */
250         e->envp = env_copy(envp);
251         if (!env_get("SHELL", e->envp)) {
252                 sprintf(envstr, "SHELL=%s", _PATH_BSHELL);
253                 e->envp = env_set(e->envp, envstr);
254         }
255         if (!env_get("HOME", e->envp)) {
256                 sprintf(envstr, "HOME=%s", pw->pw_dir);
257                 e->envp = env_set(e->envp, envstr);
258         }
259         if (!env_get("PATH", e->envp)) {
260                 sprintf(envstr, "PATH=%s", _PATH_DEFPATH);
261                 e->envp = env_set(e->envp, envstr);
262         }
263         sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
264         e->envp = env_set(e->envp, envstr);
265 #if defined(BSD)
266         sprintf(envstr, "%s=%s", "USER", pw->pw_name);
267         e->envp = env_set(e->envp, envstr);
268 #endif
269
270         Debug(DPARS, ("load_entry()...about to parse command\n"))
271
272         /* Everything up to the next \n or EOF is part of the command...
273          * too bad we don't know in advance how long it will be, since we
274          * need to malloc a string for it... so, we limit it to MAX_COMMAND.
275          * XXX - should use realloc().
276          */ 
277         ch = get_string(cmd, MAX_COMMAND, file, "\n");
278
279         /* a file without a \n before the EOF is rude, so we'll complain...
280          */
281         if (ch == EOF) {
282                 ecode = e_cmd;
283                 goto eof;
284         }
285
286         /* got the command in the 'cmd' string; save it in *e.
287          */
288         e->cmd = strdup(cmd);
289
290         Debug(DPARS, ("load_entry()...returning successfully\n"))
291
292         /* success, fini, return pointer to the entry we just created...
293          */
294         return e;
295
296  eof:
297         free(e);
298         if (ecode != e_none && error_func)
299                 (*error_func)(ecodes[(int)ecode]);
300         while (ch != EOF && ch != '\n')
301                 ch = get_char(file);
302         return NULL;
303 }
304
305
306 static char
307 get_list(bits, low, high, names, ch, file)
308         bitstr_t        *bits;          /* one bit per flag, default=FALSE */
309         int             low, high;      /* bounds, impl. offset for bitstr */
310         char            *names[];       /* NULL or *[] of names for these elements */
311         int             ch;             /* current character being processed */
312         FILE            *file;          /* file being read */
313 {
314         register int    done;
315
316         /* we know that we point to a non-blank character here;
317          * must do a Skip_Blanks before we exit, so that the
318          * next call (or the code that picks up the cmd) can
319          * assume the same thing.
320          */
321
322         Debug(DPARS|DEXT, ("get_list()...entered\n"))
323
324         /* list = range {"," range}
325          */
326         
327         /* clear the bit string, since the default is 'off'.
328          */
329         bit_nclear(bits, 0, (high-low+1));
330
331         /* process all ranges
332          */
333         done = FALSE;
334         while (!done) {
335                 ch = get_range(bits, low, high, names, ch, file);
336                 if (ch == ',')
337                         ch = get_char(file);
338                 else
339                         done = TRUE;
340         }
341
342         /* exiting.  skip to some blanks, then skip over the blanks.
343          */
344         Skip_Nonblanks(ch, file)
345         Skip_Blanks(ch, file)
346
347         Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
348
349         return ch;
350 }
351
352
353 static char
354 get_range(bits, low, high, names, ch, file)
355         bitstr_t        *bits;          /* one bit per flag, default=FALSE */
356         int             low, high;      /* bounds, impl. offset for bitstr */
357         char            *names[];       /* NULL or names of elements */
358         int             ch;             /* current character being processed */
359         FILE            *file;          /* file being read */
360 {
361         /* range = number | number "-" number [ "/" number ]
362          */
363
364         register int    i;
365         auto int        num1, num2, num3;
366
367         Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
368
369         if (ch == '*') {
370                 /* '*' means "first-last" but can still be modified by /step
371                  */
372                 num1 = low;
373                 num2 = high;
374                 ch = get_char(file);
375                 if (ch == EOF)
376                         return EOF;
377         } else {
378                 if (EOF == (ch = get_number(&num1, low, names, ch, file)))
379                         return EOF;
380
381                 if (ch != '-') {
382                         /* not a range, it's a single number.
383                          */
384                         if (EOF == set_element(bits, low, high, num1))
385                                 return EOF;
386                         return ch;
387                 } else {
388                         /* eat the dash
389                          */
390                         ch = get_char(file);
391                         if (ch == EOF)
392                                 return EOF;
393
394                         /* get the number following the dash
395                          */
396                         ch = get_number(&num2, low, names, ch, file);
397                         if (ch == EOF)
398                                 return EOF;
399                 }
400         }
401
402         /* check for step size
403          */
404         if (ch == '/') {
405                 /* eat the slash
406                  */
407                 ch = get_char(file);
408                 if (ch == EOF)
409                         return EOF;
410
411                 /* get the step size -- note: we don't pass the
412                  * names here, because the number is not an
413                  * element id, it's a step size.  'low' is
414                  * sent as a 0 since there is no offset either.
415                  */
416                 ch = get_number(&num3, 0, PPC_NULL, ch, file);
417                 if (ch == EOF)
418                         return EOF;
419         } else {
420                 /* no step.  default==1.
421                  */
422                 num3 = 1;
423         }
424
425         /* range. set all elements from num1 to num2, stepping
426          * by num3.  (the step is a downward-compatible extension
427          * proposed conceptually by bob@acornrc, syntactically
428          * designed then implmented by paul vixie).
429          */
430         for (i = num1;  i <= num2;  i += num3)
431                 if (EOF == set_element(bits, low, high, i))
432                         return EOF;
433
434         return ch;
435 }
436
437
438 static char
439 get_number(numptr, low, names, ch, file)
440         int     *numptr;        /* where does the result go? */
441         int     low;            /* offset applied to result if symbolic enum used */
442         char    *names[];       /* symbolic names, if any, for enums */
443         int     ch;             /* current character */
444         FILE    *file;          /* source */
445 {
446         char    temp[MAX_TEMPSTR], *pc;
447         int     len, i, all_digits;
448
449         /* collect alphanumerics into our fixed-size temp array
450          */
451         pc = temp;
452         len = 0;
453         all_digits = TRUE;
454         while (isalnum(ch)) {
455                 if (++len >= MAX_TEMPSTR)
456                         return EOF;
457
458                 *pc++ = ch;
459
460                 if (!isdigit(ch))
461                         all_digits = FALSE;
462
463                 ch = get_char(file);
464         }
465         *pc = '\0';
466
467         /* try to find the name in the name list
468          */
469         if (names) {
470                 for (i = 0;  names[i] != NULL;  i++) {
471                         Debug(DPARS|DEXT,
472                                 ("get_num, compare(%s,%s)\n", names[i], temp))
473                         if (!strcasecmp(names[i], temp)) {
474                                 *numptr = i+low;
475                                 return ch;
476                         }
477                 }
478         }
479
480         /* no name list specified, or there is one and our string isn't
481          * in it.  either way: if it's all digits, use its magnitude.
482          * otherwise, it's an error.
483          */
484         if (all_digits) {
485                 *numptr = atoi(temp);
486                 return ch;
487         }
488
489         return EOF;
490 }
491
492
493 static int
494 set_element(bits, low, high, number)
495         bitstr_t        *bits;          /* one bit per flag, default=FALSE */
496         int             low;
497         int             high;
498         int             number;
499 {
500         Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
501
502         if (number < low || number > high)
503                 return EOF;
504
505         bit_set(bits, (number-low));
506         return OK;
507 }