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