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