New sentence, new line.
[netbsd-mini2440.git] / usr.sbin / cron / entry.c
blob61d51c5ab221b05f09e811b9dd70d2c58d38c60d
1 /* $NetBSD: entry.c,v 1.9 2008/02/16 07:26:00 matt Exp $ */
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
4 * All rights reserved
6 * Distribute freely, except: don't remove my name from the source or
7 * documentation (don't take credit for my work), mark your changes (don't
8 * get me blamed for your possible bugs), don't alter or remove this
9 * notice. May be sold if buildable source is provided to buyer. No
10 * warrantee of any kind, express or implied, is included with this
11 * software; use at your own risk, responsibility for damages (if any) to
12 * anyone resulting from the use of this software rests entirely with the
13 * user.
15 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
16 * I'll try to keep a version up to date. I can be reached as follows:
17 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
20 #include <sys/cdefs.h>
21 #if !defined(lint) && !defined(LINT)
22 #if 0
23 static char rcsid[] = "Id: entry.c,v 2.12 1994/01/17 03:20:37 vixie Exp";
24 #else
25 __RCSID("$NetBSD: entry.c,v 1.9 2008/02/16 07:26:00 matt Exp $");
26 #endif
27 #endif
29 /* vix 26jan87 [RCS'd; rest of log is in RCS file]
30 * vix 01jan87 [added line-level error recovery]
31 * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
32 * vix 30dec86 [written]
36 #include "cron.h"
39 typedef enum ecode {
40 e_none, e_minute, e_hour, e_dom, e_month, e_dow,
41 e_cmd, e_timespec, e_username
42 } ecode_e;
44 static char get_list(bitstr_t *, int, int, const char * const [], int, FILE *),
45 get_range(bitstr_t *, int, int, const char * const [], int, FILE *),
46 get_number(int *, int, const char * const [], int, FILE *);
47 static int set_element(bitstr_t *, int, int, int);
49 static const char * const ecodes[] =
51 "no error",
52 "bad minute",
53 "bad hour",
54 "bad day-of-month",
55 "bad month",
56 "bad day-of-week",
57 "bad command",
58 "bad time specifier",
59 "bad username",
63 void
64 free_entry(entry *e)
66 free(e->cmd);
67 env_free(e->envp);
68 free(e);
72 /* return NULL if eof or syntax error occurs;
73 * otherwise return a pointer to a new entry.
75 entry *
76 load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw,
77 char **envp)
79 /* this function reads one crontab entry -- the next -- from a file.
80 * it skips any leading blank lines, ignores comments, and returns
81 * EOF if for any reason the entry can't be read and parsed.
83 * the entry is also parsed here.
85 * syntax:
86 * user crontab:
87 * minutes hours doms months dows cmd\n
88 * system crontab (/etc/crontab):
89 * minutes hours doms months dows USERNAME cmd\n
92 ecode_e ecode = e_none;
93 entry *e;
94 int ch;
95 char cmd[MAX_COMMAND];
96 char envstr[MAX_ENVSTR];
98 Debug(DPARS, ("load_entry()...about to eat comments\n"))
100 skip_comments(file);
102 ch = get_char(file);
103 if (ch == EOF)
104 return NULL;
106 /* ch is now the first useful character of a useful line.
107 * it may be an @special or it may be the first character
108 * of a list of minutes.
111 e = (entry *) calloc(sizeof(entry), sizeof(char));
113 if (ch == '@') {
114 /* all of these should be flagged and load-limited; i.e.,
115 * instead of @hourly meaning "0 * * * *" it should mean
116 * "close to the front of every hour but not 'til the
117 * system load is low". Problems are: how do you know
118 * what "low" means? (save me from /etc/cron.conf!) and:
119 * how to guarantee low variance (how low is low?), which
120 * means how to we run roughly every hour -- seems like
121 * we need to keep a history or let the first hour set
122 * the schedule, which means we aren't load-limited
123 * anymore. too much for my overloaded brain. (vix, jan90)
124 * HINT
126 ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
127 if (!strcmp("reboot", cmd)) {
128 e->flags |= WHEN_REBOOT;
129 } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
130 bit_set(e->minute, 0);
131 bit_set(e->hour, 0);
132 bit_set(e->dom, 0);
133 bit_set(e->month, 0);
134 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
135 e->flags |= DOW_STAR;
136 } else if (!strcmp("monthly", cmd)) {
137 bit_set(e->minute, 0);
138 bit_set(e->hour, 0);
139 bit_set(e->dom, 0);
140 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
141 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
142 e->flags |= DOW_STAR;
143 } else if (!strcmp("weekly", cmd)) {
144 bit_set(e->minute, 0);
145 bit_set(e->hour, 0);
146 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
147 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
148 bit_set(e->dow, 0);
149 e->flags |= DOM_STAR;
150 } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
151 bit_set(e->minute, 0);
152 bit_set(e->hour, 0);
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 e->flags |= DOM_STAR | DOW_STAR;
157 } else if (!strcmp("hourly", cmd)) {
158 bit_set(e->minute, 0);
159 bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
160 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
161 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
162 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
163 e->flags |= DOM_STAR | DOW_STAR;
164 } else {
165 ecode = e_timespec;
166 goto eof;
168 } else {
169 Debug(DPARS, ("load_entry()...about to parse numerics\n"))
171 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
172 PPC_NULL, ch, file);
173 if (ch == EOF) {
174 ecode = e_minute;
175 goto eof;
178 /* hours
181 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
182 PPC_NULL, ch, file);
183 if (ch == EOF) {
184 ecode = e_hour;
185 goto eof;
188 /* DOM (days of month)
191 if (ch == '*')
192 e->flags |= DOM_STAR;
193 ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
194 PPC_NULL, ch, file);
195 if (ch == EOF) {
196 ecode = e_dom;
197 goto eof;
200 /* month
203 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
204 MonthNames, ch, file);
205 if (ch == EOF) {
206 ecode = e_month;
207 goto eof;
210 /* DOW (days of week)
213 if (ch == '*')
214 e->flags |= DOW_STAR;
215 ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
216 DowNames, ch, file);
217 if (ch == EOF) {
218 ecode = e_dow;
219 goto eof;
223 /* make sundays equivilent */
224 if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
225 bit_set(e->dow, 0);
226 bit_set(e->dow, 7);
229 /* ch is the first character of a command, or a username */
230 unget_char(ch, file);
232 if (!pw) {
233 char *username = cmd; /* temp buffer */
235 Debug(DPARS, ("load_entry()...about to parse username\n"))
236 ch = get_string(username, MAX_COMMAND, file, " \t");
238 Debug(DPARS, ("load_entry()...got %s\n",username))
239 if (ch == EOF) {
240 ecode = e_cmd;
241 goto eof;
244 pw = getpwnam(username);
245 if (pw == NULL) {
246 ecode = e_username;
247 goto eof;
249 Debug(DPARS, ("load_entry()...uid %d, gid %d\n",e->uid,e->gid))
252 e->uid = pw->pw_uid;
253 e->gid = pw->pw_gid;
255 /* copy and fix up environment. some variables are just defaults and
256 * others are overrides.
258 e->envp = env_copy(envp);
259 if (!env_get("SHELL", e->envp)) {
260 snprintf(envstr, sizeof(envstr), "SHELL=%s", _PATH_BSHELL);
261 e->envp = env_set(e->envp, envstr);
263 if (!env_get("HOME", e->envp)) {
264 snprintf(envstr, sizeof(envstr), "HOME=%s", pw->pw_dir);
265 e->envp = env_set(e->envp, envstr);
267 if (!env_get("PATH", e->envp)) {
268 snprintf(envstr, sizeof(envstr), "PATH=%s", _PATH_DEFPATH);
269 e->envp = env_set(e->envp, envstr);
271 snprintf(envstr, sizeof(envstr), "%s=%s", "LOGNAME", pw->pw_name);
272 e->envp = env_set(e->envp, envstr);
273 #if defined(BSD)
274 snprintf(envstr, sizeof(envstr), "%s=%s", "USER", pw->pw_name);
275 e->envp = env_set(e->envp, envstr);
276 #endif
278 Debug(DPARS, ("load_entry()...about to parse command\n"))
280 /* Everything up to the next \n or EOF is part of the command...
281 * too bad we don't know in advance how long it will be, since we
282 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
283 * XXX - should use realloc().
285 ch = get_string(cmd, MAX_COMMAND, file, "\n");
287 /* a file without a \n before the EOF is rude, so we'll complain...
289 if (ch == EOF) {
290 ecode = e_cmd;
291 goto eof;
294 /* got the command in the 'cmd' string; save it in *e.
296 e->cmd = strdup(cmd);
298 Debug(DPARS, ("load_entry()...returning successfully\n"))
300 /* success, fini, return pointer to the entry we just created...
302 return e;
304 eof:
305 free(e);
306 if (ecode != e_none && error_func)
307 (*error_func)(ecodes[(int)ecode]);
308 while (ch != EOF && ch != '\n')
309 ch = get_char(file);
310 return NULL;
314 static char
315 get_list(bitstr_t *bits, /* one bit per flag, default=FALSE */
316 int low, /* bounds, impl. offset for bitstr */
317 int high, /* bounds, impl. offset for bitstr */
318 const char * const names[], /* NULL or *[] of names for these elements */
319 int ch, /* current character being processed */
320 FILE *file /* file being read */)
322 int done;
324 /* we know that we point to a non-blank character here;
325 * must do a Skip_Blanks before we exit, so that the
326 * next call (or the code that picks up the cmd) can
327 * assume the same thing.
330 Debug(DPARS|DEXT, ("get_list()...entered\n"))
332 /* list = range {"," range}
335 /* clear the bit string, since the default is 'off'.
337 bit_nclear(bits, 0, (high-low+1));
339 /* process all ranges
341 done = FALSE;
342 while (!done) {
343 ch = get_range(bits, low, high, names, ch, file);
344 if (ch == ',')
345 ch = get_char(file);
346 else
347 done = TRUE;
350 /* exiting. skip to some blanks, then skip over the blanks.
352 Skip_Nonblanks(ch, file)
353 Skip_Blanks(ch, file)
355 Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
357 return ch;
361 static int
362 random_with_range(int low, int high)
364 /* Kind of crappy error detection, but...
366 if (low >= high)
367 return low;
368 else
369 return arc4random() % (high - low + 1) + low;
373 static char
374 get_range(bitstr_t *bits, /* one bit per flag, default=FALSE */
375 int low, /* bounds, impl. offset for bitstr */
376 int high, /* bounds, impl. offset for bitstr */
377 const char * const names[], /* NULL or names of elements */
378 int ch, /* current character being processed */
379 FILE *file /* file being read */)
381 /* range = number | number "-" number [ "/" number ]
384 int i;
385 int num1, num2, num3;
386 int qmark, star;
388 qmark = star = FALSE;
390 Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
392 if (ch == '*') {
393 /* '*' means "first-last" but can still be modified by /step
395 star = TRUE;
396 num1 = low;
397 num2 = high;
398 ch = get_char(file);
399 if (ch == EOF)
400 return EOF;
401 } else if (ch == '?') {
402 qmark = TRUE;
403 ch = get_char(file);
404 if (ch == EOF)
405 return EOF;
406 if (!isdigit(ch)) {
407 num1 = random_with_range(low, high);
408 if (EOF == set_element(bits, low, high, num1))
409 return EOF;
410 return ch;
414 if (!star) {
415 if (EOF == (ch = get_number(&num1, low, names, ch, file)))
416 return EOF;
418 if (ch != '-') {
419 /* not a range, it's a single number.
420 * a single number after '?' is bogus.
422 if (qmark)
423 return EOF;
424 if (EOF == set_element(bits, low, high, num1))
425 return EOF;
426 return ch;
427 } else {
428 /* eat the dash
430 ch = get_char(file);
431 if (ch == EOF)
432 return EOF;
434 /* get the number following the dash
436 ch = get_number(&num2, low, names, ch, file);
437 if (ch == EOF)
438 return EOF;
440 /* if we have a random range, it is really
441 * like having a single number.
443 if (qmark) {
444 if (num1 > num2)
445 return EOF;
446 num1 = random_with_range(num1, num2);
447 if (EOF == set_element(bits, low, high, num1))
448 return EOF;
449 return ch;
454 /* check for step size
456 if (ch == '/') {
457 /* '?' is incompatible with '/'
459 if (qmark)
460 return EOF;
461 /* eat the slash
463 ch = get_char(file);
464 if (ch == EOF)
465 return EOF;
467 /* get the step size -- note: we don't pass the
468 * names here, because the number is not an
469 * element id, it's a step size. 'low' is
470 * sent as a 0 since there is no offset either.
472 ch = get_number(&num3, 0, PPC_NULL, ch, file);
473 if (ch == EOF)
474 return EOF;
475 } else {
476 /* no step. default==1.
478 num3 = 1;
481 /* range. set all elements from num1 to num2, stepping
482 * by num3. (the step is a downward-compatible extension
483 * proposed conceptually by bob@acornrc, syntactically
484 * designed then implmented by paul vixie).
486 for (i = num1; i <= num2; i += num3)
487 if (EOF == set_element(bits, low, high, i))
488 return EOF;
490 return ch;
494 static char
495 get_number(int *numptr, /* where does the result go? */
496 int low, /* offset applied to result if symbolic enum used */
497 const char * const names[], /* symbolic names, if any, for enums */
498 int ch, /* current character */
499 FILE *file /* source */)
501 char temp[MAX_TEMPSTR], *pc;
502 int len, i, all_digits;
504 /* collect alphanumerics into our fixed-size temp array
506 pc = temp;
507 len = 0;
508 all_digits = TRUE;
509 while (isalnum(ch)) {
510 if (++len >= MAX_TEMPSTR)
511 return EOF;
513 *pc++ = ch;
515 if (!isdigit(ch))
516 all_digits = FALSE;
518 ch = get_char(file);
520 *pc = '\0';
522 /* try to find the name in the name list
524 if (names) {
525 for (i = 0; names[i] != NULL; i++) {
526 Debug(DPARS|DEXT,
527 ("get_num, compare(%s,%s)\n", names[i], temp))
528 if (!strcasecmp(names[i], temp)) {
529 *numptr = i+low;
530 return ch;
535 /* no name list specified, or there is one and our string isn't
536 * in it. either way: if it's all digits, use its magnitude.
537 * otherwise, it's an error.
539 if (all_digits) {
540 *numptr = atoi(temp);
541 return ch;
544 return EOF;
548 static int
549 set_element(bitstr_t *bits, /* one bit per flag, default=FALSE */
550 int low, int high, int number)
552 Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
554 if (number < low || number > high)
555 return EOF;
557 bit_set(bits, (number-low));
558 return OK;