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