xref: /openbsd/bin/chio/parse.y (revision 7fc93de0)
1 /*	$OpenBSD: parse.y,v 1.14 2009/03/31 21:03:48 tobias Exp $ */
2 
3 /*
4  * Copyright (c) 2006 Bob Beck <beck@openbsd.org>
5  * Copyright (c) 2002-2006 Henning Brauer <henning@openbsd.org>
6  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
7  * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
8  * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
9  *
10  * Permission to use, copy, modify, and distribute this software for any
11  * purpose with or without fee is hereby granted, provided that the above
12  * copyright notice and this permission notice appear in all copies.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21  */
22 
23 %{
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 #include <sys/queue.h>
27 
28 #include <ctype.h>
29 #include <err.h>
30 #include <libgen.h>
31 #include <limits.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <string.h>
35 
36 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
37 static struct file {
38 	TAILQ_ENTRY(file)	 entry;
39 	FILE			*stream;
40 	char			*name;
41 	int			 lineno;
42 	int			 errors;
43 } *file, *topfile;
44 struct file	*pushfile(const char *);
45 int		 popfile(void);
46 int		 yyparse(void);
47 int		 yylex(void);
48 int		 yyerror(const char *, ...);
49 int		 kw_cmp(const void *, const void *);
50 int		 lookup(char *);
51 int		 lgetc(int);
52 int		 lungetc(int);
53 int		 findeol(void);
54 
55 struct changer {
56 	TAILQ_ENTRY(changer)	  entry;
57 	char			 *name;
58 	char			**drives;
59 	u_int			  drivecnt;
60 };
61 TAILQ_HEAD(changers, changer)	 changers;
62 struct changer			*curchanger;
63 
64 typedef struct {
65 	union {
66 		int64_t			 number;
67 		char			*string;
68 	} v;
69 	int lineno;
70 } YYSTYPE;
71 
72 %}
73 
74 %token	CHANGER
75 %token	DRIVE
76 %token	ERROR
77 %token	<v.string>		STRING
78 %token	<v.number>		NUMBER
79 %%
80 
81 grammar		: /* empty */
82 		| grammar '\n'
83 		| grammar main '\n'
84 		| grammar error '\n'		{ file->errors++; }
85 		;
86 
87 optnl		: '\n' optnl
88 		|
89 		;
90 
91 nl		: '\n' optnl
92 		;
93 
94 main		: CHANGER STRING optnl '{' optnl {
95 			curchanger = new_changer($2);
96 		}
97 		    changeropts_l '}' {
98 			TAILQ_INSERT_TAIL(&changers, curchanger, entry);
99 			curchanger = NULL;
100 		}
101 		;
102 
103 changeropts_l	: changeropts_l changeroptsl
104 		| changeroptsl
105 		;
106 
107 changeroptsl	: changeropts nl
108 		| error nl
109 		;
110 
111 changeropts	: DRIVE STRING	{
112 			void *newp;
113 
114 			if ((newp = realloc(curchanger->drives,
115 			    (curchanger->drivecnt + 1) *
116 			    sizeof(curchanger->drives))) == NULL)
117 				err(1, NULL);
118 			curchanger->drives = newp;
119 			if ((curchanger->drives[curchanger->drivecnt] =
120 			    strdup($2)) == NULL)
121 				err(1, NULL);
122 			curchanger->drivecnt++;
123 			free($2);
124 		}
125 		;
126 
127 %%
128 
129 struct keywords {
130 	const char	*k_name;
131 	int		 k_val;
132 };
133 
134 int
135 yyerror(const char *fmt, ...)
136 {
137 	va_list		 ap;
138 	char		*nfmt;
139 
140 	file->errors++;
141 	va_start(ap, fmt);
142 	if (asprintf(&nfmt, "%s:%d: %s", file->name, yylval.lineno, fmt) == -1)
143 		err(1, "yyerror asprintf");
144 	err(1, nfmt, ap);
145 	va_end(ap);
146 	free(nfmt);
147 	return (0);
148 }
149 
150 int
151 kw_cmp(const void *k, const void *e)
152 {
153 	return (strcmp(k, ((const struct keywords *)e)->k_name));
154 }
155 
156 int
157 lookup(char *s)
158 {
159 	/* this has to be sorted always */
160 	static const struct keywords keywords[] = {
161 		{ "changer",		CHANGER},
162 		{ "drive",		DRIVE}
163 	};
164 	const struct keywords	*p;
165 
166 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
167 	    sizeof(keywords[0]), kw_cmp);
168 
169 	if (p)
170 		return (p->k_val);
171 	else
172 		return (STRING);
173 }
174 
175 #define MAXPUSHBACK	128
176 
177 char	*parsebuf;
178 int	 parseindex;
179 char	 pushback_buffer[MAXPUSHBACK];
180 int	 pushback_index = 0;
181 
182 int
183 lgetc(int quotec)
184 {
185 	int		c, next;
186 
187 	if (parsebuf) {
188 		/* Read character from the parsebuffer instead of input. */
189 		if (parseindex >= 0) {
190 			c = parsebuf[parseindex++];
191 			if (c != '\0')
192 				return (c);
193 			parsebuf = NULL;
194 		} else
195 			parseindex++;
196 	}
197 
198 	if (pushback_index)
199 		return (pushback_buffer[--pushback_index]);
200 
201 	if (quotec) {
202 		if ((c = getc(file->stream)) == EOF) {
203 			yyerror("reached end of file while parsing "
204 			    "quoted string");
205 			if (file == topfile || popfile() == EOF)
206 				return (EOF);
207 			return (quotec);
208 		}
209 		return (c);
210 	}
211 
212 	while ((c = getc(file->stream)) == '\\') {
213 		next = getc(file->stream);
214 		if (next != '\n') {
215 			c = next;
216 			break;
217 		}
218 		yylval.lineno = file->lineno;
219 		file->lineno++;
220 	}
221 
222 	while (c == EOF) {
223 		if (file == topfile || popfile() == EOF)
224 			return (EOF);
225 		c = getc(file->stream);
226 	}
227 	return (c);
228 }
229 
230 int
231 lungetc(int c)
232 {
233 	if (c == EOF)
234 		return (EOF);
235 	if (parsebuf) {
236 		parseindex--;
237 		if (parseindex >= 0)
238 			return (c);
239 	}
240 	if (pushback_index < MAXPUSHBACK-1)
241 		return (pushback_buffer[pushback_index++] = c);
242 	else
243 		return (EOF);
244 }
245 
246 int
247 findeol(void)
248 {
249 	int	c;
250 
251 	parsebuf = NULL;
252 	pushback_index = 0;
253 
254 	/* skip to either EOF or the first real EOL */
255 	while (1) {
256 		c = lgetc(0);
257 		if (c == '\n') {
258 			file->lineno++;
259 			break;
260 		}
261 		if (c == EOF)
262 			break;
263 	}
264 	return (ERROR);
265 }
266 
267 int
268 yylex(void)
269 {
270 	char	 buf[8096];
271 	char	*p;
272 	int	 quotec, next, c;
273 	int	 token;
274 
275 	p = buf;
276 	while ((c = lgetc(0)) == ' ' || c == '\t')
277 		; /* nothing */
278 
279 	yylval.lineno = file->lineno;
280 	if (c == '#')
281 		while ((c = lgetc(0)) != '\n' && c != EOF)
282 			; /* nothing */
283 
284 	switch (c) {
285 	case '\'':
286 	case '"':
287 		quotec = c;
288 		while (1) {
289 			if ((c = lgetc(quotec)) == EOF)
290 				return (0);
291 			if (c == '\n') {
292 				file->lineno++;
293 				continue;
294 			} else if (c == '\\') {
295 				if ((next = lgetc(quotec)) == EOF)
296 					return (0);
297 				if (next == quotec || c == ' ' || c == '\t')
298 					c = next;
299 				else if (next == '\n')
300 					continue;
301 				else
302 					lungetc(next);
303 			} else if (c == quotec) {
304 				*p = '\0';
305 				break;
306 			}
307 			if (p + 1 >= buf + sizeof(buf) - 1) {
308 				yyerror("string too long");
309 				return (findeol());
310 			}
311 			*p++ = (char)c;
312 		}
313 		yylval.v.string = strdup(buf);
314 		if (yylval.v.string == NULL)
315 			err(1, "yylex: strdup");
316 		return (STRING);
317 	}
318 
319 #define allowed_to_end_number(x) \
320 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
321 
322 	if (c == '-' || isdigit(c)) {
323 		do {
324 			*p++ = c;
325 			if ((unsigned)(p-buf) >= sizeof(buf)) {
326 				yyerror("string too long");
327 				return (findeol());
328 			}
329 		} while ((c = lgetc(0)) != EOF && isdigit(c));
330 		lungetc(c);
331 		if (p == buf + 1 && buf[0] == '-')
332 			goto nodigits;
333 		if (c == EOF || allowed_to_end_number(c)) {
334 			const char *errstr = NULL;
335 
336 			*p = '\0';
337 			yylval.v.number = strtonum(buf, LLONG_MIN,
338 			    LLONG_MAX, &errstr);
339 			if (errstr) {
340 				yyerror("\"%s\" invalid number: %s",
341 				    buf, errstr);
342 				return (findeol());
343 			}
344 			return (NUMBER);
345 		} else {
346 nodigits:
347 			while (p > buf + 1)
348 				lungetc(*--p);
349 			c = *--p;
350 			if (c == '-')
351 				return (c);
352 		}
353 	}
354 
355 #define allowed_in_string(x) \
356 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
357 	x != '{' && x != '}' && x != '<' && x != '>' && \
358 	x != '!' && x != '=' && x != '/' && x != '#' && \
359 	x != ','))
360 
361 	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
362 		do {
363 			*p++ = c;
364 			if ((unsigned)(p-buf) >= sizeof(buf)) {
365 				yyerror("string too long");
366 				return (findeol());
367 			}
368 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
369 		lungetc(c);
370 		*p = '\0';
371 		if ((token = lookup(buf)) == STRING)
372 			if ((yylval.v.string = strdup(buf)) == NULL)
373 				err(1, "yylex: strdup");
374 		return (token);
375 	}
376 	if (c == '\n') {
377 		yylval.lineno = file->lineno;
378 		file->lineno++;
379 	}
380 	if (c == EOF)
381 		return (0);
382 	return (c);
383 }
384 
385 struct file *
386 pushfile(const char *name)
387 {
388 	struct file	*nfile;
389 
390 	if ((nfile = calloc(1, sizeof(struct file))) == NULL)
391 		return (NULL);
392 	if ((nfile->name = strdup(name)) == NULL) {
393 		free(nfile);
394 		return (NULL);
395 	}
396 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
397 		free(nfile->name);
398 		free(nfile);
399 		return (NULL);
400 	}
401 	nfile->lineno = 1;
402 	TAILQ_INSERT_TAIL(&files, nfile, entry);
403 	return (nfile);
404 }
405 
406 int
407 popfile(void)
408 {
409 	struct file	*prev;
410 
411 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
412 		prev->errors += file->errors;
413 
414 	TAILQ_REMOVE(&files, file, entry);
415 	fclose(file->stream);
416 	free(file->name);
417 	free(file);
418 	file = prev;
419 	return (file ? 0 : EOF);
420 }
421 
422 char *
423 parse_tapedev(const char *filename, const char *changer, int drive)
424 {
425 	struct changer	*p;
426 	char		*tapedev = NULL;
427 	int		 errors = 0;
428 
429 	TAILQ_INIT(&changers);
430 
431 	if ((file = pushfile(filename)) == NULL) {
432 		warnx("cannot open the main config file!");
433 		goto guess;
434 	}
435 	topfile = file;
436 
437 	yyparse();
438 	errors = file->errors;
439 	popfile();
440 
441 	TAILQ_FOREACH(p, &changers, entry) {
442 		if (strcmp(basename(changer), p->name) == 0) {
443 			if (drive >= 0 && drive < p->drivecnt) {
444 				if (asprintf(&tapedev, "/dev/%s",
445 				     p->drives[drive]) == -1)
446 					errx(1, "malloc failed");
447 			} else
448 				tapedev = NULL;
449 		}
450 	}
451 
452 guess:
453 	/* if no device found, do the default of /dev/rstX */
454 	if (tapedev == NULL)
455 		if (asprintf(&tapedev, "/dev/rst%d", drive) == -1)
456 			errx(1, "malloc failed");
457 	return (tapedev);
458 }
459 
460 struct changer *
461 new_changer(char *name)
462 {
463 	struct changer	*p;
464 
465 	if ((p = calloc(1, sizeof(*p))) == NULL)
466 		err(1, NULL);
467 
468 	if ((p->name = strdup(name)) == NULL)
469 		err(1, NULL);
470 
471 	return (p);
472 }
473 
474 
475