1 /* $NetBSD: entry.c,v 1.9 2008/02/16 07:26:00 matt Exp $ */
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
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
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)
23 static char rcsid
[] = "Id: entry.c,v 2.12 1994/01/17 03:20:37 vixie Exp";
25 __RCSID("$NetBSD: entry.c,v 1.9 2008/02/16 07:26:00 matt Exp $");
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]
40 e_none
, e_minute
, e_hour
, e_dom
, e_month
, e_dow
,
41 e_cmd
, e_timespec
, e_username
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
[] =
72 /* return NULL if eof or syntax error occurs;
73 * otherwise return a pointer to a new entry.
76 load_entry(FILE *file
, void (*error_func
)(const char *), struct passwd
*pw
,
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.
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
;
95 char cmd
[MAX_COMMAND
];
96 char envstr
[MAX_ENVSTR
];
98 Debug(DPARS
, ("load_entry()...about to eat comments\n"))
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));
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)
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);
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);
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);
146 bit_nset(e
->dom
, 0, (LAST_DOM
-FIRST_DOM
+1));
147 bit_nset(e
->month
, 0, (LAST_MONTH
-FIRST_MONTH
+1));
149 e
->flags
|= DOM_STAR
;
150 } else if (!strcmp("daily", cmd
) || !strcmp("midnight", cmd
)) {
151 bit_set(e
->minute
, 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
;
169 Debug(DPARS
, ("load_entry()...about to parse numerics\n"))
171 ch
= get_list(e
->minute
, FIRST_MINUTE
, LAST_MINUTE
,
181 ch
= get_list(e
->hour
, FIRST_HOUR
, LAST_HOUR
,
188 /* DOM (days of month)
192 e
->flags
|= DOM_STAR
;
193 ch
= get_list(e
->dom
, FIRST_DOM
, LAST_DOM
,
203 ch
= get_list(e
->month
, FIRST_MONTH
, LAST_MONTH
,
204 MonthNames
, ch
, file
);
210 /* DOW (days of week)
214 e
->flags
|= DOW_STAR
;
215 ch
= get_list(e
->dow
, FIRST_DOW
, LAST_DOW
,
223 /* make sundays equivilent */
224 if (bit_test(e
->dow
, 0) || bit_test(e
->dow
, 7)) {
229 /* ch is the first character of a command, or a username */
230 unget_char(ch
, file
);
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
))
244 pw
= getpwnam(username
);
249 Debug(DPARS
, ("load_entry()...uid %d, gid %d\n",e
->uid
,e
->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
);
274 snprintf(envstr
, sizeof(envstr
), "%s=%s", "USER", pw
->pw_name
);
275 e
->envp
= env_set(e
->envp
, envstr
);
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...
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...
306 if (ecode
!= e_none
&& error_func
)
307 (*error_func
)(ecodes
[(int)ecode
]);
308 while (ch
!= EOF
&& ch
!= '\n')
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 */)
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
343 ch
= get_range(bits
, low
, high
, names
, ch
, file
);
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
))
362 random_with_range(int low
, int high
)
364 /* Kind of crappy error detection, but...
369 return arc4random() % (high
- low
+ 1) + low
;
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 ]
385 int num1
, num2
, num3
;
388 qmark
= star
= FALSE
;
390 Debug(DPARS
|DEXT
, ("get_range()...entering, exit won't show\n"))
393 /* '*' means "first-last" but can still be modified by /step
401 } else if (ch
== '?') {
407 num1
= random_with_range(low
, high
);
408 if (EOF
== set_element(bits
, low
, high
, num1
))
415 if (EOF
== (ch
= get_number(&num1
, low
, names
, ch
, file
)))
419 /* not a range, it's a single number.
420 * a single number after '?' is bogus.
424 if (EOF
== set_element(bits
, low
, high
, num1
))
434 /* get the number following the dash
436 ch
= get_number(&num2
, low
, names
, ch
, file
);
440 /* if we have a random range, it is really
441 * like having a single number.
446 num1
= random_with_range(num1
, num2
);
447 if (EOF
== set_element(bits
, low
, high
, num1
))
454 /* check for step size
457 /* '?' is incompatible with '/'
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
);
476 /* no step. default==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
))
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
509 while (isalnum(ch
)) {
510 if (++len
>= MAX_TEMPSTR
)
522 /* try to find the name in the name list
525 for (i
= 0; names
[i
] != NULL
; i
++) {
527 ("get_num, compare(%s,%s)\n", names
[i
], temp
))
528 if (!strcasecmp(names
[i
], temp
)) {
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.
540 *numptr
= atoi(temp
);
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
)
557 bit_set(bits
, (number
-low
));