xref: /openbsd/bin/chio/parse.y (revision 2053f12a)
1 /*	$OpenBSD: parse.y,v 1.11 2007/11/12 23:59:40 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 
221 	while (c == EOF) {
222 		if (popfile() == EOF)
223 			return (EOF);
224 		c = getc(file->stream);
225 	}
226 	return (c);
227 }
228 
229 int
230 lungetc(int c)
231 {
232 	if (c == EOF)
233 		return (EOF);
234 	if (parsebuf) {
235 		parseindex--;
236 		if (parseindex >= 0)
237 			return (c);
238 	}
239 	if (pushback_index < MAXPUSHBACK-1)
240 		return (pushback_buffer[pushback_index++] = c);
241 	else
242 		return (EOF);
243 }
244 
245 int
246 findeol(void)
247 {
248 	int	c;
249 
250 	parsebuf = NULL;
251 	pushback_index = 0;
252 
253 	/* skip to either EOF or the first real EOL */
254 	while (1) {
255 		c = lgetc(0);
256 		if (c == '\n') {
257 			file->lineno++;
258 			break;
259 		}
260 		if (c == EOF)
261 			break;
262 	}
263 	return (ERROR);
264 }
265 
266 int
267 yylex(void)
268 {
269 	char	 buf[8096];
270 	char	*p;
271 	int	 quotec, next, c;
272 	int	 token;
273 
274 	p = buf;
275 	while ((c = lgetc(0)) == ' ' || c == '\t')
276 		; /* nothing */
277 
278 	yylval.lineno = file->lineno;
279 	if (c == '#')
280 		while ((c = lgetc(0)) != '\n' && c != EOF)
281 			; /* nothing */
282 
283 	switch (c) {
284 	case '\'':
285 	case '"':
286 		quotec = c;
287 		while (1) {
288 			if ((c = lgetc(quotec)) == EOF)
289 				return (0);
290 			if (c == '\n') {
291 				file->lineno++;
292 				continue;
293 			} else if (c == '\\') {
294 				if ((next = lgetc(quotec)) == EOF)
295 					return (0);
296 				if (next == quotec || c == ' ' || c == '\t')
297 					c = next;
298 				else if (next == '\n')
299 					continue;
300 				else
301 					lungetc(next);
302 			} else if (c == quotec) {
303 				*p = '\0';
304 				break;
305 			}
306 			if (p + 1 >= buf + sizeof(buf) - 1) {
307 				yyerror("string too long");
308 				return (findeol());
309 			}
310 			*p++ = (char)c;
311 		}
312 		yylval.v.string = strdup(buf);
313 		if (yylval.v.string == NULL)
314 			err(1, "yylex: strdup");
315 		return (STRING);
316 	}
317 
318 #define allowed_to_end_number(x) \
319 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
320 
321 	if (c == '-' || isdigit(c)) {
322 		do {
323 			*p++ = c;
324 			if ((unsigned)(p-buf) >= sizeof(buf)) {
325 				yyerror("string too long");
326 				return (findeol());
327 			}
328 		} while ((c = lgetc(0)) != EOF && isdigit(c));
329 		lungetc(c);
330 		if (p == buf + 1 && buf[0] == '-')
331 			goto nodigits;
332 		if (c == EOF || allowed_to_end_number(c)) {
333 			const char *errstr = NULL;
334 
335 			*p = '\0';
336 			yylval.v.number = strtonum(buf, LLONG_MIN,
337 			    LLONG_MAX, &errstr);
338 			if (errstr) {
339 				yyerror("\"%s\" invalid number: %s",
340 				    buf, errstr);
341 				return (findeol());
342 			}
343 			return (NUMBER);
344 		} else {
345 nodigits:
346 			while (p > buf + 1)
347 				lungetc(*--p);
348 			c = *--p;
349 			if (c == '-')
350 				return (c);
351 		}
352 	}
353 
354 #define allowed_in_string(x) \
355 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
356 	x != '{' && x != '}' && x != '<' && x != '>' && \
357 	x != '!' && x != '=' && x != '/' && x != '#' && \
358 	x != ','))
359 
360 	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
361 		do {
362 			*p++ = c;
363 			if ((unsigned)(p-buf) >= sizeof(buf)) {
364 				yyerror("string too long");
365 				return (findeol());
366 			}
367 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
368 		lungetc(c);
369 		*p = '\0';
370 		if ((token = lookup(buf)) == STRING)
371 			if ((yylval.v.string = strdup(buf)) == NULL)
372 				err(1, "yylex: strdup");
373 		return (token);
374 	}
375 	if (c == '\n') {
376 		yylval.lineno = file->lineno;
377 		file->lineno++;
378 	}
379 	if (c == EOF)
380 		return (0);
381 	return (c);
382 }
383 
384 struct file *
385 pushfile(const char *name)
386 {
387 	struct file	*nfile;
388 
389 	if ((nfile = calloc(1, sizeof(struct file))) == NULL ||
390 	    (nfile->name = strdup(name)) == NULL)
391 		return (NULL);
392 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
393 		free(nfile->name);
394 		free(nfile);
395 		return (NULL);
396 	}
397 	nfile->lineno = 1;
398 	TAILQ_INSERT_TAIL(&files, nfile, entry);
399 	return (nfile);
400 }
401 
402 int
403 popfile(void)
404 {
405 	struct file	*prev;
406 
407 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL) {
408 		prev->errors += file->errors;
409 		TAILQ_REMOVE(&files, file, entry);
410 		fclose(file->stream);
411 		free(file->name);
412 		free(file);
413 		file = prev;
414 		return (0);
415 	}
416 	return (EOF);
417 }
418 
419 char *
420 parse_tapedev(const char *filename, const char *changer, int drive)
421 {
422 	struct changer	*p;
423 	char		*tapedev = NULL;
424 	int		 errors = 0;
425 
426 	TAILQ_INIT(&changers);
427 
428 	if ((file = pushfile(filename)) == NULL) {
429 		warnx("cannot open the main config file!");
430 		goto guess;
431 	}
432 
433 	yyparse();
434 	errors = file->errors;
435 	popfile();
436 
437 	TAILQ_FOREACH(p, &changers, entry) {
438 		if (strcmp(basename(changer), p->name) == 0) {
439 			if (drive >= 0 && drive < p->drivecnt) {
440 				if (asprintf(&tapedev, "/dev/%s",
441 				     p->drives[drive]) == -1)
442 					errx(1, "malloc failed");
443 			} else
444 				tapedev = NULL;
445 		}
446 	}
447 
448 guess:
449 	/* if no device found, do the default of /dev/rstX */
450 	if (tapedev == NULL)
451 		if (asprintf(&tapedev, "/dev/rst%d", drive) == -1)
452 			errx(1, "malloc failed");
453 	return (tapedev);
454 }
455 
456 struct changer *
457 new_changer(char *name)
458 {
459 	struct changer	*p;
460 
461 	if ((p = calloc(1, sizeof(*p))) == NULL)
462 		err(1, NULL);
463 
464 	if ((p->name = strdup(name)) == NULL)
465 		err(1, NULL);
466 
467 	return (p);
468 }
469 
470 
471