xref: /openbsd/usr.sbin/ldomctl/parse.y (revision 91f110e0)
1 /*	$OpenBSD: parse.y,v 1.4 2013/11/25 12:57:18 benno Exp $	*/
2 
3 /*
4  * Copyright (c) 2012 Mark Kettenis <kettenis@openbsd.org>
5  * Copyright (c) 2002, 2003, 2004 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 <net/if.h>
29 #include <netinet/in.h>
30 #include <netinet/if_ether.h>
31 
32 #include <ctype.h>
33 #include <err.h>
34 #include <errno.h>
35 #include <limits.h>
36 #include <stdarg.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 
41 #include "ldomctl.h"
42 #include "util.h"
43 
44 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
45 static struct file {
46 	TAILQ_ENTRY(file)	 entry;
47 	FILE			*stream;
48 	char			*name;
49 	int			 lineno;
50 	int			 errors;
51 } *file, *topfile;
52 struct file	*pushfile(const char *);
53 int		 popfile(void);
54 int		 yyparse(void);
55 int		 yylex(void);
56 int		 yyerror(const char *, ...);
57 int		 kw_cmp(const void *, const void *);
58 int		 lookup(char *);
59 int		 lgetc(int);
60 int		 lungetc(int);
61 int		 findeol(void);
62 
63 struct ldom_config		*conf;
64 
65 struct opts {
66 	uint64_t	mac_addr;
67 	uint64_t	mtu;
68 } opts;
69 void		opts_default(void);
70 
71 typedef struct {
72 	union {
73 		int64_t			 number;
74 		char			*string;
75 		struct opts		 opts;
76 	} v;
77 	int lineno;
78 } YYSTYPE;
79 
80 %}
81 
82 %token	DOMAIN
83 %token	VCPU MEMORY VDISK VNET
84 %token	MAC_ADDR MTU
85 %token	ERROR
86 %token	<v.string>		STRING
87 %token	<v.number>		NUMBER
88 %type	<v.number>		memory
89 %type	<v.opts>		vnet_opts vnet_opts_l vnet_opt
90 %type	<v.opts>		mac_addr
91 %type	<v.opts>		mtu
92 %%
93 
94 grammar		: /* empty */
95 		| grammar '\n'
96 		| grammar domain '\n'
97 		| grammar error '\n'		{ file->errors++; }
98 		;
99 
100 domain		: DOMAIN STRING optnl '{' optnl	{
101 			domain = xzalloc(sizeof(struct domain));
102 			domain->name = $2;
103 			SIMPLEQ_INIT(&domain->vdisk_list);
104 			SIMPLEQ_INIT(&domain->vnet_list);
105 		}
106 		    domainopts_l '}' {
107 			/* domain names need to be unique. */
108 			struct domain *odomain;
109 			SIMPLEQ_FOREACH(odomain, &conf->domain_list, entry)
110 				if (strcmp(odomain->name, $2) == 0) {
111 					yyerror("duplicate domain name: %s", $2);
112 					YYERROR;
113 				}
114 			SIMPLEQ_INSERT_TAIL(&conf->domain_list, domain, entry);
115 			domain = NULL;
116 		}
117 		;
118 
119 domainopts_l	: domainopts_l domainoptsl
120 		| domainoptsl
121 		;
122 
123 domainoptsl	: domainopts nl
124 		;
125 
126 domainopts	: VCPU NUMBER {
127 			domain->vcpu = $2;
128 		}
129 		| MEMORY memory {
130 			domain->memory = $2;
131 		}
132 		| VDISK STRING {
133 			struct vdisk *vdisk = xmalloc(sizeof(struct vdisk));
134 			vdisk->path = $2;
135 			SIMPLEQ_INSERT_TAIL(&domain->vdisk_list, vdisk, entry);
136 		}
137 		| VNET vnet_opts {
138 			struct vnet *vnet = xmalloc(sizeof(struct vnet));
139 			vnet->mac_addr = $2.mac_addr;
140 			vnet->mtu = $2.mtu;
141 			SIMPLEQ_INSERT_TAIL(&domain->vnet_list, vnet, entry);
142 		}
143 		;
144 
145 vnet_opts	:	{ opts_default(); }
146 		  vnet_opts_l
147 			{ $$ = opts; }
148 		|	{ opts_default(); $$ = opts; }
149 		;
150 vnet_opts_l	: vnet_opts_l vnet_opt
151 		| vnet_opt
152 		;
153 vnet_opt	: mac_addr
154 		| mtu
155 		;
156 
157 mac_addr	: MAC_ADDR '=' STRING {
158 			struct ether_addr *ea;
159 
160 			if ((ea = ether_aton($3)) == NULL) {
161 				yyerror("invalid address: %s", $3);
162 				YYERROR;
163 			}
164 
165 			opts.mac_addr =
166 			    (uint64_t)ea->ether_addr_octet[0] << 40 |
167 			    (uint64_t)ea->ether_addr_octet[1] << 32 |
168 			    ea->ether_addr_octet[2] << 24 |
169 			    ea->ether_addr_octet[3] << 16 |
170 			    ea->ether_addr_octet[4] << 8 |
171 			    ea->ether_addr_octet[5];
172 		}
173 		;
174 
175 mtu		: MTU '=' NUMBER {
176 			opts.mtu = $3;
177 		}
178 		;
179 
180 memory		: NUMBER {
181 			$$ = $1;
182 		}
183 		| STRING {
184 			uint64_t size;
185 			char *cp;
186 
187 			size = strtoll($1, &cp, 10);
188 			if (cp != NULL) {
189 				if (strcmp(cp, "K") == 0)
190 					size *= 1024;
191 				else if (strcmp(cp, "M") == 0)
192 					size *= 1024 * 1024;
193 				else if (strcmp(cp, "G") == 0)
194 					size *= 1024 * 1024 * 1024;
195 				else {
196                                         yyerror("unknown unit %s", cp);
197                                         YYERROR;
198 				}
199 			}
200 			$$ = size;
201 		}
202 		;
203 
204 optnl		: '\n' optnl
205 		|
206 		;
207 
208 nl		: '\n' optnl		/* one newline or more */
209 		;
210 
211 %%
212 
213 void
214 opts_default(void)
215 {
216 	opts.mac_addr = -1;
217 	opts.mtu = 1500;
218 }
219 
220 struct keywords {
221 	const char	*k_name;
222 	int		 k_val;
223 };
224 
225 int
226 yyerror(const char *fmt, ...)
227 {
228 	va_list		 ap;
229 
230 	file->errors++;
231 	va_start(ap, fmt);
232 	fprintf(stderr, "%s:%d ", file->name, yylval.lineno);
233 	vfprintf(stderr, fmt, ap);
234 	fprintf(stderr, "\n");
235 	va_end(ap);
236 	return (0);
237 }
238 
239 int
240 kw_cmp(const void *k, const void *e)
241 {
242 	return (strcmp(k, ((const struct keywords *)e)->k_name));
243 }
244 
245 int
246 lookup(char *s)
247 {
248 	/* this has to be sorted always */
249 	static const struct keywords keywords[] = {
250 		{ "domain",		DOMAIN},
251 		{ "mac-addr",		MAC_ADDR},
252 		{ "memory",		MEMORY},
253 		{ "mtu",		MTU},
254 		{ "vcpu",		VCPU},
255 		{ "vdisk",		VDISK},
256 		{ "vnet",		VNET}
257 	};
258 	const struct keywords	*p;
259 
260 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
261 	    sizeof(keywords[0]), kw_cmp);
262 
263 	if (p)
264 		return (p->k_val);
265 	else
266 		return (STRING);
267 }
268 
269 #define MAXPUSHBACK	128
270 
271 u_char	*parsebuf;
272 int	 parseindex;
273 u_char	 pushback_buffer[MAXPUSHBACK];
274 int	 pushback_index = 0;
275 
276 int
277 lgetc(int quotec)
278 {
279 	int		c, next;
280 
281 	if (parsebuf) {
282 		/* Read character from the parsebuffer instead of input. */
283 		if (parseindex >= 0) {
284 			c = parsebuf[parseindex++];
285 			if (c != '\0')
286 				return (c);
287 			parsebuf = NULL;
288 		} else
289 			parseindex++;
290 	}
291 
292 	if (pushback_index)
293 		return (pushback_buffer[--pushback_index]);
294 
295 	if (quotec) {
296 		if ((c = getc(file->stream)) == EOF) {
297 			yyerror("reached end of file while parsing "
298 			    "quoted string");
299 			if (file == topfile || popfile() == EOF)
300 				return (EOF);
301 			return (quotec);
302 		}
303 		return (c);
304 	}
305 
306 	while ((c = getc(file->stream)) == '\\') {
307 		next = getc(file->stream);
308 		if (next != '\n') {
309 			c = next;
310 			break;
311 		}
312 		yylval.lineno = file->lineno;
313 		file->lineno++;
314 	}
315 
316 	while (c == EOF) {
317 		if (file == topfile || popfile() == EOF)
318 			return (EOF);
319 		c = getc(file->stream);
320 	}
321 	return (c);
322 }
323 
324 int
325 lungetc(int c)
326 {
327 	if (c == EOF)
328 		return (EOF);
329 	if (parsebuf) {
330 		parseindex--;
331 		if (parseindex >= 0)
332 			return (c);
333 	}
334 	if (pushback_index < MAXPUSHBACK-1)
335 		return (pushback_buffer[pushback_index++] = c);
336 	else
337 		return (EOF);
338 }
339 
340 int
341 findeol(void)
342 {
343 	int	c;
344 
345 	parsebuf = NULL;
346 
347 	/* skip to either EOF or the first real EOL */
348 	while (1) {
349 		if (pushback_index)
350 			c = pushback_buffer[--pushback_index];
351 		else
352 			c = lgetc(0);
353 		if (c == '\n') {
354 			file->lineno++;
355 			break;
356 		}
357 		if (c == EOF)
358 			break;
359 	}
360 	return (ERROR);
361 }
362 
363 int
364 yylex(void)
365 {
366 	u_char	 buf[8096];
367 	u_char	*p;
368 	int	 quotec, next, c;
369 	int	 token;
370 
371 	p = buf;
372 	while ((c = lgetc(0)) == ' ' || c == '\t')
373 		; /* nothing */
374 
375 	yylval.lineno = file->lineno;
376 	if (c == '#')
377 		while ((c = lgetc(0)) != '\n' && c != EOF)
378 			; /* nothing */
379 
380 	switch (c) {
381 	case '\'':
382 	case '"':
383 		quotec = c;
384 		while (1) {
385 			if ((c = lgetc(quotec)) == EOF)
386 				return (0);
387 			if (c == '\n') {
388 				file->lineno++;
389 				continue;
390 			} else if (c == '\\') {
391 				if ((next = lgetc(quotec)) == EOF)
392 					return (0);
393 				if (next == quotec || c == ' ' || c == '\t')
394 					c = next;
395 				else if (next == '\n') {
396 					file->lineno++;
397 					continue;
398 				} else
399 					lungetc(next);
400 			} else if (c == quotec) {
401 				*p = '\0';
402 				break;
403 			}
404 			if (p + 1 >= buf + sizeof(buf) - 1) {
405 				yyerror("string too long");
406 				return (findeol());
407 			}
408 			*p++ = c;
409 		}
410 		yylval.v.string = xstrdup(buf);
411 		return (STRING);
412 	}
413 
414 #define allowed_to_end_number(x) \
415 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
416 
417 	if (c == '-' || isdigit(c)) {
418 		do {
419 			*p++ = c;
420 			if ((unsigned)(p-buf) >= sizeof(buf)) {
421 				yyerror("string too long");
422 				return (findeol());
423 			}
424 		} while ((c = lgetc(0)) != EOF && isdigit(c));
425 		lungetc(c);
426 		if (p == buf + 1 && buf[0] == '-')
427 			goto nodigits;
428 		if (c == EOF || allowed_to_end_number(c)) {
429 			const char *errstr = NULL;
430 
431 			*p = '\0';
432 			yylval.v.number = strtonum(buf, LLONG_MIN,
433 			    LLONG_MAX, &errstr);
434 			if (errstr) {
435 				yyerror("\"%s\" invalid number: %s",
436 				    buf, errstr);
437 				return (findeol());
438 			}
439 			return (NUMBER);
440 		} else {
441 nodigits:
442 			while (p > buf + 1)
443 				lungetc(*--p);
444 			c = *--p;
445 			if (c == '-')
446 				return (c);
447 		}
448 	}
449 
450 #define allowed_in_string(x) \
451 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
452 	x != '{' && x != '}' && x != '<' && x != '>' && \
453 	x != '!' && x != '=' && x != '/' && x != '#' && \
454 	x != ','))
455 
456 	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
457 		do {
458 			*p++ = c;
459 			if ((unsigned)(p-buf) >= sizeof(buf)) {
460 				yyerror("string too long");
461 				return (findeol());
462 			}
463 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
464 		lungetc(c);
465 		*p = '\0';
466 		if ((token = lookup(buf)) == STRING)
467 			yylval.v.string = xstrdup(buf);
468 		return (token);
469 	}
470 	if (c == '\n') {
471 		yylval.lineno = file->lineno;
472 		file->lineno++;
473 	}
474 	if (c == EOF)
475 		return (0);
476 	return (c);
477 }
478 
479 struct file *
480 pushfile(const char *name)
481 {
482 	struct file	*nfile;
483 
484 	nfile = xzalloc(sizeof(struct file));
485 	nfile->name = xstrdup(name);
486 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
487 		warn("%s", nfile->name);
488 		free(nfile->name);
489 		free(nfile);
490 		return (NULL);
491 	}
492 	nfile->lineno = 1;
493 	TAILQ_INSERT_TAIL(&files, nfile, entry);
494 	return (nfile);
495 }
496 
497 int
498 popfile(void)
499 {
500 	struct file	*prev;
501 
502 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
503 		prev->errors += file->errors;
504 
505 	TAILQ_REMOVE(&files, file, entry);
506 	fclose(file->stream);
507 	free(file->name);
508 	free(file);
509 	file = prev;
510 	return (file ? 0 : EOF);
511 }
512 
513 int
514 parse_config(const char *filename, struct ldom_config *xconf)
515 {
516 	int		 errors = 0;
517 
518 	conf = xconf;
519 
520 	if ((file = pushfile(filename)) == NULL) {
521 		return (-1);
522 	}
523 	topfile = file;
524 
525 	yyparse();
526 	errors = file->errors;
527 	popfile();
528 
529 	return (errors ? -1 : 0);
530 }
531