Commit | Line | Data |
---|---|---|
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) | |
20 | static 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 | ||
33 | typedef 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 | ||
38 | static 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 *)); | |
41 | static int set_element __P((bitstr_t *, int, int, int)); | |
42 | ||
43 | static 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 | ||
57 | void | |
58 | free_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 | */ | |
70 | entry * | |
71 | load_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 | ||
307 | static char | |
308 | get_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 | ||
354 | static char | |
355 | get_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 | ||
439 | static char | |
440 | get_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 | ||
494 | static int | |
495 | set_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 | } |