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 45TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); 46static struct file { 47 TAILQ_ENTRY(file) entry; 48 FILE *stream; 49 char *name; 50 int lineno; 51 int errors; 52} *file, *topfile; 53struct file *pushfile(const char *); 54int popfile(void); 55int yyparse(void); 56int yylex(void); 57int yyerror(const char *, ...) 58 __attribute__((__format__ (printf, 1, 2))) 59 __attribute__((__nonnull__ (1))); 60int kw_cmp(const void *, const void *); 61int lookup(char *); 62int lgetc(int); 63int lungetc(int); 64int findeol(void); 65 66struct ldom_config *conf; 67struct domain *domain; 68 69struct vcpu_opts { 70 uint64_t count; 71 uint64_t stride; 72} vcpu_opts; 73 74struct vdisk_opts { 75 const char *devalias; 76} vdisk_opts; 77 78struct vnet_opts { 79 uint64_t mac_addr; 80 uint64_t mtu; 81 const char *devalias; 82} vnet_opts; 83 84void vcpu_opts_default(void); 85void vdisk_opts_default(void); 86void vnet_opts_default(void); 87 88typedef 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 117grammar : /* empty */ 118 | grammar '\n' 119 | grammar domain '\n' 120 | grammar error '\n' { file->errors++; } 121 ; 122 123domain : 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 162domainopts_l : domainopts_l domainoptsl 163 | domainoptsl 164 ; 165 166domainoptsl : domainopts nl 167 ; 168 169domainopts : 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 234vdisk_opts : { vdisk_opts_default(); } 235 vdisk_opts_l 236 { $$ = vdisk_opts; } 237 | { vdisk_opts_default(); $$ = vdisk_opts; } 238 ; 239vdisk_opts_l : vdisk_opts_l vdisk_opt 240 | vdisk_opt 241 ; 242vdisk_opt : vdisk_devalias 243 ; 244 245vdisk_devalias : DEVALIAS '=' STRING { 246 vdisk_opts.devalias = $3; 247 } 248 ; 249 250vnet_opts : { vnet_opts_default(); } 251 vnet_opts_l 252 { $$ = vnet_opts; } 253 | { vnet_opts_default(); $$ = vnet_opts; } 254 ; 255vnet_opts_l : vnet_opts_l vnet_opt 256 | vnet_opt 257 ; 258vnet_opt : mac_addr 259 | mtu 260 | vnet_devalias 261 ; 262 263mac_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 281mtu : MTU '=' NUMBER { 282 vnet_opts.mtu = $3; 283 } 284 ; 285 286vnet_devalias : DEVALIAS '=' STRING { 287 vnet_opts.devalias = $3; 288 } 289 ; 290 291vcpu : 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 321memory : NUMBER { 322 $$ = $1; 323 } 324 | STRING { 325 if (scan_scaled($1, &$$) == -1) { 326 yyerror("invalid size: %s", $1); 327 YYERROR; 328 } 329 } 330 ; 331 332optnl : '\n' optnl 333 | 334 ; 335 336nl : '\n' optnl /* one newline or more */ 337 ; 338 339%% 340 341void 342vcpu_opts_default(void) 343{ 344 vcpu_opts.count = -1; 345 vcpu_opts.stride = 1; 346} 347 348void 349vdisk_opts_default(void) 350{ 351 vdisk_opts.devalias = NULL; 352} 353 354void 355vnet_opts_default(void) 356{ 357 vnet_opts.mac_addr = -1; 358 vnet_opts.mtu = 1500; 359 vnet_opts.devalias = NULL; 360} 361 362struct keywords { 363 const char *k_name; 364 int k_val; 365}; 366 367int 368yyerror(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 381int 382kw_cmp(const void *k, const void *e) 383{ 384 return (strcmp(k, ((const struct keywords *)e)->k_name)); 385} 386 387int 388lookup(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 416char *parsebuf; 417int parseindex; 418char pushback_buffer[MAXPUSHBACK]; 419int pushback_index = 0; 420 421int 422lgetc(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 469int 470lungetc(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 485int 486findeol(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 508int 509yylex(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 { 590nodigits: 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 628struct file * 629pushfile(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 646int 647popfile(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 662int 663parse_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