12311Sjkh/* Copyright 1988,1990,1993,1994 by Paul Vixie
22311Sjkh * All rights reserved
32311Sjkh *
42311Sjkh * Distribute freely, except: don't remove my name from the source or
52311Sjkh * documentation (don't take credit for my work), mark your changes (don't
62311Sjkh * get me blamed for your possible bugs), don't alter or remove this
72311Sjkh * notice.  May be sold if buildable source is provided to buyer.  No
82311Sjkh * warrantee of any kind, express or implied, is included with this
92311Sjkh * software; use at your own risk, responsibility for damages (if any) to
102311Sjkh * anyone resulting from the use of this software rests entirely with the
112311Sjkh * user.
122311Sjkh *
132311Sjkh * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
142311Sjkh * I'll try to keep a version up to date.  I can be reached as follows:
152311Sjkh * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
162311Sjkh */
172311Sjkh
182311Sjkh#if !defined(lint) && !defined(LINT)
1929452Scharnierstatic const char rcsid[] =
2050479Speter  "$FreeBSD$";
212311Sjkh#endif
222311Sjkh
232311Sjkh/* vix 26jan87 [RCS'd; rest of log is in RCS file]
242311Sjkh * vix 01jan87 [added line-level error recovery]
252311Sjkh * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
262311Sjkh * vix 30dec86 [written]
272311Sjkh */
282311Sjkh
292311Sjkh
302311Sjkh#include "cron.h"
3130895Sache#include <grp.h>
3230895Sache#ifdef LOGIN_CAP
3330895Sache#include <login_cap.h>
3430895Sache#endif
352311Sjkh
362311Sjkhtypedef	enum ecode {
372311Sjkh	e_none, e_minute, e_hour, e_dom, e_month, e_dow,
3860825Sghelmer	e_cmd, e_timespec, e_username, e_group, e_mem
3930895Sache#ifdef LOGIN_CAP
4030895Sache	, e_class
4130895Sache#endif
422311Sjkh} ecode_e;
432311Sjkh
44173412Skevlostatic char	get_list(bitstr_t *, int, int, char *[], int, FILE *),
45173412Skevlo		get_range(bitstr_t *, int, int, char *[], int, FILE *),
46173412Skevlo		get_number(int *, int, char *[], int, FILE *);
47173412Skevlostatic int	set_element(bitstr_t *, int, int, int);
482311Sjkh
492311Sjkhstatic char *ecodes[] =
502311Sjkh	{
512311Sjkh		"no error",
522311Sjkh		"bad minute",
532311Sjkh		"bad hour",
542311Sjkh		"bad day-of-month",
552311Sjkh		"bad month",
562311Sjkh		"bad day-of-week",
572311Sjkh		"bad command",
582311Sjkh		"bad time specifier",
592311Sjkh		"bad username",
6030895Sache		"bad group name",
6160825Sghelmer		"out of memory",
6230895Sache#ifdef LOGIN_CAP
6330895Sache		"bad class name",
6430895Sache#endif
652311Sjkh	};
662311Sjkh
672311Sjkh
682311Sjkhvoid
692311Sjkhfree_entry(e)
702311Sjkh	entry	*e;
712311Sjkh{
7230895Sache#ifdef LOGIN_CAP
7330895Sache	if (e->class != NULL)
7430895Sache		free(e->class);
7530895Sache#endif
7679860Sdd	if (e->cmd != NULL)
7779860Sdd		free(e->cmd);
7879860Sdd	if (e->envp != NULL)
7979860Sdd		env_free(e->envp);
802311Sjkh	free(e);
812311Sjkh}
822311Sjkh
832311Sjkh
842311Sjkh/* return NULL if eof or syntax error occurs;
852311Sjkh * otherwise return a pointer to a new entry.
862311Sjkh */
872311Sjkhentry *
882311Sjkhload_entry(file, error_func, pw, envp)
892311Sjkh	FILE		*file;
90184809Smatteo	void		(*error_func)(char *);
912311Sjkh	struct passwd	*pw;
922311Sjkh	char		**envp;
932311Sjkh{
942311Sjkh	/* this function reads one crontab entry -- the next -- from a file.
952311Sjkh	 * it skips any leading blank lines, ignores comments, and returns
962311Sjkh	 * EOF if for any reason the entry can't be read and parsed.
972311Sjkh	 *
982311Sjkh	 * the entry is also parsed here.
992311Sjkh	 *
1002311Sjkh	 * syntax:
1012311Sjkh	 *   user crontab:
1022311Sjkh	 *	minutes hours doms months dows cmd\n
1032311Sjkh	 *   system crontab (/etc/crontab):
1042311Sjkh	 *	minutes hours doms months dows USERNAME cmd\n
1052311Sjkh	 */
1062311Sjkh
1072311Sjkh	ecode_e	ecode = e_none;
1082311Sjkh	entry	*e;
1092311Sjkh	int	ch;
1102311Sjkh	char	cmd[MAX_COMMAND];
1112311Sjkh	char	envstr[MAX_ENVSTR];
11260825Sghelmer	char	**prev_env;
1132311Sjkh
1142311Sjkh	Debug(DPARS, ("load_entry()...about to eat comments\n"))
1152311Sjkh
1162311Sjkh	skip_comments(file);
1172311Sjkh
1182311Sjkh	ch = get_char(file);
1192311Sjkh	if (ch == EOF)
1202311Sjkh		return NULL;
1212311Sjkh
1222311Sjkh	/* ch is now the first useful character of a useful line.
1232311Sjkh	 * it may be an @special or it may be the first character
1242311Sjkh	 * of a list of minutes.
1252311Sjkh	 */
1262311Sjkh
1272311Sjkh	e = (entry *) calloc(sizeof(entry), sizeof(char));
1282311Sjkh
12960825Sghelmer	if (e == NULL) {
13060825Sghelmer		warn("load_entry: calloc failed");
13160825Sghelmer		return NULL;
13260825Sghelmer	}
13360825Sghelmer
1342311Sjkh	if (ch == '@') {
1352311Sjkh		/* all of these should be flagged and load-limited; i.e.,
1362311Sjkh		 * instead of @hourly meaning "0 * * * *" it should mean
1372311Sjkh		 * "close to the front of every hour but not 'til the
1382311Sjkh		 * system load is low".  Problems are: how do you know
1392311Sjkh		 * what "low" means? (save me from /etc/cron.conf!) and:
1402311Sjkh		 * how to guarantee low variance (how low is low?), which
1412311Sjkh		 * means how to we run roughly every hour -- seems like
1422311Sjkh		 * we need to keep a history or let the first hour set
1432311Sjkh		 * the schedule, which means we aren't load-limited
1442311Sjkh		 * anymore.  too much for my overloaded brain. (vix, jan90)
1452311Sjkh		 * HINT
1462311Sjkh		 */
14758017Sghelmer		Debug(DPARS, ("load_entry()...about to test shortcuts\n"))
1482311Sjkh		ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
1492311Sjkh		if (!strcmp("reboot", cmd)) {
15058017Sghelmer			Debug(DPARS, ("load_entry()...reboot shortcut\n"))
1512311Sjkh			e->flags |= WHEN_REBOOT;
1522311Sjkh		} else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
15358017Sghelmer			Debug(DPARS, ("load_entry()...yearly shortcut\n"))
154242101Ssobomax			bit_set(e->second, 0);
1552311Sjkh			bit_set(e->minute, 0);
1562311Sjkh			bit_set(e->hour, 0);
1572311Sjkh			bit_set(e->dom, 0);
1582311Sjkh			bit_set(e->month, 0);
1592311Sjkh			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
16081778Smikeh			e->flags |= DOW_STAR;
1612311Sjkh		} else if (!strcmp("monthly", cmd)) {
16258017Sghelmer			Debug(DPARS, ("load_entry()...monthly shortcut\n"))
163242101Ssobomax			bit_set(e->second, 0);
1642311Sjkh			bit_set(e->minute, 0);
1652311Sjkh			bit_set(e->hour, 0);
1662311Sjkh			bit_set(e->dom, 0);
1672311Sjkh			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
1682311Sjkh			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
16981778Smikeh			e->flags |= DOW_STAR;
1702311Sjkh		} else if (!strcmp("weekly", cmd)) {
17158017Sghelmer			Debug(DPARS, ("load_entry()...weekly shortcut\n"))
172242101Ssobomax			bit_set(e->second, 0);
1732311Sjkh			bit_set(e->minute, 0);
1742311Sjkh			bit_set(e->hour, 0);
1752311Sjkh			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
17681778Smikeh			e->flags |= DOM_STAR;
1772311Sjkh			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
1782311Sjkh			bit_set(e->dow, 0);
1792311Sjkh		} else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
18058017Sghelmer			Debug(DPARS, ("load_entry()...daily shortcut\n"))
181242101Ssobomax			bit_set(e->second, 0);
1822311Sjkh			bit_set(e->minute, 0);
1832311Sjkh			bit_set(e->hour, 0);
1842311Sjkh			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
1852311Sjkh			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
1862311Sjkh			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
1872311Sjkh		} else if (!strcmp("hourly", cmd)) {
18858017Sghelmer			Debug(DPARS, ("load_entry()...hourly shortcut\n"))
189242101Ssobomax			bit_set(e->second, 0);
1902311Sjkh			bit_set(e->minute, 0);
19158017Sghelmer			bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
1922311Sjkh			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
1932311Sjkh			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
1942311Sjkh			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
195242101Ssobomax		} else if (!strcmp("every_minute", cmd)) {
196242101Ssobomax			Debug(DPARS, ("load_entry()...every_minute shortcut\n"))
197242101Ssobomax			bit_set(e->second, 0);
198242101Ssobomax			bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1));
199242101Ssobomax			bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
200242101Ssobomax			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
201242101Ssobomax			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
202242101Ssobomax			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
203242101Ssobomax		} else if (!strcmp("every_second", cmd)) {
204242101Ssobomax			Debug(DPARS, ("load_entry()...every_second shortcut\n"))
205242101Ssobomax			e->flags |= SEC_RES;
206242101Ssobomax			bit_nset(e->second, 0, (LAST_SECOND-FIRST_SECOND+1));
207242101Ssobomax			bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1));
208242101Ssobomax			bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
209242101Ssobomax			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
210242101Ssobomax			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
211242101Ssobomax			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
2122311Sjkh		} else {
2132311Sjkh			ecode = e_timespec;
2142311Sjkh			goto eof;
2152311Sjkh		}
21658017Sghelmer		/* Advance past whitespace between shortcut and
21758017Sghelmer		 * username/command.
21858017Sghelmer		 */
21958017Sghelmer		Skip_Blanks(ch, file);
22058017Sghelmer		if (ch == EOF) {
22158017Sghelmer			ecode = e_cmd;
22258017Sghelmer			goto eof;
22358017Sghelmer		}
2242311Sjkh	} else {
2252311Sjkh		Debug(DPARS, ("load_entry()...about to parse numerics\n"))
226242101Ssobomax		bit_set(e->second, 0);
2272311Sjkh
2282311Sjkh		ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
2292311Sjkh			      PPC_NULL, ch, file);
2302311Sjkh		if (ch == EOF) {
2312311Sjkh			ecode = e_minute;
2322311Sjkh			goto eof;
2332311Sjkh		}
2342311Sjkh
2352311Sjkh		/* hours
2362311Sjkh		 */
2372311Sjkh
2382311Sjkh		ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
2392311Sjkh			      PPC_NULL, ch, file);
2402311Sjkh		if (ch == EOF) {
2412311Sjkh			ecode = e_hour;
2422311Sjkh			goto eof;
2432311Sjkh		}
2442311Sjkh
2452311Sjkh		/* DOM (days of month)
2462311Sjkh		 */
2472311Sjkh
2482311Sjkh		if (ch == '*')
2492311Sjkh			e->flags |= DOM_STAR;
2502311Sjkh		ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
2512311Sjkh			      PPC_NULL, ch, file);
2522311Sjkh		if (ch == EOF) {
2532311Sjkh			ecode = e_dom;
2542311Sjkh			goto eof;
2552311Sjkh		}
2562311Sjkh
2572311Sjkh		/* month
2582311Sjkh		 */
2592311Sjkh
2602311Sjkh		ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
2612311Sjkh			      MonthNames, ch, file);
2622311Sjkh		if (ch == EOF) {
2632311Sjkh			ecode = e_month;
2642311Sjkh			goto eof;
2652311Sjkh		}
2662311Sjkh
2672311Sjkh		/* DOW (days of week)
2682311Sjkh		 */
2692311Sjkh
2702311Sjkh		if (ch == '*')
2712311Sjkh			e->flags |= DOW_STAR;
2722311Sjkh		ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
2732311Sjkh			      DowNames, ch, file);
2742311Sjkh		if (ch == EOF) {
2752311Sjkh			ecode = e_dow;
2762311Sjkh			goto eof;
2772311Sjkh		}
2782311Sjkh	}
2792311Sjkh
280228990Suqs	/* make sundays equivalent */
2812311Sjkh	if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
2822311Sjkh		bit_set(e->dow, 0);
2832311Sjkh		bit_set(e->dow, 7);
2842311Sjkh	}
2852311Sjkh
2862311Sjkh	/* ch is the first character of a command, or a username */
2872311Sjkh	unget_char(ch, file);
2882311Sjkh
2892311Sjkh	if (!pw) {
2902311Sjkh		char		*username = cmd;	/* temp buffer */
29179861Sdd		char            *s;
29230895Sache		struct group    *grp;
29378156Sdd#ifdef LOGIN_CAP
29478156Sdd		login_cap_t *lc;
29578156Sdd#endif
2962311Sjkh
2972311Sjkh		Debug(DPARS, ("load_entry()...about to parse username\n"))
2982311Sjkh		ch = get_string(username, MAX_COMMAND, file, " \t");
2992311Sjkh
3002311Sjkh		Debug(DPARS, ("load_entry()...got %s\n",username))
3012311Sjkh		if (ch == EOF) {
3022311Sjkh			ecode = e_cmd;
3032311Sjkh			goto eof;
3042311Sjkh		}
3052311Sjkh
30630895Sache#ifdef LOGIN_CAP
30730895Sache		if ((s = strrchr(username, '/')) != NULL) {
30830895Sache			*s = '\0';
30930895Sache			e->class = strdup(s + 1);
31060825Sghelmer			if (e->class == NULL)
31160825Sghelmer				warn("strdup(\"%s\")", s + 1);
31260825Sghelmer		} else {
31330895Sache			e->class = strdup(RESOURCE_RC);
31460825Sghelmer			if (e->class == NULL)
31560825Sghelmer				warn("strdup(\"%s\")", RESOURCE_RC);
31660825Sghelmer		}
31760825Sghelmer		if (e->class == NULL) {
31860825Sghelmer			ecode = e_mem;
31960825Sghelmer			goto eof;
32060825Sghelmer		}
32178156Sdd		if ((lc = login_getclass(e->class)) == NULL) {
32230895Sache			ecode = e_class;
32330895Sache			goto eof;
32430895Sache		}
32578156Sdd		login_close(lc);
32630895Sache#endif
32730895Sache		grp = NULL;
32830895Sache		if ((s = strrchr(username, ':')) != NULL) {
32930895Sache			*s = '\0';
33030895Sache			if ((grp = getgrnam(s + 1)) == NULL) {
33130895Sache				ecode = e_group;
33230895Sache				goto eof;
33330895Sache			}
33430895Sache		}
33530895Sache
3362311Sjkh		pw = getpwnam(username);
3372311Sjkh		if (pw == NULL) {
3382311Sjkh			ecode = e_username;
3392311Sjkh			goto eof;
3402311Sjkh		}
34130895Sache		if (grp != NULL)
34230895Sache			pw->pw_gid = grp->gr_gid;
34330895Sache		Debug(DPARS, ("load_entry()...uid %d, gid %d\n",pw->pw_uid,pw->pw_gid))
34430895Sache#ifdef LOGIN_CAP
34530895Sache		Debug(DPARS, ("load_entry()...class %s\n",e->class))
34630895Sache#endif
3472311Sjkh	}
3482311Sjkh
349170890Syar#ifndef PAM	/* PAM takes care of account expiration by itself */
35010401Smpp	if (pw->pw_expire && time(NULL) >= pw->pw_expire) {
35110401Smpp		ecode = e_username;
35210401Smpp		goto eof;
35310401Smpp	}
354170890Syar#endif /* !PAM */
35510401Smpp
3562311Sjkh	e->uid = pw->pw_uid;
3572311Sjkh	e->gid = pw->pw_gid;
3582311Sjkh
3592311Sjkh	/* copy and fix up environment.  some variables are just defaults and
3602311Sjkh	 * others are overrides.
3612311Sjkh	 */
3622311Sjkh	e->envp = env_copy(envp);
36360825Sghelmer	if (e->envp == NULL) {
36460825Sghelmer		warn("env_copy");
36560825Sghelmer		ecode = e_mem;
36660825Sghelmer		goto eof;
36760825Sghelmer	}
3682311Sjkh	if (!env_get("SHELL", e->envp)) {
36960825Sghelmer		prev_env = e->envp;
3702311Sjkh		sprintf(envstr, "SHELL=%s", _PATH_BSHELL);
3712311Sjkh		e->envp = env_set(e->envp, envstr);
37260825Sghelmer		if (e->envp == NULL) {
37360825Sghelmer			warn("env_set(%s)", envstr);
37460825Sghelmer			env_free(prev_env);
37560825Sghelmer			ecode = e_mem;
37660825Sghelmer			goto eof;
37760825Sghelmer		}
3782311Sjkh	}
379167328Swill	if (!env_get("HOME", e->envp)) {
380167328Swill		prev_env = e->envp;
381167328Swill		sprintf(envstr, "HOME=%s", pw->pw_dir);
382167328Swill		e->envp = env_set(e->envp, envstr);
383167328Swill		if (e->envp == NULL) {
384167328Swill			warn("env_set(%s)", envstr);
385167328Swill			env_free(prev_env);
386167328Swill			ecode = e_mem;
387167328Swill			goto eof;
388167328Swill		}
38960825Sghelmer	}
3902311Sjkh	if (!env_get("PATH", e->envp)) {
39160825Sghelmer		prev_env = e->envp;
3922311Sjkh		sprintf(envstr, "PATH=%s", _PATH_DEFPATH);
3932311Sjkh		e->envp = env_set(e->envp, envstr);
39460825Sghelmer		if (e->envp == NULL) {
39560825Sghelmer			warn("env_set(%s)", envstr);
39660825Sghelmer			env_free(prev_env);
39760825Sghelmer			ecode = e_mem;
39860825Sghelmer			goto eof;
39960825Sghelmer		}
4002311Sjkh	}
40160825Sghelmer	prev_env = e->envp;
4022311Sjkh	sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
4032311Sjkh	e->envp = env_set(e->envp, envstr);
40460825Sghelmer	if (e->envp == NULL) {
40560825Sghelmer		warn("env_set(%s)", envstr);
40660825Sghelmer		env_free(prev_env);
40760825Sghelmer		ecode = e_mem;
40860825Sghelmer		goto eof;
40960825Sghelmer	}
4102311Sjkh#if defined(BSD)
41160825Sghelmer	prev_env = e->envp;
4122311Sjkh	sprintf(envstr, "%s=%s", "USER", pw->pw_name);
4132311Sjkh	e->envp = env_set(e->envp, envstr);
41460825Sghelmer	if (e->envp == NULL) {
41560825Sghelmer		warn("env_set(%s)", envstr);
41660825Sghelmer		env_free(prev_env);
41760825Sghelmer		ecode = e_mem;
41860825Sghelmer		goto eof;
41960825Sghelmer	}
4202311Sjkh#endif
4212311Sjkh
4222311Sjkh	Debug(DPARS, ("load_entry()...about to parse command\n"))
4232311Sjkh
4242311Sjkh	/* Everything up to the next \n or EOF is part of the command...
4252311Sjkh	 * too bad we don't know in advance how long it will be, since we
4262311Sjkh	 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
4272311Sjkh	 * XXX - should use realloc().
4288857Srgrimes	 */
4292311Sjkh	ch = get_string(cmd, MAX_COMMAND, file, "\n");
4302311Sjkh
4312311Sjkh	/* a file without a \n before the EOF is rude, so we'll complain...
4322311Sjkh	 */
4332311Sjkh	if (ch == EOF) {
4342311Sjkh		ecode = e_cmd;
4352311Sjkh		goto eof;
4362311Sjkh	}
4372311Sjkh
4382311Sjkh	/* got the command in the 'cmd' string; save it in *e.
4392311Sjkh	 */
4402311Sjkh	e->cmd = strdup(cmd);
44160825Sghelmer	if (e->cmd == NULL) {
44279861Sdd		warn("strdup(\"%s\")", cmd);
44360825Sghelmer		ecode = e_mem;
44460825Sghelmer		goto eof;
44560825Sghelmer	}
4462311Sjkh	Debug(DPARS, ("load_entry()...returning successfully\n"))
4472311Sjkh
4482311Sjkh	/* success, fini, return pointer to the entry we just created...
4492311Sjkh	 */
4502311Sjkh	return e;
4512311Sjkh
4522311Sjkh eof:
45378156Sdd	free_entry(e);
4542311Sjkh	if (ecode != e_none && error_func)
4552311Sjkh		(*error_func)(ecodes[(int)ecode]);
4562311Sjkh	while (ch != EOF && ch != '\n')
4572311Sjkh		ch = get_char(file);
4582311Sjkh	return NULL;
4592311Sjkh}
4602311Sjkh
4612311Sjkh
4622311Sjkhstatic char
4632311Sjkhget_list(bits, low, high, names, ch, file)
4642311Sjkh	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
4652311Sjkh	int		low, high;	/* bounds, impl. offset for bitstr */
4662311Sjkh	char		*names[];	/* NULL or *[] of names for these elements */
4672311Sjkh	int		ch;		/* current character being processed */
4682311Sjkh	FILE		*file;		/* file being read */
4692311Sjkh{
4702311Sjkh	register int	done;
4712311Sjkh
4722311Sjkh	/* we know that we point to a non-blank character here;
4732311Sjkh	 * must do a Skip_Blanks before we exit, so that the
4742311Sjkh	 * next call (or the code that picks up the cmd) can
4752311Sjkh	 * assume the same thing.
4762311Sjkh	 */
4772311Sjkh
4782311Sjkh	Debug(DPARS|DEXT, ("get_list()...entered\n"))
4792311Sjkh
4802311Sjkh	/* list = range {"," range}
4812311Sjkh	 */
4828857Srgrimes
4832311Sjkh	/* clear the bit string, since the default is 'off'.
4842311Sjkh	 */
4852311Sjkh	bit_nclear(bits, 0, (high-low+1));
4862311Sjkh
4872311Sjkh	/* process all ranges
4882311Sjkh	 */
4892311Sjkh	done = FALSE;
4902311Sjkh	while (!done) {
4912311Sjkh		ch = get_range(bits, low, high, names, ch, file);
4922311Sjkh		if (ch == ',')
4932311Sjkh			ch = get_char(file);
4942311Sjkh		else
4952311Sjkh			done = TRUE;
4962311Sjkh	}
4972311Sjkh
4982311Sjkh	/* exiting.  skip to some blanks, then skip over the blanks.
4992311Sjkh	 */
5002311Sjkh	Skip_Nonblanks(ch, file)
5012311Sjkh	Skip_Blanks(ch, file)
5022311Sjkh
5032311Sjkh	Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
5042311Sjkh
5052311Sjkh	return ch;
5062311Sjkh}
5072311Sjkh
5082311Sjkh
5092311Sjkhstatic char
5102311Sjkhget_range(bits, low, high, names, ch, file)
5112311Sjkh	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
5122311Sjkh	int		low, high;	/* bounds, impl. offset for bitstr */
5132311Sjkh	char		*names[];	/* NULL or names of elements */
5142311Sjkh	int		ch;		/* current character being processed */
5152311Sjkh	FILE		*file;		/* file being read */
5162311Sjkh{
5172311Sjkh	/* range = number | number "-" number [ "/" number ]
5182311Sjkh	 */
5192311Sjkh
5202311Sjkh	register int	i;
5212311Sjkh	auto int	num1, num2, num3;
5222311Sjkh
5232311Sjkh	Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
5242311Sjkh
5252311Sjkh	if (ch == '*') {
5262311Sjkh		/* '*' means "first-last" but can still be modified by /step
5272311Sjkh		 */
5282311Sjkh		num1 = low;
5292311Sjkh		num2 = high;
5302311Sjkh		ch = get_char(file);
5312311Sjkh		if (ch == EOF)
5322311Sjkh			return EOF;
5332311Sjkh	} else {
5342311Sjkh		if (EOF == (ch = get_number(&num1, low, names, ch, file)))
5352311Sjkh			return EOF;
5362311Sjkh
537162666Sbrian		if (ch == '/')
538162666Sbrian			num2 = high;
539162666Sbrian		else if (ch != '-') {
5402311Sjkh			/* not a range, it's a single number.
5412311Sjkh			 */
5422311Sjkh			if (EOF == set_element(bits, low, high, num1))
5432311Sjkh				return EOF;
5442311Sjkh			return ch;
5452311Sjkh		} else {
5462311Sjkh			/* eat the dash
5472311Sjkh			 */
5482311Sjkh			ch = get_char(file);
5492311Sjkh			if (ch == EOF)
5502311Sjkh				return EOF;
5512311Sjkh
5522311Sjkh			/* get the number following the dash
5532311Sjkh			 */
5542311Sjkh			ch = get_number(&num2, low, names, ch, file);
5552311Sjkh			if (ch == EOF)
5562311Sjkh				return EOF;
5572311Sjkh		}
5582311Sjkh	}
5592311Sjkh
5602311Sjkh	/* check for step size
5612311Sjkh	 */
5622311Sjkh	if (ch == '/') {
5632311Sjkh		/* eat the slash
5642311Sjkh		 */
5652311Sjkh		ch = get_char(file);
5662311Sjkh		if (ch == EOF)
5672311Sjkh			return EOF;
5682311Sjkh
5692311Sjkh		/* get the step size -- note: we don't pass the
5702311Sjkh		 * names here, because the number is not an
5712311Sjkh		 * element id, it's a step size.  'low' is
5722311Sjkh		 * sent as a 0 since there is no offset either.
5732311Sjkh		 */
5742311Sjkh		ch = get_number(&num3, 0, PPC_NULL, ch, file);
575141915Sdelphij		if (ch == EOF || num3 == 0)
5762311Sjkh			return EOF;
5772311Sjkh	} else {
5782311Sjkh		/* no step.  default==1.
5792311Sjkh		 */
5802311Sjkh		num3 = 1;
5812311Sjkh	}
5822311Sjkh
5832311Sjkh	/* range. set all elements from num1 to num2, stepping
5842311Sjkh	 * by num3.  (the step is a downward-compatible extension
5852311Sjkh	 * proposed conceptually by bob@acornrc, syntactically
5862311Sjkh	 * designed then implmented by paul vixie).
5872311Sjkh	 */
5882311Sjkh	for (i = num1;  i <= num2;  i += num3)
5892311Sjkh		if (EOF == set_element(bits, low, high, i))
5902311Sjkh			return EOF;
5912311Sjkh
5922311Sjkh	return ch;
5932311Sjkh}
5942311Sjkh
5952311Sjkh
5962311Sjkhstatic char
5972311Sjkhget_number(numptr, low, names, ch, file)
5982311Sjkh	int	*numptr;	/* where does the result go? */
5992311Sjkh	int	low;		/* offset applied to result if symbolic enum used */
6002311Sjkh	char	*names[];	/* symbolic names, if any, for enums */
6012311Sjkh	int	ch;		/* current character */
6022311Sjkh	FILE	*file;		/* source */
6032311Sjkh{
6042311Sjkh	char	temp[MAX_TEMPSTR], *pc;
6052311Sjkh	int	len, i, all_digits;
6062311Sjkh
6072311Sjkh	/* collect alphanumerics into our fixed-size temp array
6082311Sjkh	 */
6092311Sjkh	pc = temp;
6102311Sjkh	len = 0;
6112311Sjkh	all_digits = TRUE;
6122311Sjkh	while (isalnum(ch)) {
6132311Sjkh		if (++len >= MAX_TEMPSTR)
6142311Sjkh			return EOF;
6152311Sjkh
6162311Sjkh		*pc++ = ch;
6172311Sjkh
6182311Sjkh		if (!isdigit(ch))
6192311Sjkh			all_digits = FALSE;
6202311Sjkh
6212311Sjkh		ch = get_char(file);
6222311Sjkh	}
6232311Sjkh	*pc = '\0';
624141915Sdelphij	if (len == 0)
625141915Sdelphij	    return (EOF);
6262311Sjkh
6272311Sjkh	/* try to find the name in the name list
6282311Sjkh	 */
6292311Sjkh	if (names) {
6302311Sjkh		for (i = 0;  names[i] != NULL;  i++) {
6312311Sjkh			Debug(DPARS|DEXT,
6322311Sjkh				("get_num, compare(%s,%s)\n", names[i], temp))
6332311Sjkh			if (!strcasecmp(names[i], temp)) {
6342311Sjkh				*numptr = i+low;
6352311Sjkh				return ch;
6362311Sjkh			}
6372311Sjkh		}
6382311Sjkh	}
6392311Sjkh
6402311Sjkh	/* no name list specified, or there is one and our string isn't
6412311Sjkh	 * in it.  either way: if it's all digits, use its magnitude.
6422311Sjkh	 * otherwise, it's an error.
6432311Sjkh	 */
6442311Sjkh	if (all_digits) {
6452311Sjkh		*numptr = atoi(temp);
6462311Sjkh		return ch;
6472311Sjkh	}
6482311Sjkh
6492311Sjkh	return EOF;
6502311Sjkh}
6512311Sjkh
6522311Sjkh
6532311Sjkhstatic int
6542311Sjkhset_element(bits, low, high, number)
6552311Sjkh	bitstr_t	*bits; 		/* one bit per flag, default=FALSE */
6562311Sjkh	int		low;
6572311Sjkh	int		high;
6582311Sjkh	int		number;
6592311Sjkh{
6602311Sjkh	Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
6612311Sjkh
6622311Sjkh	if (number < low || number > high)
6632311Sjkh		return EOF;
6642311Sjkh
6652311Sjkh	bit_set(bits, (number-low));
6662311Sjkh	return OK;
6672311Sjkh}
668