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