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