xref: /openbsd-src/usr.sbin/vmd/parse.y (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: parse.y,v 1.7 2016/06/21 21:35:25 benno Exp $	*/
2 
3 /*
4  * Copyright (c) 2007-2015 Reyk Floeter <reyk@openbsd.org>
5  * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
6  * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
7  * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
8  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
9  * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
10  * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
11  *
12  * Permission to use, copy, modify, and distribute this software for any
13  * purpose with or without fee is hereby granted, provided that the above
14  * copyright notice and this permission notice appear in all copies.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
17  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
19  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
21  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
22  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23  */
24 
25 %{
26 #include <sys/types.h>
27 #include <sys/queue.h>
28 #include <sys/uio.h>
29 
30 #include <machine/vmmvar.h>
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <limits.h>
35 #include <stdarg.h>
36 #include <string.h>
37 #include <ctype.h>
38 #include <netdb.h>
39 #include <util.h>
40 #include <errno.h>
41 #include <err.h>
42 
43 #include "proc.h"
44 #include "vmd.h"
45 
46 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
47 static struct file {
48 	TAILQ_ENTRY(file)	 entry;
49 	FILE			*stream;
50 	char			*name;
51 	int			 lineno;
52 	int			 errors;
53 } *file, *topfile;
54 struct file	*pushfile(const char *, int);
55 int		 popfile(void);
56 int		 yyparse(void);
57 int		 yylex(void);
58 int		 yyerror(const char *, ...)
59     __attribute__((__format__ (printf, 1, 2)))
60     __attribute__((__nonnull__ (1)));
61 int		 kw_cmp(const void *, const void *);
62 int		 lookup(char *);
63 int		 lgetc(int);
64 int		 lungetc(int);
65 int		 findeol(void);
66 
67 TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
68 struct sym {
69 	TAILQ_ENTRY(sym)	 entry;
70 	int			 used;
71 	int			 persist;
72 	char			*nam;
73 	char			*val;
74 };
75 int		 symset(const char *, const char *, int);
76 char		*symget(const char *);
77 
78 ssize_t		 parse_size(char *, int64_t);
79 int		 parse_disk(char *);
80 
81 static struct vm_create_params	 vcp;
82 static int			 vcp_disable = 0;
83 static int			 errors = 0;
84 
85 extern struct vmd		*env;
86 
87 typedef struct {
88 	union {
89 		int64_t		 number;
90 		char		*string;
91 	} v;
92 	int lineno;
93 } YYSTYPE;
94 
95 %}
96 
97 
98 %token	INCLUDE ERROR
99 %token	DISK NIFS PATH SIZE VMID
100 %token	ENABLE DISABLE VM KERNEL MEMORY
101 %token	<v.string>	STRING
102 %token  <v.number>	NUMBER
103 %type	<v.number>	disable
104 %type	<v.string>	string
105 
106 %%
107 
108 grammar		: /* empty */
109 		| grammar include '\n'
110 		| grammar '\n'
111 		| grammar varset '\n'
112 		| grammar main '\n'
113 		| grammar error '\n'		{ file->errors++; }
114 		;
115 
116 include		: INCLUDE string		{
117 			struct file	*nfile;
118 
119 			if ((nfile = pushfile($2, 0)) == NULL) {
120 				yyerror("failed to include file %s", $2);
121 				free($2);
122 				YYERROR;
123 			}
124 			free($2);
125 
126 			file = nfile;
127 			lungetc('\n');
128 		}
129 		;
130 
131 varset		: STRING '=' STRING		{
132 			char *s = $1;
133 			while (*s++) {
134 				if (isspace((unsigned char)*s)) {
135 					yyerror("macro name cannot contain "
136 					    "whitespace");
137 					YYERROR;
138 				}
139 			}
140 			if (symset($1, $3, 0) == -1)
141 				fatalx("cannot store variable");
142 			free($1);
143 			free($3);
144 		}
145 		;
146 
147 main		: VM string			{
148 			memset(&vcp, 0, sizeof(vcp));
149 			vcp_disable = 0;
150 			if (strlcpy(vcp.vcp_name, $2, sizeof(vcp.vcp_name)) >=
151 			    sizeof(vcp.vcp_name)) {
152 				yyerror("vm name too long");
153 				YYERROR;
154 			}
155 		} '{' optnl vm_opts_l '}'	{
156 			int ret;
157 
158 			if (vcp_disable) {
159 				log_debug("%s:%d: vm \"%s\" skipped (disabled)",
160 				    file->name, yylval.lineno, vcp.vcp_name);
161 			} else if (!env->vmd_noaction) {
162 				/*
163 				 * XXX Start the vm right away -
164 				 * XXX this should be done after parsing
165 				 * XXX the configuration.
166 				 */
167 				ret = config_getvm(&env->vmd_ps, &vcp, -1, -1);
168 				if (ret == -1 && errno == EALREADY) {
169 					log_debug("%s:%d: vm \"%s\""
170 					    " skipped (running)",
171 					    file->name, yylval.lineno,
172 					    vcp.vcp_name);
173 				} else if (ret == -1) {
174 					log_warn("%s:%d: vm \"%s\" failed",
175 					    file->name, yylval.lineno,
176 					    vcp.vcp_name);
177 					YYERROR;
178 				} else {
179 					log_debug("%s:%d: vm \"%s\" enabled",
180 					    file->name, yylval.lineno,
181 					    vcp.vcp_name);
182 				}
183 			}
184 		}
185 		;
186 
187 vm_opts_l	: vm_opts_l vm_opts nl
188 		| vm_opts optnl
189 		;
190 
191 vm_opts		: disable			{
192 			vcp_disable = $1;
193 		}
194 		| DISK string			{
195 			if (parse_disk($2) != 0) {
196 				yyerror("failed to parse disks: %s", $2);
197 				free($2);
198 				YYERROR;
199 			}
200 			free($2);
201 		}
202 		| KERNEL string			{
203 			if (vcp.vcp_kernel[0] != '\0') {
204 				yyerror("kernel specified more than once");
205 				free($2);
206 				YYERROR;
207 			}
208 			if (strlcpy(vcp.vcp_kernel, $2,
209 			    sizeof(vcp.vcp_kernel)) >= sizeof(vcp.vcp_kernel)) {
210 				yyerror("kernel name too long");
211 				free($2);
212 				YYERROR;
213 			}
214 			free($2);
215 		}
216 		| NIFS NUMBER			{
217 			if (vcp.vcp_nnics != 0) {
218 				yyerror("interfaces specified more than once");
219 				YYERROR;
220 			}
221 			if ($2 < 0 || $2 > VMM_MAX_NICS_PER_VM) {
222 				yyerror("too many interfaces: %lld", $2);
223 				YYERROR;
224 			}
225 			vcp.vcp_nnics = (size_t)$2;
226 		}
227 		| MEMORY NUMBER			{
228 			ssize_t	 res;
229 			if (vcp.vcp_memranges[0].vmr_size != 0) {
230 				yyerror("memory specified more than once");
231 				YYERROR;
232 			}
233 			if ((res = parse_size(NULL, $2)) == -1) {
234 				yyerror("failed to parse size: %lld", $2);
235 				YYERROR;
236 			}
237 			vcp.vcp_memranges[0].vmr_size = (size_t)res;
238 		}
239 		| MEMORY STRING			{
240 			ssize_t	 res;
241 			if (vcp.vcp_memranges[0].vmr_size != 0) {
242 				yyerror("argument specified more than once");
243 				free($2);
244 				YYERROR;
245 			}
246 			if ((res = parse_size($2, 0)) == -1) {
247 				yyerror("failed to parse size: %s", $2);
248 				free($2);
249 				YYERROR;
250 			}
251 			vcp.vcp_memranges[0].vmr_size = (size_t)res;
252 		}
253 		;
254 
255 string		: STRING string				{
256 			if (asprintf(&$$, "%s%s", $1, $2) == -1)
257 				fatal("asprintf string");
258 			free($1);
259 			free($2);
260 		}
261 		| STRING
262 		;
263 
264 disable		: ENABLE			{ $$ = 0; }
265 		| DISABLE			{ $$ = 1; }
266 		;
267 
268 optnl		: '\n' optnl
269 		|
270 		;
271 
272 nl		: '\n' optnl
273 		;
274 
275 %%
276 
277 struct keywords {
278 	const char	*k_name;
279 	int		 k_val;
280 };
281 
282 int
283 yyerror(const char *fmt, ...)
284 {
285 	va_list		 ap;
286 	char		*msg;
287 
288 	file->errors++;
289 	va_start(ap, fmt);
290 	if (vasprintf(&msg, fmt, ap) == -1)
291 		fatal("yyerror vasprintf");
292 	va_end(ap);
293 	log_warnx("%s:%d: %s", file->name, yylval.lineno, msg);
294 	free(msg);
295 	return (0);
296 }
297 
298 int
299 kw_cmp(const void *k, const void *e)
300 {
301 	return (strcmp(k, ((const struct keywords *)e)->k_name));
302 }
303 
304 int
305 lookup(char *s)
306 {
307 	/* this has to be sorted always */
308 	static const struct keywords keywords[] = {
309 		{ "disable",		DISABLE },
310 		{ "disk",		DISK },
311 		{ "enable",		ENABLE },
312 		{ "id",			VMID },
313 		{ "include",		INCLUDE },
314 		{ "interfaces",		NIFS },
315 		{ "kernel",		KERNEL },
316 		{ "memory",		MEMORY },
317 		{ "size",		SIZE },
318 		{ "vm",			VM }
319 	};
320 	const struct keywords	*p;
321 
322 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
323 	    sizeof(keywords[0]), kw_cmp);
324 
325 	if (p)
326 		return (p->k_val);
327 	else
328 		return (STRING);
329 }
330 
331 #define MAXPUSHBACK	128
332 
333 u_char	*parsebuf;
334 int	 parseindex;
335 u_char	 pushback_buffer[MAXPUSHBACK];
336 int	 pushback_index = 0;
337 
338 int
339 lgetc(int quotec)
340 {
341 	int		c, next;
342 
343 	if (parsebuf) {
344 		/* Read character from the parsebuffer instead of input. */
345 		if (parseindex >= 0) {
346 			c = parsebuf[parseindex++];
347 			if (c != '\0')
348 				return (c);
349 			parsebuf = NULL;
350 		} else
351 			parseindex++;
352 	}
353 
354 	if (pushback_index)
355 		return (pushback_buffer[--pushback_index]);
356 
357 	if (quotec) {
358 		if ((c = getc(file->stream)) == EOF) {
359 			yyerror("reached end of file while parsing "
360 			    "quoted string");
361 			if (file == topfile || popfile() == EOF)
362 				return (EOF);
363 			return (quotec);
364 		}
365 		return (c);
366 	}
367 
368 	while ((c = getc(file->stream)) == '\\') {
369 		next = getc(file->stream);
370 		if (next != '\n') {
371 			c = next;
372 			break;
373 		}
374 		yylval.lineno = file->lineno;
375 		file->lineno++;
376 	}
377 	if (c == '\t' || c == ' ') {
378 		/* Compress blanks to a single space. */
379 		do {
380 			c = getc(file->stream);
381 		} while (c == '\t' || c == ' ');
382 		ungetc(c, file->stream);
383 		c = ' ';
384 	}
385 
386 	while (c == EOF) {
387 		if (file == topfile || popfile() == EOF)
388 			return (EOF);
389 		c = getc(file->stream);
390 	}
391 	return (c);
392 }
393 
394 int
395 lungetc(int c)
396 {
397 	if (c == EOF)
398 		return (EOF);
399 	if (parsebuf) {
400 		parseindex--;
401 		if (parseindex >= 0)
402 			return (c);
403 	}
404 	if (pushback_index < MAXPUSHBACK-1)
405 		return (pushback_buffer[pushback_index++] = c);
406 	else
407 		return (EOF);
408 }
409 
410 int
411 findeol(void)
412 {
413 	int	c;
414 
415 	parsebuf = NULL;
416 
417 	/* skip to either EOF or the first real EOL */
418 	while (1) {
419 		if (pushback_index)
420 			c = pushback_buffer[--pushback_index];
421 		else
422 			c = lgetc(0);
423 		if (c == '\n') {
424 			file->lineno++;
425 			break;
426 		}
427 		if (c == EOF)
428 			break;
429 	}
430 	return (ERROR);
431 }
432 
433 int
434 yylex(void)
435 {
436 	u_char	 buf[8096];
437 	u_char	*p, *val;
438 	int	 quotec, next, c;
439 	int	 token;
440 
441 top:
442 	p = buf;
443 	while ((c = lgetc(0)) == ' ' || c == '\t')
444 		; /* nothing */
445 
446 	yylval.lineno = file->lineno;
447 	if (c == '#')
448 		while ((c = lgetc(0)) != '\n' && c != EOF)
449 			; /* nothing */
450 	if (c == '$' && parsebuf == NULL) {
451 		while (1) {
452 			if ((c = lgetc(0)) == EOF)
453 				return (0);
454 
455 			if (p + 1 >= buf + sizeof(buf) - 1) {
456 				yyerror("string too long");
457 				return (findeol());
458 			}
459 			if (isalnum(c) || c == '_') {
460 				*p++ = c;
461 				continue;
462 			}
463 			*p = '\0';
464 			lungetc(c);
465 			break;
466 		}
467 		val = symget(buf);
468 		if (val == NULL) {
469 			yyerror("macro '%s' not defined", buf);
470 			return (findeol());
471 		}
472 		parsebuf = val;
473 		parseindex = 0;
474 		goto top;
475 	}
476 
477 	switch (c) {
478 	case '\'':
479 	case '"':
480 		quotec = c;
481 		while (1) {
482 			if ((c = lgetc(quotec)) == EOF)
483 				return (0);
484 			if (c == '\n') {
485 				file->lineno++;
486 				continue;
487 			} else if (c == '\\') {
488 				if ((next = lgetc(quotec)) == EOF)
489 					return (0);
490 				if (next == quotec || c == ' ' || c == '\t')
491 					c = next;
492 				else if (next == '\n') {
493 					file->lineno++;
494 					continue;
495 				} else
496 					lungetc(next);
497 			} else if (c == quotec) {
498 				*p = '\0';
499 				break;
500 			} else if (c == '\0') {
501 				yyerror("syntax error");
502 				return (findeol());
503 			}
504 			if (p + 1 >= buf + sizeof(buf) - 1) {
505 				yyerror("string too long");
506 				return (findeol());
507 			}
508 			*p++ = c;
509 		}
510 		yylval.v.string = strdup(buf);
511 		if (yylval.v.string == NULL)
512 			fatal("yylex: strdup");
513 		return (STRING);
514 	}
515 
516 #define allowed_to_end_number(x) \
517 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
518 
519 	if (c == '-' || isdigit(c)) {
520 		do {
521 			*p++ = c;
522 			if ((unsigned)(p-buf) >= sizeof(buf)) {
523 				yyerror("string too long");
524 				return (findeol());
525 			}
526 		} while ((c = lgetc(0)) != EOF && isdigit(c));
527 		lungetc(c);
528 		if (p == buf + 1 && buf[0] == '-')
529 			goto nodigits;
530 		if (c == EOF || allowed_to_end_number(c)) {
531 			const char *errstr = NULL;
532 
533 			*p = '\0';
534 			yylval.v.number = strtonum(buf, LLONG_MIN,
535 			    LLONG_MAX, &errstr);
536 			if (errstr) {
537 				yyerror("\"%s\" invalid number: %s",
538 				    buf, errstr);
539 				return (findeol());
540 			}
541 			return (NUMBER);
542 		} else {
543 nodigits:
544 			while (p > buf + 1)
545 				lungetc(*--p);
546 			c = *--p;
547 			if (c == '-')
548 				return (c);
549 		}
550 	}
551 
552 #define allowed_in_string(x) \
553 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
554 	x != '{' && x != '}' && \
555 	x != '!' && x != '=' && x != '#' && \
556 	x != ','))
557 
558 	if (isalnum(c) || c == ':' || c == '_' || c == '/') {
559 		do {
560 			*p++ = c;
561 			if ((unsigned)(p-buf) >= sizeof(buf)) {
562 				yyerror("string too long");
563 				return (findeol());
564 			}
565 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
566 		lungetc(c);
567 		*p = '\0';
568 		if ((token = lookup(buf)) == STRING)
569 			if ((yylval.v.string = strdup(buf)) == NULL)
570 				fatal("yylex: strdup");
571 		return (token);
572 	}
573 	if (c == '\n') {
574 		yylval.lineno = file->lineno;
575 		file->lineno++;
576 	}
577 	if (c == EOF)
578 		return (0);
579 	return (c);
580 }
581 
582 struct file *
583 pushfile(const char *name, int secret)
584 {
585 	struct file	*nfile;
586 
587 	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
588 		log_warn("malloc");
589 		return (NULL);
590 	}
591 	if ((nfile->name = strdup(name)) == NULL) {
592 		log_warn("malloc");
593 		free(nfile);
594 		return (NULL);
595 	}
596 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
597 		free(nfile->name);
598 		free(nfile);
599 		return (NULL);
600 	}
601 	nfile->lineno = 1;
602 	TAILQ_INSERT_TAIL(&files, nfile, entry);
603 	return (nfile);
604 }
605 
606 int
607 popfile(void)
608 {
609 	struct file	*prev;
610 
611 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
612 		prev->errors += file->errors;
613 
614 	TAILQ_REMOVE(&files, file, entry);
615 	fclose(file->stream);
616 	free(file->name);
617 	free(file);
618 	file = prev;
619 	return (file ? 0 : EOF);
620 }
621 
622 int
623 parse_config(const char *filename)
624 {
625 	struct sym	*sym, *next;
626 
627 	if ((file = pushfile(filename, 0)) == NULL) {
628 		log_warn("failed to open %s", filename);
629 		return (0);
630 	}
631 	topfile = file;
632 	setservent(1);
633 
634 	yyparse();
635 	errors = file->errors;
636 	popfile();
637 
638 	endservent();
639 
640 	/* Free macros and check which have not been used. */
641 	for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
642 		next = TAILQ_NEXT(sym, entry);
643 		if (!sym->used)
644 			fprintf(stderr, "warning: macro '%s' not "
645 			    "used\n", sym->nam);
646 		if (!sym->persist) {
647 			free(sym->nam);
648 			free(sym->val);
649 			TAILQ_REMOVE(&symhead, sym, entry);
650 			free(sym);
651 		}
652 	}
653 
654 	if (errors)
655 		return (-1);
656 
657 	return (0);
658 }
659 
660 int
661 symset(const char *nam, const char *val, int persist)
662 {
663 	struct sym	*sym;
664 
665 	for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
666 	    sym = TAILQ_NEXT(sym, entry))
667 		;	/* nothing */
668 
669 	if (sym != NULL) {
670 		if (sym->persist == 1)
671 			return (0);
672 		else {
673 			free(sym->nam);
674 			free(sym->val);
675 			TAILQ_REMOVE(&symhead, sym, entry);
676 			free(sym);
677 		}
678 	}
679 	if ((sym = calloc(1, sizeof(*sym))) == NULL)
680 		return (-1);
681 
682 	sym->nam = strdup(nam);
683 	if (sym->nam == NULL) {
684 		free(sym);
685 		return (-1);
686 	}
687 	sym->val = strdup(val);
688 	if (sym->val == NULL) {
689 		free(sym->nam);
690 		free(sym);
691 		return (-1);
692 	}
693 	sym->used = 0;
694 	sym->persist = persist;
695 	TAILQ_INSERT_TAIL(&symhead, sym, entry);
696 	return (0);
697 }
698 
699 int
700 cmdline_symset(char *s)
701 {
702 	char	*sym, *val;
703 	int	ret;
704 	size_t	len;
705 
706 	if ((val = strrchr(s, '=')) == NULL)
707 		return (-1);
708 
709 	len = (val - s) + 1;
710 	if ((sym = malloc(len)) == NULL)
711 		fatal("cmdline_symset: malloc");
712 
713 	(void)strlcpy(sym, s, len);
714 
715 	ret = symset(sym, val + 1, 1);
716 	free(sym);
717 
718 	return (ret);
719 }
720 
721 char *
722 symget(const char *nam)
723 {
724 	struct sym	*sym;
725 
726 	TAILQ_FOREACH(sym, &symhead, entry)
727 		if (strcmp(nam, sym->nam) == 0) {
728 			sym->used = 1;
729 			return (sym->val);
730 		}
731 	return (NULL);
732 }
733 
734 ssize_t
735 parse_size(char *word, int64_t val)
736 {
737 	ssize_t		 size;
738 	long long	 res;
739 
740 	if (word != NULL) {
741 		if (scan_scaled(word, &res) != 0) {
742 			log_warn("invalid size: %s", word);
743 			return (-1);
744 		}
745 		val = (int64_t)res;
746 	}
747 
748 	if (val < (1024 * 1024)) {
749 		log_warnx("size must be at least one megabyte");
750 		return (-1);
751 	} else
752 		size = val / 1024 / 1024;
753 
754 	if ((size * 1024 * 1024) != val)
755 		log_warnx("size rounded to %zd megabytes", size);
756 
757 	return ((ssize_t)size);
758 }
759 
760 int
761 parse_disk(char *word)
762 {
763 	if (vcp.vcp_ndisks >= VMM_MAX_DISKS_PER_VM) {
764 		log_warnx("too many disks");
765 		return (-1);
766 	}
767 
768 	if (strlcpy(vcp.vcp_disks[vcp.vcp_ndisks], word,
769 	    VMM_MAX_PATH_DISK) >= VMM_MAX_PATH_DISK) {
770 		log_warnx("disk path too long");
771 		return (-1);
772 	}
773 
774 	vcp.vcp_ndisks++;
775 
776 	return (0);
777 }
778