xref: /openbsd-src/usr.sbin/ldomctl/parse.y (revision ee96180de911a25bf20a6f878c381b9ba950e865)
1 /*	$OpenBSD: parse.y,v 1.25 2022/10/06 21:35:52 kn 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 #include <util.h>
41 
42 #include "ldomctl.h"
43 #include "ldom_util.h"
44 
45 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
46 static struct file {
47 	TAILQ_ENTRY(file)	 entry;
48 	FILE			*stream;
49 	char			*name;
50 	int			 lineno;
51 	int			 errors;
52 } *file, *topfile;
53 struct file	*pushfile(const char *);
54 int		 popfile(void);
55 int		 yyparse(void);
56 int		 yylex(void);
57 int		 yyerror(const char *, ...)
58     __attribute__((__format__ (printf, 1, 2)))
59     __attribute__((__nonnull__ (1)));
60 int		 kw_cmp(const void *, const void *);
61 int		 lookup(char *);
62 int		 lgetc(int);
63 int		 lungetc(int);
64 int		 findeol(void);
65 
66 struct ldom_config		*conf;
67 struct domain			*domain;
68 
69 struct vcpu_opts {
70 	uint64_t	count;
71 	uint64_t	stride;
72 } vcpu_opts;
73 
74 struct vdisk_opts {
75 	const char	*devalias;
76 } vdisk_opts;
77 
78 struct vnet_opts {
79 	uint64_t	mac_addr;
80 	uint64_t	mtu;
81 	const char	*devalias;
82 } vnet_opts;
83 
84 void		vcpu_opts_default(void);
85 void		vdisk_opts_default(void);
86 void		vnet_opts_default(void);
87 
88 typedef struct {
89 	union {
90 		int64_t			 number;
91 		char			*string;
92 		struct vcpu_opts	 vcpu_opts;
93 		struct vdisk_opts	 vdisk_opts;
94 		struct vnet_opts	 vnet_opts;
95 	} v;
96 	int lineno;
97 } YYSTYPE;
98 
99 %}
100 
101 %token	DOMAIN
102 %token	VCPU MEMORY VDISK DEVALIAS VNET VARIABLE IODEVICE
103 %token	MAC_ADDR MTU
104 %token	ERROR
105 %token	<v.string>		STRING
106 %token	<v.number>		NUMBER
107 %type	<v.number>		memory
108 %type	<v.vcpu_opts>		vcpu
109 %type	<v.vdisk_opts>		vdisk_opts vdisk_opts_l vdisk_opt
110 %type	<v.vdisk_opts>		vdisk_devalias
111 %type	<v.vnet_opts>		vnet_opts vnet_opts_l vnet_opt
112 %type	<v.vnet_opts>		mac_addr
113 %type	<v.vnet_opts>		mtu
114 %type	<v.vnet_opts>		vnet_devalias
115 %%
116 
117 grammar		: /* empty */
118 		| grammar '\n'
119 		| grammar domain '\n'
120 		| grammar error '\n'		{ file->errors++; }
121 		;
122 
123 domain		: DOMAIN STRING optnl '{' optnl	{
124 			struct domain *odomain;
125 			SIMPLEQ_FOREACH(odomain, &conf->domain_list, entry)
126 				if (strcmp(odomain->name, $2) == 0) {
127 					yyerror("duplicate domain name: %s", $2);
128 					YYERROR;
129 				}
130 			domain = xzalloc(sizeof(struct domain));
131 			domain->name = $2;
132 			SIMPLEQ_INIT(&domain->vdisk_list);
133 			SIMPLEQ_INIT(&domain->vnet_list);
134 			SIMPLEQ_INIT(&domain->var_list);
135 			SIMPLEQ_INIT(&domain->iodev_list);
136 		}
137 		    domainopts_l '}' {
138 			if (strcmp(domain->name, "primary") != 0) {
139 				if (domain->vcpu == 0) {
140 					yyerror("vcpu is required: %s",
141 					    domain->name);
142 					YYERROR;
143 				}
144 				if ( domain->memory == 0) {
145 					yyerror("memory is required: %s",
146 					    domain->name);
147 					YYERROR;
148 				}
149 				if (SIMPLEQ_EMPTY(&domain->vdisk_list) &&
150 				    SIMPLEQ_EMPTY(&domain->vnet_list) &&
151 				    SIMPLEQ_EMPTY(&domain->iodev_list)) {
152 					yyerror("at least one bootable device"
153 					    " is required: %s", domain->name);
154 					YYERROR;
155 				}
156 			}
157 			SIMPLEQ_INSERT_TAIL(&conf->domain_list, domain, entry);
158 			domain = NULL;
159 		}
160 		;
161 
162 domainopts_l	: domainopts_l domainoptsl
163 		| domainoptsl
164 		;
165 
166 domainoptsl	: domainopts nl
167 		;
168 
169 domainopts	: VCPU vcpu {
170 			if (domain->vcpu) {
171 				yyerror("duplicate vcpu option");
172 				YYERROR;
173 			}
174 			domain->vcpu = $2.count;
175 			domain->vcpu_stride = $2.stride;
176 		}
177 		| MEMORY memory {
178 			if (domain->memory) {
179 				yyerror("duplicate memory option");
180 				YYERROR;
181 			}
182 			domain->memory = $2;
183 		}
184 		| VDISK STRING vdisk_opts {
185 			if (strcmp(domain->name, "primary") == 0) {
186 				yyerror("vdisk option invalid for primary"
187 				    " domain");
188 				YYERROR;
189 			}
190 			struct vdisk *vdisk = xmalloc(sizeof(struct vdisk));
191 			vdisk->path = $2;
192 			vdisk->devalias = $3.devalias;
193 			SIMPLEQ_INSERT_TAIL(&domain->vdisk_list, vdisk, entry);
194 		}
195 		| VNET vnet_opts {
196 			if (strcmp(domain->name, "primary") == 0) {
197 				yyerror("vnet option invalid for primary"
198 				    " domain");
199 				YYERROR;
200 			}
201 			struct vnet *vnet = xmalloc(sizeof(struct vnet));
202 			vnet->mac_addr = $2.mac_addr;
203 			vnet->mtu = $2.mtu;
204 			vnet->devalias = $2.devalias;
205 			SIMPLEQ_INSERT_TAIL(&domain->vnet_list, vnet, entry);
206 		}
207 		| VARIABLE STRING '=' STRING {
208 			struct var *var = xmalloc(sizeof(struct var));
209 			var->name = $2;
210 			var->str = $4;
211 			SIMPLEQ_INSERT_TAIL(&domain->var_list, var, entry);
212 		}
213 		| IODEVICE STRING {
214 			if (strcmp(domain->name, "primary") == 0) {
215 				yyerror("iodevice option invalid for primary"
216 				    " domain");
217 				YYERROR;
218 			}
219 			struct domain *odomain;
220 			struct iodev *iodev;
221 			SIMPLEQ_FOREACH(odomain, &conf->domain_list, entry)
222 				SIMPLEQ_FOREACH(iodev, &odomain->iodev_list, entry)
223 					if (strcmp(iodev->dev, $2) == 0) {
224 						yyerror("iodevice %s already"
225 						    " assigned", $2);
226 						YYERROR;
227 					}
228 			iodev = xmalloc(sizeof(struct iodev));
229 			iodev->dev = $2;
230 			SIMPLEQ_INSERT_TAIL(&domain->iodev_list, iodev, entry);
231 		}
232 		;
233 
234 vdisk_opts	:	{ vdisk_opts_default(); }
235 		  vdisk_opts_l
236 			{ $$ = vdisk_opts; }
237 		|	{ vdisk_opts_default(); $$ = vdisk_opts; }
238 		;
239 vdisk_opts_l	: vdisk_opts_l vdisk_opt
240 		| vdisk_opt
241 		;
242 vdisk_opt	: vdisk_devalias
243 		;
244 
245 vdisk_devalias	: DEVALIAS '=' STRING {
246 			vdisk_opts.devalias = $3;
247 		}
248 		;
249 
250 vnet_opts	:	{ vnet_opts_default(); }
251 		  vnet_opts_l
252 			{ $$ = vnet_opts; }
253 		|	{ vnet_opts_default(); $$ = vnet_opts; }
254 		;
255 vnet_opts_l	: vnet_opts_l vnet_opt
256 		| vnet_opt
257 		;
258 vnet_opt	: mac_addr
259 		| mtu
260 		| vnet_devalias
261 		;
262 
263 mac_addr	: MAC_ADDR '=' STRING {
264 			struct ether_addr *ea;
265 
266 			if ((ea = ether_aton($3)) == NULL) {
267 				yyerror("invalid address: %s", $3);
268 				YYERROR;
269 			}
270 
271 			vnet_opts.mac_addr =
272 			    (uint64_t)ea->ether_addr_octet[0] << 40 |
273 			    (uint64_t)ea->ether_addr_octet[1] << 32 |
274 			    ea->ether_addr_octet[2] << 24 |
275 			    ea->ether_addr_octet[3] << 16 |
276 			    ea->ether_addr_octet[4] << 8 |
277 			    ea->ether_addr_octet[5];
278 		}
279 		;
280 
281 mtu		: MTU '=' NUMBER {
282 			vnet_opts.mtu = $3;
283 		}
284 		;
285 
286 vnet_devalias	: DEVALIAS '=' STRING {
287 			vnet_opts.devalias = $3;
288 		}
289 		;
290 
291 vcpu		: STRING {
292 			const char *errstr;
293 			char *colon;
294 
295 			vcpu_opts_default();
296 			colon = strchr($1, ':');
297 			if (colon == NULL) {
298 				yyerror("bogus stride in %s", $1);
299 				YYERROR;
300 			}
301 			*colon++ = '\0';
302 			vcpu_opts.count = strtonum($1, 0, INT_MAX, &errstr);
303 			if (errstr) {
304 				yyerror("number %s is %s", $1, errstr);
305 				YYERROR;
306 			}
307 			vcpu_opts.stride = strtonum(colon, 0, INT_MAX, &errstr);
308 			if (errstr) {
309 				yyerror("number %s is %s", colon, errstr);
310 				YYERROR;
311 			}
312 			$$ = vcpu_opts;
313 		}
314 		| NUMBER {
315 			vcpu_opts_default();
316 			vcpu_opts.count = $1;
317 			$$ = vcpu_opts;
318 		}
319 		;
320 
321 memory		: NUMBER {
322 			$$ = $1;
323 		}
324 		| STRING {
325 			if (scan_scaled($1, &$$) == -1) {
326 				yyerror("invalid size: %s", $1);
327 				YYERROR;
328 			}
329 		}
330 		;
331 
332 optnl		: '\n' optnl
333 		|
334 		;
335 
336 nl		: '\n' optnl		/* one newline or more */
337 		;
338 
339 %%
340 
341 void
342 vcpu_opts_default(void)
343 {
344 	vcpu_opts.count = -1;
345 	vcpu_opts.stride = 1;
346 }
347 
348 void
vdisk_opts_default(void)349 vdisk_opts_default(void)
350 {
351 	vdisk_opts.devalias = NULL;
352 }
353 
354 void
vnet_opts_default(void)355 vnet_opts_default(void)
356 {
357 	vnet_opts.mac_addr = -1;
358 	vnet_opts.mtu = 1500;
359 	vnet_opts.devalias = NULL;
360 }
361 
362 struct keywords {
363 	const char	*k_name;
364 	int		 k_val;
365 };
366 
367 int
yyerror(const char * fmt,...)368 yyerror(const char *fmt, ...)
369 {
370 	va_list		 ap;
371 
372 	file->errors++;
373 	va_start(ap, fmt);
374 	fprintf(stderr, "%s:%d ", file->name, yylval.lineno);
375 	vfprintf(stderr, fmt, ap);
376 	fprintf(stderr, "\n");
377 	va_end(ap);
378 	return (0);
379 }
380 
381 int
kw_cmp(const void * k,const void * e)382 kw_cmp(const void *k, const void *e)
383 {
384 	return (strcmp(k, ((const struct keywords *)e)->k_name));
385 }
386 
387 int
lookup(char * s)388 lookup(char *s)
389 {
390 	/* this has to be sorted always */
391 	static const struct keywords keywords[] = {
392 		{ "devalias",		DEVALIAS},
393 		{ "domain",		DOMAIN},
394 		{ "iodevice",		IODEVICE},
395 		{ "mac-addr",		MAC_ADDR},
396 		{ "memory",		MEMORY},
397 		{ "mtu",		MTU},
398 		{ "variable",		VARIABLE},
399 		{ "vcpu",		VCPU},
400 		{ "vdisk",		VDISK},
401 		{ "vnet",		VNET}
402 	};
403 	const struct keywords	*p;
404 
405 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
406 	    sizeof(keywords[0]), kw_cmp);
407 
408 	if (p)
409 		return (p->k_val);
410 	else
411 		return (STRING);
412 }
413 
414 #define MAXPUSHBACK	128
415 
416 char	*parsebuf;
417 int	 parseindex;
418 char	 pushback_buffer[MAXPUSHBACK];
419 int	 pushback_index = 0;
420 
421 int
lgetc(int quotec)422 lgetc(int quotec)
423 {
424 	int		c, next;
425 
426 	if (parsebuf) {
427 		/* Read character from the parsebuffer instead of input. */
428 		if (parseindex >= 0) {
429 			c = (unsigned char)parsebuf[parseindex++];
430 			if (c != '\0')
431 				return (c);
432 			parsebuf = NULL;
433 		} else
434 			parseindex++;
435 	}
436 
437 	if (pushback_index)
438 		return ((unsigned char)pushback_buffer[--pushback_index]);
439 
440 	if (quotec) {
441 		if ((c = getc(file->stream)) == EOF) {
442 			yyerror("reached end of file while parsing "
443 			    "quoted string");
444 			if (file == topfile || popfile() == EOF)
445 				return (EOF);
446 			return (quotec);
447 		}
448 		return (c);
449 	}
450 
451 	while ((c = getc(file->stream)) == '\\') {
452 		next = getc(file->stream);
453 		if (next != '\n') {
454 			c = next;
455 			break;
456 		}
457 		yylval.lineno = file->lineno;
458 		file->lineno++;
459 	}
460 
461 	while (c == EOF) {
462 		if (file == topfile || popfile() == EOF)
463 			return (EOF);
464 		c = getc(file->stream);
465 	}
466 	return (c);
467 }
468 
469 int
lungetc(int c)470 lungetc(int c)
471 {
472 	if (c == EOF)
473 		return (EOF);
474 	if (parsebuf) {
475 		parseindex--;
476 		if (parseindex >= 0)
477 			return (c);
478 	}
479 	if (pushback_index + 1 >= MAXPUSHBACK)
480 		return (EOF);
481 	pushback_buffer[pushback_index++] = c;
482 	return (c);
483 }
484 
485 int
findeol(void)486 findeol(void)
487 {
488 	int	c;
489 
490 	parsebuf = NULL;
491 
492 	/* skip to either EOF or the first real EOL */
493 	while (1) {
494 		if (pushback_index)
495 			c = (unsigned char)pushback_buffer[--pushback_index];
496 		else
497 			c = lgetc(0);
498 		if (c == '\n') {
499 			file->lineno++;
500 			break;
501 		}
502 		if (c == EOF)
503 			break;
504 	}
505 	return (ERROR);
506 }
507 
508 int
yylex(void)509 yylex(void)
510 {
511 	char	 buf[8096];
512 	char	*p;
513 	int	 quotec, next, c;
514 	int	 token;
515 
516 	p = buf;
517 	while ((c = lgetc(0)) == ' ' || c == '\t')
518 		; /* nothing */
519 
520 	yylval.lineno = file->lineno;
521 	if (c == '#')
522 		while ((c = lgetc(0)) != '\n' && c != EOF)
523 			; /* nothing */
524 
525 	switch (c) {
526 	case '\'':
527 	case '"':
528 		quotec = c;
529 		while (1) {
530 			if ((c = lgetc(quotec)) == EOF)
531 				return (0);
532 			if (c == '\n') {
533 				file->lineno++;
534 				continue;
535 			} else if (c == '\\') {
536 				if ((next = lgetc(quotec)) == EOF)
537 					return (0);
538 				if (next == quotec || next == ' ' ||
539 				    next == '\t')
540 					c = next;
541 				else if (next == '\n') {
542 					file->lineno++;
543 					continue;
544 				} else
545 					lungetc(next);
546 			} else if (c == quotec) {
547 				*p = '\0';
548 				break;
549 			} else if (c == '\0') {
550 				yyerror("syntax error");
551 				return (findeol());
552 			}
553 			if (p + 1 >= buf + sizeof(buf) - 1) {
554 				yyerror("string too long");
555 				return (findeol());
556 			}
557 			*p++ = c;
558 		}
559 		yylval.v.string = xstrdup(buf);
560 		return (STRING);
561 	}
562 
563 #define allowed_to_end_number(x) \
564 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
565 
566 	if (c == '-' || isdigit(c)) {
567 		do {
568 			*p++ = c;
569 			if ((size_t)(p-buf) >= sizeof(buf)) {
570 				yyerror("string too long");
571 				return (findeol());
572 			}
573 		} while ((c = lgetc(0)) != EOF && isdigit(c));
574 		lungetc(c);
575 		if (p == buf + 1 && buf[0] == '-')
576 			goto nodigits;
577 		if (c == EOF || allowed_to_end_number(c)) {
578 			const char *errstr = NULL;
579 
580 			*p = '\0';
581 			yylval.v.number = strtonum(buf, LLONG_MIN,
582 			    LLONG_MAX, &errstr);
583 			if (errstr) {
584 				yyerror("\"%s\" invalid number: %s",
585 				    buf, errstr);
586 				return (findeol());
587 			}
588 			return (NUMBER);
589 		} else {
590 nodigits:
591 			while (p > buf + 1)
592 				lungetc((unsigned char)*--p);
593 			c = (unsigned char)*--p;
594 			if (c == '-')
595 				return (c);
596 		}
597 	}
598 
599 #define allowed_in_string(x) \
600 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
601 	x != '{' && x != '}' && x != '<' && x != '>' && \
602 	x != '!' && x != '=' && x != '/' && x != '#' && \
603 	x != ','))
604 
605 	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
606 		do {
607 			*p++ = c;
608 			if ((size_t)(p-buf) >= sizeof(buf)) {
609 				yyerror("string too long");
610 				return (findeol());
611 			}
612 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
613 		lungetc(c);
614 		*p = '\0';
615 		if ((token = lookup(buf)) == STRING)
616 			yylval.v.string = xstrdup(buf);
617 		return (token);
618 	}
619 	if (c == '\n') {
620 		yylval.lineno = file->lineno;
621 		file->lineno++;
622 	}
623 	if (c == EOF)
624 		return (0);
625 	return (c);
626 }
627 
628 struct file *
pushfile(const char * name)629 pushfile(const char *name)
630 {
631 	struct file	*nfile;
632 
633 	nfile = xzalloc(sizeof(struct file));
634 	nfile->name = xstrdup(name);
635 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
636 		warn("%s: %s", __func__, nfile->name);
637 		free(nfile->name);
638 		free(nfile);
639 		return (NULL);
640 	}
641 	nfile->lineno = 1;
642 	TAILQ_INSERT_TAIL(&files, nfile, entry);
643 	return (nfile);
644 }
645 
646 int
popfile(void)647 popfile(void)
648 {
649 	struct file	*prev;
650 
651 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
652 		prev->errors += file->errors;
653 
654 	TAILQ_REMOVE(&files, file, entry);
655 	fclose(file->stream);
656 	free(file->name);
657 	free(file);
658 	file = prev;
659 	return (file ? 0 : EOF);
660 }
661 
662 int
parse_config(const char * filename,struct ldom_config * xconf)663 parse_config(const char *filename, struct ldom_config *xconf)
664 {
665 	int		 errors = 0;
666 
667 	conf = xconf;
668 
669 	if ((file = pushfile(filename)) == NULL) {
670 		return (-1);
671 	}
672 	topfile = file;
673 
674 	yyparse();
675 	errors = file->errors;
676 	popfile();
677 
678 	return (errors ? -1 : 0);
679 }
680