xref: /openbsd/usr.sbin/vmd/parse.y (revision 5a38ef86)
1 /*	$OpenBSD: parse.y,v 1.59 2021/10/15 15:01:29 naddy Exp $	*/
2 
3 /*
4  * Copyright (c) 2007-2016 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/socket.h>
29 #include <sys/uio.h>
30 
31 #include <machine/vmmvar.h>
32 
33 #include <net/if.h>
34 #include <netinet/in.h>
35 #include <netinet/if_ether.h>
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <limits.h>
40 #include <stdarg.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <ctype.h>
44 #include <netdb.h>
45 #include <util.h>
46 #include <errno.h>
47 #include <err.h>
48 #include <fcntl.h>
49 #include <pwd.h>
50 #include <grp.h>
51 
52 #include "vmd.h"
53 
54 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
55 static struct file {
56 	TAILQ_ENTRY(file)	 entry;
57 	FILE			*stream;
58 	char			*name;
59 	size_t			 ungetpos;
60 	size_t			 ungetsize;
61 	u_char			*ungetbuf;
62 	int			 eof_reached;
63 	int			 lineno;
64 	int			 errors;
65 } *file, *topfile;
66 struct file	*pushfile(const char *, int);
67 int		 popfile(void);
68 int		 yyparse(void);
69 int		 yylex(void);
70 int		 yyerror(const char *, ...)
71     __attribute__((__format__ (printf, 1, 2)))
72     __attribute__((__nonnull__ (1)));
73 int		 kw_cmp(const void *, const void *);
74 int		 lookup(char *);
75 int		 igetc(void);
76 int		 lgetc(int);
77 void		 lungetc(int);
78 int		 findeol(void);
79 
80 TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
81 struct sym {
82 	TAILQ_ENTRY(sym)	 entry;
83 	int			 used;
84 	int			 persist;
85 	char			*nam;
86 	char			*val;
87 };
88 int		 symset(const char *, const char *, int);
89 char		*symget(const char *);
90 
91 ssize_t		 parse_size(char *, int64_t);
92 int		 parse_disk(char *, int);
93 unsigned int	 parse_format(const char *);
94 
95 static struct vmop_create_params vmc;
96 static struct vm_create_params	*vcp;
97 static struct vmd_switch	*vsw;
98 static char			 vsw_type[IF_NAMESIZE];
99 static int			 vcp_disable;
100 static size_t			 vcp_nnics;
101 static int			 errors;
102 extern struct vmd		*env;
103 extern const char		*vmd_descsw[];
104 
105 typedef struct {
106 	union {
107 		uint8_t		 lladdr[ETHER_ADDR_LEN];
108 		int64_t		 number;
109 		char		*string;
110 		struct {
111 			uid_t	 uid;
112 			int64_t	 gid;
113 		}		 owner;
114 	} v;
115 	int lineno;
116 } YYSTYPE;
117 
118 %}
119 
120 
121 %token	INCLUDE ERROR
122 %token	ADD ALLOW BOOT CDROM DEVICE DISABLE DISK DOWN ENABLE FORMAT GROUP
123 %token	INET6 INSTANCE INTERFACE LLADDR LOCAL LOCKED MEMORY NET NIFS OWNER
124 %token	PATH PREFIX RDOMAIN SIZE SOCKET SWITCH UP VM VMID STAGGERED START
125 %token  PARALLEL DELAY
126 %token	<v.number>	NUMBER
127 %token	<v.string>	STRING
128 %type	<v.lladdr>	lladdr
129 %type	<v.number>	bootdevice
130 %type	<v.number>	disable
131 %type	<v.number>	image_format
132 %type	<v.number>	local
133 %type	<v.number>	locked
134 %type	<v.number>	updown
135 %type	<v.owner>	owner_id
136 %type	<v.string>	optstring
137 %type	<v.string>	string
138 %type	<v.string>	vm_instance
139 
140 %%
141 
142 grammar		: /* empty */
143 		| grammar include '\n'
144 		| grammar '\n'
145 		| grammar varset '\n'
146 		| grammar main '\n'
147 		| grammar switch '\n'
148 		| grammar vm '\n'
149 		| grammar error '\n'		{ file->errors++; }
150 		;
151 
152 include		: INCLUDE string		{
153 			struct file	*nfile;
154 
155 			if ((nfile = pushfile($2, 0)) == NULL) {
156 				yyerror("failed to include file %s", $2);
157 				free($2);
158 				YYERROR;
159 			}
160 			free($2);
161 
162 			file = nfile;
163 			lungetc('\n');
164 		}
165 		;
166 
167 varset		: STRING '=' STRING		{
168 			char *s = $1;
169 			while (*s++) {
170 				if (isspace((unsigned char)*s)) {
171 					yyerror("macro name cannot contain "
172 					    "whitespace");
173 					free($1);
174 					free($3);
175 					YYERROR;
176 				}
177 			}
178 			if (symset($1, $3, 0) == -1)
179 				fatalx("cannot store variable");
180 			free($1);
181 			free($3);
182 		}
183 		;
184 
185 main		: LOCAL INET6 {
186 			env->vmd_cfg.cfg_flags |= VMD_CFG_INET6;
187 		}
188 		| LOCAL INET6 PREFIX STRING {
189 			struct address	 h;
190 
191 			if (host($4, &h) == -1 ||
192 			    h.ss.ss_family != AF_INET6 ||
193 			    h.prefixlen > 64 || h.prefixlen < 0) {
194 				yyerror("invalid local inet6 prefix: %s", $4);
195 				free($4);
196 				YYERROR;
197 			}
198 
199 			env->vmd_cfg.cfg_flags |= VMD_CFG_INET6;
200 			env->vmd_cfg.cfg_flags &= ~VMD_CFG_AUTOINET6;
201 			memcpy(&env->vmd_cfg.cfg_localprefix6, &h, sizeof(h));
202 		}
203 		| LOCAL PREFIX STRING {
204 			struct address	 h;
205 
206 			if (host($3, &h) == -1 ||
207 			    h.ss.ss_family != AF_INET ||
208 			    h.prefixlen > 32 || h.prefixlen < 0) {
209 				yyerror("invalid local prefix: %s", $3);
210 				free($3);
211 				YYERROR;
212 			}
213 
214 			memcpy(&env->vmd_cfg.cfg_localprefix, &h, sizeof(h));
215 		}
216 		| SOCKET OWNER owner_id {
217 			env->vmd_ps.ps_csock.cs_uid = $3.uid;
218 			env->vmd_ps.ps_csock.cs_gid = $3.gid == -1 ? 0 : $3.gid;
219 		}
220 		| STAGGERED START PARALLEL NUMBER DELAY NUMBER {
221 			env->vmd_cfg.cfg_flags |= VMD_CFG_STAGGERED_START;
222 			env->vmd_cfg.delay.tv_sec = $6;
223 			env->vmd_cfg.parallelism = $4;
224 		}
225 		;
226 
227 switch		: SWITCH string			{
228 			if ((vsw = calloc(1, sizeof(*vsw))) == NULL)
229 				fatal("could not allocate switch");
230 
231 			vsw->sw_id = env->vmd_nswitches + 1;
232 			vsw->sw_name = $2;
233 			vsw->sw_flags = VMIFF_UP;
234 
235 			vcp_disable = 0;
236 		} '{' optnl switch_opts_l '}'	{
237 			if (strnlen(vsw->sw_ifname,
238 			    sizeof(vsw->sw_ifname)) == 0) {
239 				yyerror("switch \"%s\" "
240 				    "is missing interface name",
241 				    vsw->sw_name);
242 				YYERROR;
243 			}
244 
245 			if (vcp_disable) {
246 				log_debug("%s:%d: switch \"%s\""
247 				    " skipped (disabled)",
248 				    file->name, yylval.lineno, vsw->sw_name);
249 			} else if (!env->vmd_noaction) {
250 				TAILQ_INSERT_TAIL(env->vmd_switches,
251 				    vsw, sw_entry);
252 				env->vmd_nswitches++;
253 				log_debug("%s:%d: switch \"%s\" registered",
254 				    file->name, yylval.lineno, vsw->sw_name);
255 			}
256 		}
257 		;
258 
259 switch_opts_l	: switch_opts_l switch_opts nl
260 		| switch_opts optnl
261 		;
262 
263 switch_opts	: disable			{
264 			vcp_disable = $1;
265 		}
266 		| GROUP string			{
267 			if (priv_validgroup($2) == -1) {
268 				yyerror("invalid group name: %s", $2);
269 				free($2);
270 				YYERROR;
271 			}
272 			vsw->sw_group = $2;
273 		}
274 		| INTERFACE string		{
275 			if (priv_getiftype($2, vsw_type, NULL) == -1 ||
276 			    priv_findname(vsw_type, vmd_descsw) == -1) {
277 				yyerror("invalid switch interface: %s", $2);
278 				free($2);
279 				YYERROR;
280 			}
281 
282 			if (strlcpy(vsw->sw_ifname, $2,
283 			    sizeof(vsw->sw_ifname)) >= sizeof(vsw->sw_ifname)) {
284 				yyerror("switch interface too long: %s", $2);
285 				free($2);
286 				YYERROR;
287 			}
288 			free($2);
289 		}
290 		| LOCKED LLADDR			{
291 			vsw->sw_flags |= VMIFF_LOCKED;
292 		}
293 		| RDOMAIN NUMBER		{
294 			if ($2 < 0 || $2 > RT_TABLEID_MAX) {
295 				yyerror("invalid rdomain: %lld", $2);
296 				YYERROR;
297 			}
298 			vsw->sw_flags |= VMIFF_RDOMAIN;
299 			vsw->sw_rdomain = $2;
300 		}
301 		| updown			{
302 			if ($1)
303 				vsw->sw_flags |= VMIFF_UP;
304 			else
305 				vsw->sw_flags &= ~VMIFF_UP;
306 		}
307 		;
308 
309 vm		: VM string vm_instance		{
310 			unsigned int	 i;
311 			char		*name;
312 
313 			memset(&vmc, 0, sizeof(vmc));
314 			vcp = &vmc.vmc_params;
315 			vcp_disable = 0;
316 			vcp_nnics = 0;
317 
318 			if ($3 != NULL) {
319 				/* This is an instance of a pre-configured VM */
320 				if (strlcpy(vmc.vmc_instance, $2,
321 				    sizeof(vmc.vmc_instance)) >=
322 				    sizeof(vmc.vmc_instance)) {
323 					yyerror("vm %s name too long", $2);
324 					free($2);
325 					free($3);
326 					YYERROR;
327 				}
328 
329 				free($2);
330 				name = $3;
331 				vmc.vmc_flags |= VMOP_CREATE_INSTANCE;
332 			} else
333 				name = $2;
334 
335 			for (i = 0; i < VMM_MAX_NICS_PER_VM; i++) {
336 				/* Set the interface to UP by default */
337 				vmc.vmc_ifflags[i] |= IFF_UP;
338 			}
339 
340 			if (strlcpy(vcp->vcp_name, name,
341 			    sizeof(vcp->vcp_name)) >= sizeof(vcp->vcp_name)) {
342 				yyerror("vm name too long");
343 				free($2);
344 				free($3);
345 				YYERROR;
346 			}
347 
348 			/* set default user/group permissions */
349 			vmc.vmc_owner.uid = 0;
350 			vmc.vmc_owner.gid = -1;
351 		} '{' optnl vm_opts_l '}'	{
352 			struct vmd_vm	*vm;
353 			int		 ret;
354 
355 			/* configured interfaces vs. number of interfaces */
356 			if (vcp_nnics > vcp->vcp_nnics)
357 				vcp->vcp_nnics = vcp_nnics;
358 
359 			if (!env->vmd_noaction) {
360 				ret = vm_register(&env->vmd_ps, &vmc,
361 				    &vm, 0, 0);
362 				if (ret == -1 && errno == EALREADY) {
363 					log_debug("%s:%d: vm \"%s\""
364 					    " skipped (%s)",
365 					    file->name, yylval.lineno,
366 					    vcp->vcp_name,
367 					    (vm->vm_state & VM_STATE_RUNNING) ?
368 					    "running" : "already exists");
369 				} else if (ret == -1) {
370 					yyerror("vm \"%s\" failed: %s",
371 					    vcp->vcp_name, strerror(errno));
372 					YYERROR;
373 				} else {
374 					if (vcp_disable)
375 						vm->vm_state |= VM_STATE_DISABLED;
376 					else
377 						vm->vm_state |= VM_STATE_WAITING;
378 					log_debug("%s:%d: vm \"%s\" "
379 					    "registered (%s)",
380 					    file->name, yylval.lineno,
381 					    vcp->vcp_name,
382 					    vcp_disable ?
383 					    "disabled" : "enabled");
384 				}
385 				vm->vm_from_config = 1;
386 			}
387 		}
388 		;
389 
390 vm_instance	: /* empty */			{ $$ = NULL; }
391 		| INSTANCE string		{ $$ = $2; }
392 		;
393 
394 vm_opts_l	: vm_opts_l vm_opts nl
395 		| vm_opts optnl
396 		;
397 
398 vm_opts		: disable			{
399 			vcp_disable = $1;
400 		}
401 		| DISK string image_format	{
402 			if (parse_disk($2, $3) != 0) {
403 				yyerror("failed to parse disks: %s", $2);
404 				free($2);
405 				YYERROR;
406 			}
407 			free($2);
408 			vmc.vmc_flags |= VMOP_CREATE_DISK;
409 		}
410 		| local INTERFACE optstring iface_opts_o {
411 			unsigned int	i;
412 			char		type[IF_NAMESIZE];
413 
414 			i = vcp_nnics;
415 			if (++vcp_nnics > VMM_MAX_NICS_PER_VM) {
416 				yyerror("too many interfaces: %zu", vcp_nnics);
417 				free($3);
418 				YYERROR;
419 			}
420 
421 			if ($1)
422 				vmc.vmc_ifflags[i] |= VMIFF_LOCAL;
423 			if ($3 != NULL) {
424 				if (strcmp($3, "tap") != 0 &&
425 				    (priv_getiftype($3, type, NULL) == -1 ||
426 				    strcmp(type, "tap") != 0)) {
427 					yyerror("invalid interface: %s", $3);
428 					free($3);
429 					YYERROR;
430 				}
431 
432 				if (strlcpy(vmc.vmc_ifnames[i], $3,
433 				    sizeof(vmc.vmc_ifnames[i])) >=
434 				    sizeof(vmc.vmc_ifnames[i])) {
435 					yyerror("interface name too long: %s",
436 					    $3);
437 					free($3);
438 					YYERROR;
439 				}
440 			}
441 			free($3);
442 			vmc.vmc_flags |= VMOP_CREATE_NETWORK;
443 		}
444 		| BOOT string			{
445 			char	 path[PATH_MAX];
446 
447 			if (vcp->vcp_kernel[0] != '\0') {
448 				yyerror("kernel specified more than once");
449 				free($2);
450 				YYERROR;
451 
452 			}
453 			if (realpath($2, path) == NULL) {
454 				yyerror("kernel path not found: %s",
455 				    strerror(errno));
456 				free($2);
457 				YYERROR;
458 			}
459 			free($2);
460 			if (strlcpy(vcp->vcp_kernel, path,
461 			    sizeof(vcp->vcp_kernel)) >=
462 			    sizeof(vcp->vcp_kernel)) {
463 				yyerror("kernel name too long");
464 				YYERROR;
465 			}
466 			vmc.vmc_flags |= VMOP_CREATE_KERNEL;
467 		}
468 		| BOOT DEVICE bootdevice	{
469 			vmc.vmc_bootdevice = $3;
470 		}
471 		| CDROM string			{
472 			if (vcp->vcp_cdrom[0] != '\0') {
473 				yyerror("cdrom specified more than once");
474 				free($2);
475 				YYERROR;
476 
477 			}
478 			if (strlcpy(vcp->vcp_cdrom, $2,
479 			    sizeof(vcp->vcp_cdrom)) >=
480 			    sizeof(vcp->vcp_cdrom)) {
481 				yyerror("cdrom name too long");
482 				free($2);
483 				YYERROR;
484 			}
485 			free($2);
486 			vmc.vmc_flags |= VMOP_CREATE_CDROM;
487 		}
488 		| NIFS NUMBER			{
489 			if (vcp->vcp_nnics != 0) {
490 				yyerror("interfaces specified more than once");
491 				YYERROR;
492 			}
493 			if ($2 < 0 || $2 > VMM_MAX_NICS_PER_VM) {
494 				yyerror("too many interfaces: %lld", $2);
495 				YYERROR;
496 			}
497 			vcp->vcp_nnics = (size_t)$2;
498 			vmc.vmc_flags |= VMOP_CREATE_NETWORK;
499 		}
500 		| MEMORY NUMBER			{
501 			ssize_t	 res;
502 			if (vcp->vcp_memranges[0].vmr_size != 0) {
503 				yyerror("memory specified more than once");
504 				YYERROR;
505 			}
506 			if ((res = parse_size(NULL, $2)) == -1) {
507 				yyerror("failed to parse size: %lld", $2);
508 				YYERROR;
509 			}
510 			vcp->vcp_memranges[0].vmr_size = (size_t)res;
511 			vmc.vmc_flags |= VMOP_CREATE_MEMORY;
512 		}
513 		| MEMORY STRING			{
514 			ssize_t	 res;
515 			if (vcp->vcp_memranges[0].vmr_size != 0) {
516 				yyerror("argument specified more than once");
517 				free($2);
518 				YYERROR;
519 			}
520 			if ((res = parse_size($2, 0)) == -1) {
521 				yyerror("failed to parse size: %s", $2);
522 				free($2);
523 				YYERROR;
524 			}
525 			vcp->vcp_memranges[0].vmr_size = (size_t)res;
526 			vmc.vmc_flags |= VMOP_CREATE_MEMORY;
527 		}
528 		| OWNER owner_id		{
529 			vmc.vmc_owner.uid = $2.uid;
530 			vmc.vmc_owner.gid = $2.gid;
531 		}
532 		| instance
533 		;
534 
535 instance	: ALLOW INSTANCE '{' optnl instance_l '}'
536 		| ALLOW INSTANCE instance_flags
537 		;
538 
539 instance_l	: instance_flags optcommanl instance_l
540 		| instance_flags optnl
541 		;
542 
543 instance_flags	: BOOT		{ vmc.vmc_insflags |= VMOP_CREATE_KERNEL; }
544 		| MEMORY	{ vmc.vmc_insflags |= VMOP_CREATE_MEMORY; }
545 		| INTERFACE	{ vmc.vmc_insflags |= VMOP_CREATE_NETWORK; }
546 		| DISK		{ vmc.vmc_insflags |= VMOP_CREATE_DISK; }
547 		| CDROM		{ vmc.vmc_insflags |= VMOP_CREATE_CDROM; }
548 		| INSTANCE	{ vmc.vmc_insflags |= VMOP_CREATE_INSTANCE; }
549 		| OWNER owner_id {
550 			vmc.vmc_insowner.uid = $2.uid;
551 			vmc.vmc_insowner.gid = $2.gid;
552 		}
553 		;
554 
555 owner_id	: NUMBER		{
556 			$$.uid = $1;
557 			$$.gid = -1;
558 		}
559 		| STRING		{
560 			char		*user, *group;
561 			struct passwd	*pw;
562 			struct group	*gr;
563 
564 			$$.uid = 0;
565 			$$.gid = -1;
566 
567 			user = $1;
568 			if ((group = strchr(user, ':')) != NULL) {
569 				if (group == user)
570 					user = NULL;
571 				*group++ = '\0';
572 			}
573 
574 			if (user != NULL && *user) {
575 				if ((pw = getpwnam(user)) == NULL) {
576 					yyerror("failed to get user: %s",
577 					    user);
578 					free($1);
579 					YYERROR;
580 				}
581 				$$.uid = pw->pw_uid;
582 			}
583 
584 			if (group != NULL && *group) {
585 				if ((gr = getgrnam(group)) == NULL) {
586 					yyerror("failed to get group: %s",
587 					    group);
588 					free($1);
589 					YYERROR;
590 				}
591 				$$.gid = gr->gr_gid;
592 			}
593 
594 			free($1);
595 		}
596 		;
597 
598 image_format	: /* none 	*/	{
599 			$$ = 0;
600 		}
601 	     	| FORMAT string		{
602 			if (($$ = parse_format($2)) == 0) {
603 				yyerror("unrecognized disk format %s", $2);
604 				free($2);
605 				YYERROR;
606 			}
607 		}
608 		;
609 
610 iface_opts_o	: '{' optnl iface_opts_l '}'
611 		| iface_opts_c
612 		| /* empty */
613 		;
614 
615 iface_opts_l	: iface_opts_l iface_opts optnl
616 		| iface_opts optnl
617 		;
618 
619 iface_opts_c	: iface_opts_c iface_opts optcomma
620 		| iface_opts
621 		;
622 
623 iface_opts	: SWITCH string			{
624 			unsigned int	i = vcp_nnics;
625 
626 			/* No need to check if the switch exists */
627 			if (strlcpy(vmc.vmc_ifswitch[i], $2,
628 			    sizeof(vmc.vmc_ifswitch[i])) >=
629 			    sizeof(vmc.vmc_ifswitch[i])) {
630 				yyerror("switch name too long: %s", $2);
631 				free($2);
632 				YYERROR;
633 			}
634 			free($2);
635 		}
636 		| GROUP string			{
637 			unsigned int	i = vcp_nnics;
638 
639 			if (priv_validgroup($2) == -1) {
640 				yyerror("invalid group name: %s", $2);
641 				free($2);
642 				YYERROR;
643 			}
644 
645 			/* No need to check if the group exists */
646 			(void)strlcpy(vmc.vmc_ifgroup[i], $2,
647 			    sizeof(vmc.vmc_ifgroup[i]));
648 			free($2);
649 		}
650 		| locked LLADDR lladdr		{
651 			if ($1)
652 				vmc.vmc_ifflags[vcp_nnics] |= VMIFF_LOCKED;
653 			memcpy(vcp->vcp_macs[vcp_nnics], $3, ETHER_ADDR_LEN);
654 		}
655 		| RDOMAIN NUMBER		{
656 			if ($2 < 0 || $2 > RT_TABLEID_MAX) {
657 				yyerror("invalid rdomain: %lld", $2);
658 				YYERROR;
659 			}
660 			vmc.vmc_ifflags[vcp_nnics] |= VMIFF_RDOMAIN;
661 			vmc.vmc_ifrdomain[vcp_nnics] = $2;
662 		}
663 		| updown			{
664 			if ($1)
665 				vmc.vmc_ifflags[vcp_nnics] |= VMIFF_UP;
666 			else
667 				vmc.vmc_ifflags[vcp_nnics] &= ~VMIFF_UP;
668 		}
669 		;
670 
671 optstring	: STRING			{ $$ = $1; }
672 		| /* empty */			{ $$ = NULL; }
673 		;
674 
675 string		: STRING string			{
676 			if (asprintf(&$$, "%s%s", $1, $2) == -1)
677 				fatal("asprintf string");
678 			free($1);
679 			free($2);
680 		}
681 		| STRING
682 		;
683 
684 lladdr		: STRING			{
685 			struct ether_addr *ea;
686 
687 			if ((ea = ether_aton($1)) == NULL) {
688 				yyerror("invalid address: %s\n", $1);
689 				free($1);
690 				YYERROR;
691 			}
692 			free($1);
693 
694 			memcpy($$, ea, ETHER_ADDR_LEN);
695 		}
696 		| /* empty */ {
697 			memset($$, 0, ETHER_ADDR_LEN);
698 		}
699 		;
700 
701 local		: /* empty */			{ $$ = 0; }
702 		| LOCAL				{ $$ = 1; }
703 		;
704 
705 locked		: /* empty */			{ $$ = 0; }
706 		| LOCKED			{ $$ = 1; }
707 		;
708 
709 updown		: UP				{ $$ = 1; }
710 		| DOWN				{ $$ = 0; }
711 		;
712 
713 disable		: ENABLE			{ $$ = 0; }
714 		| DISABLE			{ $$ = 1; }
715 		;
716 
717 bootdevice	: CDROM				{ $$ = VMBOOTDEV_CDROM; }
718 		| DISK				{ $$ = VMBOOTDEV_DISK; }
719 		| NET				{ $$ = VMBOOTDEV_NET; }
720 		;
721 
722 optcomma	: ','
723 		|
724 		;
725 
726 optnl		: '\n' optnl
727 		|
728 		;
729 
730 optcommanl	: ',' optnl
731 		| nl
732 		;
733 
734 nl		: '\n' optnl
735 		;
736 
737 %%
738 
739 struct keywords {
740 	const char	*k_name;
741 	int		 k_val;
742 };
743 
744 int
745 yyerror(const char *fmt, ...)
746 {
747 	va_list		 ap;
748 	char		*msg;
749 
750 	file->errors++;
751 	va_start(ap, fmt);
752 	if (vasprintf(&msg, fmt, ap) == -1)
753 		fatal("yyerror vasprintf");
754 	va_end(ap);
755 	log_warnx("%s:%d: %s", file->name, yylval.lineno, msg);
756 	free(msg);
757 	return (0);
758 }
759 
760 int
761 kw_cmp(const void *k, const void *e)
762 {
763 	return (strcmp(k, ((const struct keywords *)e)->k_name));
764 }
765 
766 int
767 lookup(char *s)
768 {
769 	/* this has to be sorted always */
770 	static const struct keywords keywords[] = {
771 		{ "add",		ADD },
772 		{ "allow",		ALLOW },
773 		{ "boot",		BOOT },
774 		{ "cdrom",		CDROM },
775 		{ "delay",		DELAY },
776 		{ "device",		DEVICE },
777 		{ "disable",		DISABLE },
778 		{ "disk",		DISK },
779 		{ "down",		DOWN },
780 		{ "enable",		ENABLE },
781 		{ "format",		FORMAT },
782 		{ "group",		GROUP },
783 		{ "id",			VMID },
784 		{ "include",		INCLUDE },
785 		{ "inet6",		INET6 },
786 		{ "instance",		INSTANCE },
787 		{ "interface",		INTERFACE },
788 		{ "interfaces",		NIFS },
789 		{ "lladdr",		LLADDR },
790 		{ "local",		LOCAL },
791 		{ "locked",		LOCKED },
792 		{ "memory",		MEMORY },
793 		{ "net",		NET },
794 		{ "owner",		OWNER },
795 		{ "parallel",		PARALLEL },
796 		{ "prefix",		PREFIX },
797 		{ "rdomain",		RDOMAIN },
798 		{ "size",		SIZE },
799 		{ "socket",		SOCKET },
800 		{ "staggered",		STAGGERED },
801 		{ "start",		START  },
802 		{ "switch",		SWITCH },
803 		{ "up",			UP },
804 		{ "vm",			VM }
805 	};
806 	const struct keywords	*p;
807 
808 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
809 	    sizeof(keywords[0]), kw_cmp);
810 
811 	if (p)
812 		return (p->k_val);
813 	else
814 		return (STRING);
815 }
816 
817 #define START_EXPAND	1
818 #define DONE_EXPAND	2
819 
820 static int	expanding;
821 
822 int
823 igetc(void)
824 {
825 	int	c;
826 
827 	while (1) {
828 		if (file->ungetpos > 0)
829 			c = file->ungetbuf[--file->ungetpos];
830 		else
831 			c = getc(file->stream);
832 
833 		if (c == START_EXPAND)
834 			expanding = 1;
835 		else if (c == DONE_EXPAND)
836 			expanding = 0;
837 		else
838 			break;
839 	}
840 	return (c);
841 }
842 
843 int
844 lgetc(int quotec)
845 {
846 	int		c, next;
847 
848 	if (quotec) {
849 		if ((c = igetc()) == EOF) {
850 			yyerror("reached end of file while parsing "
851 			    "quoted string");
852 			if (file == topfile || popfile() == EOF)
853 				return (EOF);
854 			return (quotec);
855 		}
856 		return (c);
857 	}
858 
859 	while ((c = igetc()) == '\\') {
860 		next = igetc();
861 		if (next != '\n') {
862 			c = next;
863 			break;
864 		}
865 		yylval.lineno = file->lineno;
866 		file->lineno++;
867 	}
868 	if (c == '\t' || c == ' ') {
869 		/* Compress blanks to a single space. */
870 		do {
871 			c = getc(file->stream);
872 		} while (c == '\t' || c == ' ');
873 		ungetc(c, file->stream);
874 		c = ' ';
875 	}
876 
877 	if (c == EOF) {
878 		/*
879 		 * Fake EOL when hit EOF for the first time. This gets line
880 		 * count right if last line in included file is syntactically
881 		 * invalid and has no newline.
882 		 */
883 		if (file->eof_reached == 0) {
884 			file->eof_reached = 1;
885 			return ('\n');
886 		}
887 		while (c == EOF) {
888 			if (file == topfile || popfile() == EOF)
889 				return (EOF);
890 			c = igetc();
891 		}
892 	}
893 	return (c);
894 }
895 
896 void
897 lungetc(int c)
898 {
899 	if (c == EOF)
900 		return;
901 
902 	if (file->ungetpos >= file->ungetsize) {
903 		void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
904 		if (p == NULL)
905 			err(1, "%s", __func__);
906 		file->ungetbuf = p;
907 		file->ungetsize *= 2;
908 	}
909 	file->ungetbuf[file->ungetpos++] = c;
910 }
911 
912 int
913 findeol(void)
914 {
915 	int	c;
916 
917 	/* skip to either EOF or the first real EOL */
918 	while (1) {
919 		c = lgetc(0);
920 		if (c == '\n') {
921 			file->lineno++;
922 			break;
923 		}
924 		if (c == EOF)
925 			break;
926 	}
927 	return (ERROR);
928 }
929 
930 int
931 yylex(void)
932 {
933 	char	 buf[8096];
934 	char	*p, *val;
935 	int	 quotec, next, c;
936 	int	 token;
937 
938 top:
939 	p = buf;
940 	while ((c = lgetc(0)) == ' ' || c == '\t')
941 		; /* nothing */
942 
943 	yylval.lineno = file->lineno;
944 	if (c == '#')
945 		while ((c = lgetc(0)) != '\n' && c != EOF)
946 			; /* nothing */
947 	if (c == '$' && !expanding) {
948 		while (1) {
949 			if ((c = lgetc(0)) == EOF)
950 				return (0);
951 
952 			if (p + 1 >= buf + sizeof(buf) - 1) {
953 				yyerror("string too long");
954 				return (findeol());
955 			}
956 			if (isalnum(c) || c == '_') {
957 				*p++ = c;
958 				continue;
959 			}
960 			*p = '\0';
961 			lungetc(c);
962 			break;
963 		}
964 		val = symget(buf);
965 		if (val == NULL) {
966 			yyerror("macro '%s' not defined", buf);
967 			return (findeol());
968 		}
969 		p = val + strlen(val) - 1;
970 		lungetc(DONE_EXPAND);
971 		while (p >= val) {
972 			lungetc((unsigned char)*p);
973 			p--;
974 		}
975 		lungetc(START_EXPAND);
976 		goto top;
977 	}
978 
979 	switch (c) {
980 	case '\'':
981 	case '"':
982 		quotec = c;
983 		while (1) {
984 			if ((c = lgetc(quotec)) == EOF)
985 				return (0);
986 			if (c == '\n') {
987 				file->lineno++;
988 				continue;
989 			} else if (c == '\\') {
990 				if ((next = lgetc(quotec)) == EOF)
991 					return (0);
992 				if (next == quotec || next == ' ' ||
993 				    next == '\t')
994 					c = next;
995 				else if (next == '\n') {
996 					file->lineno++;
997 					continue;
998 				} else
999 					lungetc(next);
1000 			} else if (c == quotec) {
1001 				*p = '\0';
1002 				break;
1003 			} else if (c == '\0') {
1004 				yyerror("syntax error");
1005 				return (findeol());
1006 			}
1007 			if (p + 1 >= buf + sizeof(buf) - 1) {
1008 				yyerror("string too long");
1009 				return (findeol());
1010 			}
1011 			*p++ = c;
1012 		}
1013 		yylval.v.string = strdup(buf);
1014 		if (yylval.v.string == NULL)
1015 			fatal("yylex: strdup");
1016 		return (STRING);
1017 	}
1018 
1019 #define allowed_to_end_number(x) \
1020 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
1021 
1022 	if (c == '-' || isdigit(c)) {
1023 		do {
1024 			*p++ = c;
1025 			if ((size_t)(p-buf) >= sizeof(buf)) {
1026 				yyerror("string too long");
1027 				return (findeol());
1028 			}
1029 		} while ((c = lgetc(0)) != EOF && isdigit(c));
1030 		lungetc(c);
1031 		if (p == buf + 1 && buf[0] == '-')
1032 			goto nodigits;
1033 		if (c == EOF || allowed_to_end_number(c)) {
1034 			const char *errstr = NULL;
1035 
1036 			*p = '\0';
1037 			yylval.v.number = strtonum(buf, LLONG_MIN,
1038 			    LLONG_MAX, &errstr);
1039 			if (errstr) {
1040 				yyerror("\"%s\" invalid number: %s",
1041 				    buf, errstr);
1042 				return (findeol());
1043 			}
1044 			return (NUMBER);
1045 		} else {
1046 nodigits:
1047 			while (p > buf + 1)
1048 				lungetc((unsigned char)*--p);
1049 			c = (unsigned char)*--p;
1050 			if (c == '-')
1051 				return (c);
1052 		}
1053 	}
1054 
1055 #define allowed_in_string(x) \
1056 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
1057 	x != '{' && x != '}' && \
1058 	x != '!' && x != '=' && x != '#' && \
1059 	x != ','))
1060 
1061 	if (isalnum(c) || c == ':' || c == '_' || c == '/') {
1062 		do {
1063 			*p++ = c;
1064 			if ((size_t)(p-buf) >= sizeof(buf)) {
1065 				yyerror("string too long");
1066 				return (findeol());
1067 			}
1068 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
1069 		lungetc(c);
1070 		*p = '\0';
1071 		if ((token = lookup(buf)) == STRING)
1072 			if ((yylval.v.string = strdup(buf)) == NULL)
1073 				fatal("yylex: strdup");
1074 		return (token);
1075 	}
1076 	if (c == '\n') {
1077 		yylval.lineno = file->lineno;
1078 		file->lineno++;
1079 	}
1080 	if (c == EOF)
1081 		return (0);
1082 	return (c);
1083 }
1084 
1085 struct file *
1086 pushfile(const char *name, int secret)
1087 {
1088 	struct file	*nfile;
1089 
1090 	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
1091 		log_warn("%s", __func__);
1092 		return (NULL);
1093 	}
1094 	if ((nfile->name = strdup(name)) == NULL) {
1095 		log_warn("%s", __func__);
1096 		free(nfile);
1097 		return (NULL);
1098 	}
1099 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
1100 		free(nfile->name);
1101 		free(nfile);
1102 		return (NULL);
1103 	}
1104 	nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
1105 	nfile->ungetsize = 16;
1106 	nfile->ungetbuf = malloc(nfile->ungetsize);
1107 	if (nfile->ungetbuf == NULL) {
1108 		log_warn("%s", __func__);
1109 		fclose(nfile->stream);
1110 		free(nfile->name);
1111 		free(nfile);
1112 		return (NULL);
1113 	}
1114 	TAILQ_INSERT_TAIL(&files, nfile, entry);
1115 	return (nfile);
1116 }
1117 
1118 int
1119 popfile(void)
1120 {
1121 	struct file	*prev;
1122 
1123 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
1124 		prev->errors += file->errors;
1125 
1126 	TAILQ_REMOVE(&files, file, entry);
1127 	fclose(file->stream);
1128 	free(file->name);
1129 	free(file->ungetbuf);
1130 	free(file);
1131 	file = prev;
1132 	return (file ? 0 : EOF);
1133 }
1134 
1135 int
1136 parse_config(const char *filename)
1137 {
1138 	struct sym	*sym, *next;
1139 
1140 	if ((file = pushfile(filename, 0)) == NULL) {
1141 		log_warn("failed to open %s", filename);
1142 		if (errno == ENOENT)
1143 			return (0);
1144 		return (-1);
1145 	}
1146 	topfile = file;
1147 	setservent(1);
1148 
1149 	/* Set the default switch type */
1150 	(void)strlcpy(vsw_type, VMD_SWITCH_TYPE, sizeof(vsw_type));
1151 
1152 	yyparse();
1153 	errors = file->errors;
1154 	popfile();
1155 
1156 	endservent();
1157 
1158 	/* Free macros and check which have not been used. */
1159 	TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
1160 		if (!sym->used)
1161 			fprintf(stderr, "warning: macro '%s' not "
1162 			    "used\n", sym->nam);
1163 		if (!sym->persist) {
1164 			free(sym->nam);
1165 			free(sym->val);
1166 			TAILQ_REMOVE(&symhead, sym, entry);
1167 			free(sym);
1168 		}
1169 	}
1170 
1171 	if (errors)
1172 		return (-1);
1173 
1174 	return (0);
1175 }
1176 
1177 int
1178 symset(const char *nam, const char *val, int persist)
1179 {
1180 	struct sym	*sym;
1181 
1182 	TAILQ_FOREACH(sym, &symhead, entry) {
1183 		if (strcmp(nam, sym->nam) == 0)
1184 			break;
1185 	}
1186 
1187 	if (sym != NULL) {
1188 		if (sym->persist == 1)
1189 			return (0);
1190 		else {
1191 			free(sym->nam);
1192 			free(sym->val);
1193 			TAILQ_REMOVE(&symhead, sym, entry);
1194 			free(sym);
1195 		}
1196 	}
1197 	if ((sym = calloc(1, sizeof(*sym))) == NULL)
1198 		return (-1);
1199 
1200 	sym->nam = strdup(nam);
1201 	if (sym->nam == NULL) {
1202 		free(sym);
1203 		return (-1);
1204 	}
1205 	sym->val = strdup(val);
1206 	if (sym->val == NULL) {
1207 		free(sym->nam);
1208 		free(sym);
1209 		return (-1);
1210 	}
1211 	sym->used = 0;
1212 	sym->persist = persist;
1213 	TAILQ_INSERT_TAIL(&symhead, sym, entry);
1214 	return (0);
1215 }
1216 
1217 int
1218 cmdline_symset(char *s)
1219 {
1220 	char	*sym, *val;
1221 	int	ret;
1222 
1223 	if ((val = strrchr(s, '=')) == NULL)
1224 		return (-1);
1225 	sym = strndup(s, val - s);
1226 	if (sym == NULL)
1227 		fatal("%s: strndup", __func__);
1228 	ret = symset(sym, val + 1, 1);
1229 	free(sym);
1230 
1231 	return (ret);
1232 }
1233 
1234 char *
1235 symget(const char *nam)
1236 {
1237 	struct sym	*sym;
1238 
1239 	TAILQ_FOREACH(sym, &symhead, entry) {
1240 		if (strcmp(nam, sym->nam) == 0) {
1241 			sym->used = 1;
1242 			return (sym->val);
1243 		}
1244 	}
1245 	return (NULL);
1246 }
1247 
1248 ssize_t
1249 parse_size(char *word, int64_t val)
1250 {
1251 	ssize_t		 size;
1252 	long long	 res;
1253 
1254 	if (word != NULL) {
1255 		if (scan_scaled(word, &res) != 0) {
1256 			log_warn("invalid size: %s", word);
1257 			return (-1);
1258 		}
1259 		val = (int64_t)res;
1260 	}
1261 
1262 	if (val < (1024 * 1024)) {
1263 		log_warnx("size must be at least one megabyte");
1264 		return (-1);
1265 	} else
1266 		size = val / 1024 / 1024;
1267 
1268 	if ((size * 1024 * 1024) != val)
1269 		log_warnx("size rounded to %zd megabytes", size);
1270 
1271 	return ((ssize_t)size);
1272 }
1273 
1274 int
1275 parse_disk(char *word, int type)
1276 {
1277 	char	 buf[BUFSIZ], path[PATH_MAX];
1278 	int	 fd;
1279 	ssize_t	 len;
1280 
1281 	if (vcp->vcp_ndisks >= VMM_MAX_DISKS_PER_VM) {
1282 		log_warnx("too many disks");
1283 		return (-1);
1284 	}
1285 
1286 	if (realpath(word, path) == NULL) {
1287 		log_warn("disk %s", word);
1288 		return (-1);
1289 	}
1290 
1291 	if (!type) {
1292 		/* Use raw as the default format */
1293 		type = VMDF_RAW;
1294 
1295 		/* Try to derive the format from the file signature */
1296 		if ((fd = open(path, O_RDONLY)) != -1) {
1297 			len = read(fd, buf, sizeof(buf));
1298 			close(fd);
1299 			if (len >= (ssize_t)strlen(VM_MAGIC_QCOW) &&
1300 			    strncmp(buf, VM_MAGIC_QCOW,
1301 			    strlen(VM_MAGIC_QCOW)) == 0) {
1302 				/* The qcow version will be checked later */
1303 				type = VMDF_QCOW2;
1304 			}
1305 		}
1306 	}
1307 
1308 	if (strlcpy(vcp->vcp_disks[vcp->vcp_ndisks], path,
1309 	    VMM_MAX_PATH_DISK) >= VMM_MAX_PATH_DISK) {
1310 		log_warnx("disk path too long");
1311 		return (-1);
1312 	}
1313 	vmc.vmc_disktypes[vcp->vcp_ndisks] = type;
1314 
1315 	vcp->vcp_ndisks++;
1316 
1317 	return (0);
1318 }
1319 
1320 unsigned int
1321 parse_format(const char *word)
1322 {
1323 	if (strcasecmp(word, "raw") == 0)
1324 		return (VMDF_RAW);
1325 	else if (strcasecmp(word, "qcow2") == 0)
1326 		return (VMDF_QCOW2);
1327 	return (0);
1328 }
1329 
1330 int
1331 host(const char *str, struct address *h)
1332 {
1333 	struct addrinfo		 hints, *res;
1334 	int			 prefixlen;
1335 	char			*s, *p;
1336 	const char		*errstr;
1337 
1338 	if ((s = strdup(str)) == NULL) {
1339 		log_warn("%s", __func__);
1340 		goto fail;
1341 	}
1342 
1343 	if ((p = strrchr(s, '/')) != NULL) {
1344 		*p++ = '\0';
1345 		prefixlen = strtonum(p, 0, 128, &errstr);
1346 		if (errstr) {
1347 			log_warnx("prefixlen is %s: %s", errstr, p);
1348 			goto fail;
1349 		}
1350 	} else
1351 		prefixlen = 128;
1352 
1353 	memset(&hints, 0, sizeof(hints));
1354 	hints.ai_family = AF_UNSPEC;
1355 	hints.ai_flags = AI_NUMERICHOST;
1356 	if (getaddrinfo(s, NULL, &hints, &res) == 0) {
1357 		memset(h, 0, sizeof(*h));
1358 		memcpy(&h->ss, res->ai_addr, res->ai_addrlen);
1359 		h->prefixlen = prefixlen;
1360 		freeaddrinfo(res);
1361 		free(s);
1362 		return (0);
1363 	}
1364 
1365  fail:
1366 	free(s);
1367 	return (-1);
1368 }
1369