xref: /openbsd/bin/chio/parse.y (revision 015d7b4d)
1 /*	$OpenBSD: parse.y,v 1.16 2013/11/25 12:51:10 benno 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 char		*parse_tapedev(const char *, const char *, int);
55 struct changer	*new_changer(char *);
56 
57 struct changer {
58 	TAILQ_ENTRY(changer)	  entry;
59 	char			 *name;
60 	char			**drives;
61 	u_int			  drivecnt;
62 };
63 TAILQ_HEAD(changers, changer)	 changers;
64 struct changer			*curchanger;
65 
66 typedef struct {
67 	union {
68 		int64_t			 number;
69 		char			*string;
70 	} v;
71 	int lineno;
72 } YYSTYPE;
73 
74 %}
75 
76 %token	CHANGER
77 %token	DRIVE
78 %token	ERROR
79 %token	<v.string>		STRING
80 %token	<v.number>		NUMBER
81 %%
82 
83 grammar		: /* empty */
84 		| grammar '\n'
85 		| grammar main '\n'
86 		| grammar error '\n'		{ file->errors++; }
87 		;
88 
89 optnl		: '\n' optnl
90 		|
91 		;
92 
93 nl		: '\n' optnl
94 		;
95 
96 main		: CHANGER STRING optnl '{' optnl {
97 			curchanger = new_changer($2);
98 		}
99 		    changeropts_l '}' {
100 			TAILQ_INSERT_TAIL(&changers, curchanger, entry);
101 			curchanger = NULL;
102 		}
103 		;
104 
105 changeropts_l	: changeropts_l changeroptsl
106 		| changeroptsl
107 		;
108 
109 changeroptsl	: changeropts nl
110 		| error nl
111 		;
112 
113 changeropts	: DRIVE STRING	{
114 			void *newp;
115 
116 			if ((newp = realloc(curchanger->drives,
117 			    (curchanger->drivecnt + 1) *
118 			    sizeof(curchanger->drives))) == NULL)
119 				err(1, NULL);
120 			curchanger->drives = newp;
121 			if ((curchanger->drives[curchanger->drivecnt] =
122 			    strdup($2)) == NULL)
123 				err(1, NULL);
124 			curchanger->drivecnt++;
125 			free($2);
126 		}
127 		;
128 
129 %%
130 
131 struct keywords {
132 	const char	*k_name;
133 	int		 k_val;
134 };
135 
136 int
137 yyerror(const char *fmt, ...)
138 {
139 	va_list		 ap;
140 	char		*nfmt;
141 
142 	file->errors++;
143 	va_start(ap, fmt);
144 	if (asprintf(&nfmt, "%s:%d: %s", file->name, yylval.lineno, fmt) == -1)
145 		err(1, "yyerror asprintf");
146 	err(1, nfmt, ap);
147 	va_end(ap);
148 	free(nfmt);
149 	return (0);
150 }
151 
152 int
153 kw_cmp(const void *k, const void *e)
154 {
155 	return (strcmp(k, ((const struct keywords *)e)->k_name));
156 }
157 
158 int
159 lookup(char *s)
160 {
161 	/* this has to be sorted always */
162 	static const struct keywords keywords[] = {
163 		{ "changer",		CHANGER},
164 		{ "drive",		DRIVE}
165 	};
166 	const struct keywords	*p;
167 
168 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
169 	    sizeof(keywords[0]), kw_cmp);
170 
171 	if (p)
172 		return (p->k_val);
173 	else
174 		return (STRING);
175 }
176 
177 #define MAXPUSHBACK	128
178 
179 u_char	*parsebuf;
180 int	 parseindex;
181 u_char	 pushback_buffer[MAXPUSHBACK];
182 int	 pushback_index = 0;
183 
184 int
185 lgetc(int quotec)
186 {
187 	int		c, next;
188 
189 	if (parsebuf) {
190 		/* Read character from the parsebuffer instead of input. */
191 		if (parseindex >= 0) {
192 			c = parsebuf[parseindex++];
193 			if (c != '\0')
194 				return (c);
195 			parsebuf = NULL;
196 		} else
197 			parseindex++;
198 	}
199 
200 	if (pushback_index)
201 		return (pushback_buffer[--pushback_index]);
202 
203 	if (quotec) {
204 		if ((c = getc(file->stream)) == EOF) {
205 			yyerror("reached end of file while parsing "
206 			    "quoted string");
207 			if (file == topfile || popfile() == EOF)
208 				return (EOF);
209 			return (quotec);
210 		}
211 		return (c);
212 	}
213 
214 	while ((c = getc(file->stream)) == '\\') {
215 		next = getc(file->stream);
216 		if (next != '\n') {
217 			c = next;
218 			break;
219 		}
220 		yylval.lineno = file->lineno;
221 		file->lineno++;
222 	}
223 
224 	while (c == EOF) {
225 		if (file == topfile || popfile() == EOF)
226 			return (EOF);
227 		c = getc(file->stream);
228 	}
229 	return (c);
230 }
231 
232 int
233 lungetc(int c)
234 {
235 	if (c == EOF)
236 		return (EOF);
237 	if (parsebuf) {
238 		parseindex--;
239 		if (parseindex >= 0)
240 			return (c);
241 	}
242 	if (pushback_index < MAXPUSHBACK-1)
243 		return (pushback_buffer[pushback_index++] = c);
244 	else
245 		return (EOF);
246 }
247 
248 int
249 findeol(void)
250 {
251 	int	c;
252 
253 	parsebuf = NULL;
254 	pushback_index = 0;
255 
256 	/* skip to either EOF or the first real EOL */
257 	while (1) {
258 		c = lgetc(0);
259 		if (c == '\n') {
260 			file->lineno++;
261 			break;
262 		}
263 		if (c == EOF)
264 			break;
265 	}
266 	return (ERROR);
267 }
268 
269 int
270 yylex(void)
271 {
272 	u_char	 buf[8096];
273 	u_char	*p;
274 	int	 quotec, next, c;
275 	int	 token;
276 
277 	p = buf;
278 	while ((c = lgetc(0)) == ' ' || c == '\t')
279 		; /* nothing */
280 
281 	yylval.lineno = file->lineno;
282 	if (c == '#')
283 		while ((c = lgetc(0)) != '\n' && c != EOF)
284 			; /* nothing */
285 
286 	switch (c) {
287 	case '\'':
288 	case '"':
289 		quotec = c;
290 		while (1) {
291 			if ((c = lgetc(quotec)) == EOF)
292 				return (0);
293 			if (c == '\n') {
294 				file->lineno++;
295 				continue;
296 			} else if (c == '\\') {
297 				if ((next = lgetc(quotec)) == EOF)
298 					return (0);
299 				if (next == quotec || c == ' ' || c == '\t')
300 					c = next;
301 				else if (next == '\n')
302 					continue;
303 				else
304 					lungetc(next);
305 			} else if (c == quotec) {
306 				*p = '\0';
307 				break;
308 			}
309 			if (p + 1 >= buf + sizeof(buf) - 1) {
310 				yyerror("string too long");
311 				return (findeol());
312 			}
313 			*p++ = c;
314 		}
315 		yylval.v.string = strdup(buf);
316 		if (yylval.v.string == NULL)
317 			err(1, "yylex: strdup");
318 		return (STRING);
319 	}
320 
321 #define allowed_to_end_number(x) \
322 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
323 
324 	if (c == '-' || isdigit(c)) {
325 		do {
326 			*p++ = c;
327 			if ((unsigned)(p-buf) >= sizeof(buf)) {
328 				yyerror("string too long");
329 				return (findeol());
330 			}
331 		} while ((c = lgetc(0)) != EOF && isdigit(c));
332 		lungetc(c);
333 		if (p == buf + 1 && buf[0] == '-')
334 			goto nodigits;
335 		if (c == EOF || allowed_to_end_number(c)) {
336 			const char *errstr = NULL;
337 
338 			*p = '\0';
339 			yylval.v.number = strtonum(buf, LLONG_MIN,
340 			    LLONG_MAX, &errstr);
341 			if (errstr) {
342 				yyerror("\"%s\" invalid number: %s",
343 				    buf, errstr);
344 				return (findeol());
345 			}
346 			return (NUMBER);
347 		} else {
348 nodigits:
349 			while (p > buf + 1)
350 				lungetc(*--p);
351 			c = *--p;
352 			if (c == '-')
353 				return (c);
354 		}
355 	}
356 
357 #define allowed_in_string(x) \
358 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
359 	x != '{' && x != '}' && x != '<' && x != '>' && \
360 	x != '!' && x != '=' && x != '/' && x != '#' && \
361 	x != ','))
362 
363 	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
364 		do {
365 			*p++ = c;
366 			if ((unsigned)(p-buf) >= sizeof(buf)) {
367 				yyerror("string too long");
368 				return (findeol());
369 			}
370 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
371 		lungetc(c);
372 		*p = '\0';
373 		if ((token = lookup(buf)) == STRING)
374 			if ((yylval.v.string = strdup(buf)) == NULL)
375 				err(1, "yylex: strdup");
376 		return (token);
377 	}
378 	if (c == '\n') {
379 		yylval.lineno = file->lineno;
380 		file->lineno++;
381 	}
382 	if (c == EOF)
383 		return (0);
384 	return (c);
385 }
386 
387 struct file *
388 pushfile(const char *name)
389 {
390 	struct file	*nfile;
391 
392 	if ((nfile = calloc(1, sizeof(struct file))) == NULL)
393 		return (NULL);
394 	if ((nfile->name = strdup(name)) == NULL) {
395 		free(nfile);
396 		return (NULL);
397 	}
398 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
399 		free(nfile->name);
400 		free(nfile);
401 		return (NULL);
402 	}
403 	nfile->lineno = 1;
404 	TAILQ_INSERT_TAIL(&files, nfile, entry);
405 	return (nfile);
406 }
407 
408 int
409 popfile(void)
410 {
411 	struct file	*prev;
412 
413 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
414 		prev->errors += file->errors;
415 
416 	TAILQ_REMOVE(&files, file, entry);
417 	fclose(file->stream);
418 	free(file->name);
419 	free(file);
420 	file = prev;
421 	return (file ? 0 : EOF);
422 }
423 
424 char *
425 parse_tapedev(const char *filename, const char *changer, int drive)
426 {
427 	struct changer	*p;
428 	char		*tapedev = NULL;
429 	int		 errors = 0;
430 
431 	TAILQ_INIT(&changers);
432 
433 	if ((file = pushfile(filename)) == NULL) {
434 		warnx("cannot open the main config file!");
435 		goto guess;
436 	}
437 	topfile = file;
438 
439 	yyparse();
440 	errors = file->errors;
441 	popfile();
442 
443 	TAILQ_FOREACH(p, &changers, entry) {
444 		if (strcmp(basename(changer), p->name) == 0) {
445 			if (drive >= 0 && drive < p->drivecnt) {
446 				if (asprintf(&tapedev, "/dev/%s",
447 				     p->drives[drive]) == -1)
448 					errx(1, "malloc failed");
449 			} else
450 				tapedev = NULL;
451 		}
452 	}
453 
454 guess:
455 	/* if no device found, do the default of /dev/rstX */
456 	if (tapedev == NULL)
457 		if (asprintf(&tapedev, "/dev/rst%d", drive) == -1)
458 			errx(1, "malloc failed");
459 	return (tapedev);
460 }
461 
462 struct changer *
463 new_changer(char *name)
464 {
465 	struct changer	*p;
466 
467 	if ((p = calloc(1, sizeof(*p))) == NULL)
468 		err(1, NULL);
469 
470 	if ((p->name = strdup(name)) == NULL)
471 		err(1, NULL);
472 
473 	return (p);
474 }
475 
476 
477