xref: /openbsd/bin/chio/parse.y (revision 0cf2c9c3)
1 /*	$OpenBSD: parse.y,v 1.10 2007/10/16 20:01:23 mpf 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;
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 quoted string");
204 			if (popfile() == EOF)
205 				return (EOF);
206 			return (quotec);
207 		}
208 		return (c);
209 	}
210 
211 	while ((c = getc(file->stream)) == '\\') {
212 		next = getc(file->stream);
213 		if (next != '\n') {
214 			c = next;
215 			break;
216 		}
217 		yylval.lineno = file->lineno;
218 		file->lineno++;
219 	}
220 	if (c == '\t' || c == ' ') {
221 		/* Compress blanks to a single space. */
222 		do {
223 			c = getc(file->stream);
224 		} while (c == '\t' || c == ' ');
225 		ungetc(c, file->stream);
226 		c = ' ';
227 	}
228 
229 	while (c == EOF) {
230 		if (popfile() == EOF)
231 			return (EOF);
232 		c = getc(file->stream);
233 	}
234 	return (c);
235 }
236 
237 int
238 lungetc(int c)
239 {
240 	if (c == EOF)
241 		return (EOF);
242 	if (parsebuf) {
243 		parseindex--;
244 		if (parseindex >= 0)
245 			return (c);
246 	}
247 	if (pushback_index < MAXPUSHBACK-1)
248 		return (pushback_buffer[pushback_index++] = c);
249 	else
250 		return (EOF);
251 }
252 
253 int
254 findeol(void)
255 {
256 	int	c;
257 
258 	parsebuf = NULL;
259 	pushback_index = 0;
260 
261 	/* skip to either EOF or the first real EOL */
262 	while (1) {
263 		c = lgetc(0);
264 		if (c == '\n') {
265 			file->lineno++;
266 			break;
267 		}
268 		if (c == EOF)
269 			break;
270 	}
271 	return (ERROR);
272 }
273 
274 int
275 yylex(void)
276 {
277 	char	 buf[8096];
278 	char	*p;
279 	int	 quotec, next, c;
280 	int	 token;
281 
282 	p = buf;
283 	while ((c = lgetc(0)) == ' ')
284 		; /* nothing */
285 
286 	yylval.lineno = file->lineno;
287 	if (c == '#')
288 		while ((c = lgetc(0)) != '\n' && c != EOF)
289 			; /* nothing */
290 
291 	switch (c) {
292 	case '\'':
293 	case '"':
294 		quotec = c;
295 		while (1) {
296 			if ((c = lgetc(quotec)) == EOF)
297 				return (0);
298 			if (c == '\n') {
299 				file->lineno++;
300 				continue;
301 			} else if (c == '\\') {
302 				if ((next = lgetc(quotec)) == EOF)
303 					return (0);
304 				if (next == quotec || c == ' ' || c == '\t')
305 					c = next;
306 				else if (next == '\n')
307 					continue;
308 				else
309 					lungetc(next);
310 			} else if (c == quotec) {
311 				*p = '\0';
312 				break;
313 			}
314 			if (p + 1 >= buf + sizeof(buf) - 1) {
315 				yyerror("string too long");
316 				return (findeol());
317 			}
318 			*p++ = (char)c;
319 		}
320 		yylval.v.string = strdup(buf);
321 		if (yylval.v.string == NULL)
322 			err(1, "yylex: strdup");
323 		return (STRING);
324 	}
325 
326 #define allowed_to_end_number(x) \
327 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
328 
329 	if (c == '-' || isdigit(c)) {
330 		do {
331 			*p++ = c;
332 			if ((unsigned)(p-buf) >= sizeof(buf)) {
333 				yyerror("string too long");
334 				return (findeol());
335 			}
336 		} while ((c = lgetc(0)) != EOF && isdigit(c));
337 		lungetc(c);
338 		if (p == buf + 1 && buf[0] == '-')
339 			goto nodigits;
340 		if (c == EOF || allowed_to_end_number(c)) {
341 			const char *errstr = NULL;
342 
343 			*p = '\0';
344 			yylval.v.number = strtonum(buf, LLONG_MIN,
345 			    LLONG_MAX, &errstr);
346 			if (errstr) {
347 				yyerror("\"%s\" invalid number: %s",
348 				    buf, errstr);
349 				return (findeol());
350 			}
351 			return (NUMBER);
352 		} else {
353 nodigits:
354 			while (p > buf + 1)
355 				lungetc(*--p);
356 			c = *--p;
357 			if (c == '-')
358 				return (c);
359 		}
360 	}
361 
362 #define allowed_in_string(x) \
363 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
364 	x != '{' && x != '}' && x != '<' && x != '>' && \
365 	x != '!' && x != '=' && x != '/' && x != '#' && \
366 	x != ','))
367 
368 	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
369 		do {
370 			*p++ = c;
371 			if ((unsigned)(p-buf) >= sizeof(buf)) {
372 				yyerror("string too long");
373 				return (findeol());
374 			}
375 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
376 		lungetc(c);
377 		*p = '\0';
378 		if ((token = lookup(buf)) == STRING)
379 			if ((yylval.v.string = strdup(buf)) == NULL)
380 				err(1, "yylex: strdup");
381 		return (token);
382 	}
383 	if (c == '\n') {
384 		yylval.lineno = file->lineno;
385 		file->lineno++;
386 	}
387 	if (c == EOF)
388 		return (0);
389 	return (c);
390 }
391 
392 struct file *
393 pushfile(const char *name)
394 {
395 	struct file	*nfile;
396 
397 	if ((nfile = calloc(1, sizeof(struct file))) == NULL ||
398 	    (nfile->name = strdup(name)) == NULL)
399 		return (NULL);
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 		TAILQ_REMOVE(&files, file, entry);
418 		fclose(file->stream);
419 		free(file->name);
420 		free(file);
421 		file = prev;
422 		return (0);
423 	}
424 	return (EOF);
425 }
426 
427 char *
428 parse_tapedev(const char *filename, const char *changer, int drive)
429 {
430 	struct changer	*p;
431 	char		*tapedev = NULL;
432 	int		 errors = 0;
433 
434 	TAILQ_INIT(&changers);
435 
436 	if ((file = pushfile(filename)) == NULL) {
437 		warnx("cannot open the main config file!");
438 		goto guess;
439 	}
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