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