1 /* $OpenBSD: cmd-parse.y,v 1.17 2019/06/18 11:17:40 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 %{ 20 21 #include <sys/types.h> 22 23 #include <ctype.h> 24 #include <errno.h> 25 #include <pwd.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 30 #include "tmux.h" 31 32 static int yylex(void); 33 static int yyparse(void); 34 static int printflike(1,2) yyerror(const char *, ...); 35 36 static char *yylex_token(int); 37 static char *yylex_format(void); 38 39 struct cmd_parse_scope { 40 int flag; 41 TAILQ_ENTRY (cmd_parse_scope) entry; 42 }; 43 44 struct cmd_parse_command { 45 char *name; 46 u_int line; 47 48 int argc; 49 char **argv; 50 51 TAILQ_ENTRY(cmd_parse_command) entry; 52 }; 53 TAILQ_HEAD(cmd_parse_commands, cmd_parse_command); 54 55 struct cmd_parse_state { 56 FILE *f; 57 58 const char *buf; 59 size_t len; 60 size_t off; 61 62 int condition; 63 int eol; 64 int eof; 65 struct cmd_parse_input *input; 66 u_int escapes; 67 68 char *error; 69 struct cmd_parse_commands *commands; 70 71 struct cmd_parse_scope *scope; 72 TAILQ_HEAD(, cmd_parse_scope) stack; 73 }; 74 static struct cmd_parse_state parse_state; 75 76 static char *cmd_parse_get_error(const char *, u_int, const char *); 77 static void cmd_parse_free_command(struct cmd_parse_command *); 78 static struct cmd_parse_commands *cmd_parse_new_commands(void); 79 static void cmd_parse_free_commands(struct cmd_parse_commands *); 80 static void cmd_parse_print_commands(struct cmd_parse_input *, u_int, 81 struct cmd_list *); 82 83 %} 84 85 %union 86 { 87 char *token; 88 struct { 89 int argc; 90 char **argv; 91 } arguments; 92 int flag; 93 struct { 94 int flag; 95 struct cmd_parse_commands *commands; 96 } elif; 97 struct cmd_parse_commands *commands; 98 struct cmd_parse_command *command; 99 } 100 101 %token ERROR 102 %token IF 103 %token ELSE 104 %token ELIF 105 %token ENDIF 106 %token <token> FORMAT TOKEN EQUALS 107 108 %type <token> argument expanded format 109 %type <arguments> arguments 110 %type <flag> if_open if_elif 111 %type <elif> elif elif1 112 %type <commands> statements statement commands condition condition1 113 %type <command> command 114 115 %% 116 117 lines : /* empty */ 118 | statements 119 { 120 struct cmd_parse_state *ps = &parse_state; 121 122 ps->commands = $1; 123 } 124 125 statements : statement '\n' 126 { 127 $$ = $1; 128 } 129 | statements statement '\n' 130 { 131 $$ = $1; 132 TAILQ_CONCAT($$, $2, entry); 133 free($2); 134 } 135 136 statement : condition 137 { 138 struct cmd_parse_state *ps = &parse_state; 139 140 if (ps->scope == NULL || ps->scope->flag) 141 $$ = $1; 142 else { 143 $$ = cmd_parse_new_commands(); 144 cmd_parse_free_commands($1); 145 } 146 } 147 | assignment 148 { 149 $$ = xmalloc (sizeof *$$); 150 TAILQ_INIT($$); 151 } 152 | commands 153 { 154 struct cmd_parse_state *ps = &parse_state; 155 156 if (ps->scope == NULL || ps->scope->flag) 157 $$ = $1; 158 else { 159 $$ = cmd_parse_new_commands(); 160 cmd_parse_free_commands($1); 161 } 162 } 163 164 format : FORMAT 165 { 166 $$ = $1; 167 } 168 | TOKEN 169 { 170 $$ = $1; 171 } 172 173 expanded : format 174 { 175 struct cmd_parse_state *ps = &parse_state; 176 struct cmd_parse_input *pi = ps->input; 177 struct format_tree *ft; 178 struct client *c = pi->c; 179 struct cmd_find_state *fs; 180 int flags = FORMAT_NOJOBS; 181 182 if (cmd_find_valid_state(&pi->fs)) 183 fs = &pi->fs; 184 else 185 fs = NULL; 186 ft = format_create(NULL, pi->item, FORMAT_NONE, flags); 187 if (fs != NULL) 188 format_defaults(ft, c, fs->s, fs->wl, fs->wp); 189 else 190 format_defaults(ft, c, NULL, NULL, NULL); 191 192 $$ = format_expand(ft, $1); 193 format_free(ft); 194 free($1); 195 } 196 197 assignment : /* empty */ 198 | EQUALS 199 { 200 struct cmd_parse_state *ps = &parse_state; 201 int flags = ps->input->flags; 202 203 if ((~flags & CMD_PARSE_PARSEONLY) && 204 (ps->scope == NULL || ps->scope->flag)) 205 environ_put(global_environ, $1); 206 free($1); 207 } 208 209 if_open : IF expanded 210 { 211 struct cmd_parse_state *ps = &parse_state; 212 struct cmd_parse_scope *scope; 213 214 scope = xmalloc(sizeof *scope); 215 $$ = scope->flag = format_true($2); 216 free($2); 217 218 if (ps->scope != NULL) 219 TAILQ_INSERT_HEAD(&ps->stack, ps->scope, entry); 220 ps->scope = scope; 221 } 222 223 if_else : ELSE 224 { 225 struct cmd_parse_state *ps = &parse_state; 226 struct cmd_parse_scope *scope; 227 228 scope = xmalloc(sizeof *scope); 229 scope->flag = !ps->scope->flag; 230 231 free(ps->scope); 232 ps->scope = scope; 233 } 234 235 if_elif : ELIF expanded 236 { 237 struct cmd_parse_state *ps = &parse_state; 238 struct cmd_parse_scope *scope; 239 240 scope = xmalloc(sizeof *scope); 241 $$ = scope->flag = format_true($2); 242 free($2); 243 244 free(ps->scope); 245 ps->scope = scope; 246 } 247 248 if_close : ENDIF 249 { 250 struct cmd_parse_state *ps = &parse_state; 251 252 free(ps->scope); 253 ps->scope = TAILQ_FIRST(&ps->stack); 254 if (ps->scope != NULL) 255 TAILQ_REMOVE(&ps->stack, ps->scope, entry); 256 } 257 258 condition : if_open '\n' statements if_close 259 { 260 if ($1) 261 $$ = $3; 262 else { 263 $$ = cmd_parse_new_commands(); 264 cmd_parse_free_commands($3); 265 } 266 } 267 | if_open '\n' statements if_else '\n' statements if_close 268 { 269 if ($1) { 270 $$ = $3; 271 cmd_parse_free_commands($6); 272 } else { 273 $$ = $6; 274 cmd_parse_free_commands($3); 275 } 276 } 277 | if_open '\n' statements elif if_close 278 { 279 if ($1) { 280 $$ = $3; 281 cmd_parse_free_commands($4.commands); 282 } else if ($4.flag) { 283 $$ = $4.commands; 284 cmd_parse_free_commands($3); 285 } else { 286 $$ = cmd_parse_new_commands(); 287 cmd_parse_free_commands($3); 288 cmd_parse_free_commands($4.commands); 289 } 290 } 291 | if_open '\n' statements elif if_else '\n' statements if_close 292 { 293 if ($1) { 294 $$ = $3; 295 cmd_parse_free_commands($4.commands); 296 cmd_parse_free_commands($7); 297 } else if ($4.flag) { 298 $$ = $4.commands; 299 cmd_parse_free_commands($3); 300 cmd_parse_free_commands($7); 301 } else { 302 $$ = $7; 303 cmd_parse_free_commands($3); 304 cmd_parse_free_commands($4.commands); 305 } 306 } 307 308 elif : if_elif '\n' statements 309 { 310 if ($1) { 311 $$.flag = 1; 312 $$.commands = $3; 313 } else { 314 $$.flag = 0; 315 $$.commands = cmd_parse_new_commands(); 316 cmd_parse_free_commands($3); 317 } 318 } 319 | if_elif '\n' statements elif 320 { 321 if ($1) { 322 $$.flag = 1; 323 $$.commands = $3; 324 cmd_parse_free_commands($4.commands); 325 } else if ($4.flag) { 326 $$.flag = 1; 327 $$.commands = $4.commands; 328 cmd_parse_free_commands($3); 329 } else { 330 $$.flag = 0; 331 $$.commands = cmd_parse_new_commands(); 332 cmd_parse_free_commands($3); 333 cmd_parse_free_commands($4.commands); 334 } 335 } 336 337 commands : command 338 { 339 struct cmd_parse_state *ps = &parse_state; 340 341 $$ = cmd_parse_new_commands(); 342 if (ps->scope == NULL || ps->scope->flag) 343 TAILQ_INSERT_TAIL($$, $1, entry); 344 else 345 cmd_parse_free_command($1); 346 } 347 | commands ';' 348 { 349 $$ = $1; 350 } 351 | commands ';' condition1 352 { 353 $$ = $1; 354 TAILQ_CONCAT($$, $3, entry); 355 free($3); 356 } 357 | commands ';' command 358 { 359 struct cmd_parse_state *ps = &parse_state; 360 361 if (ps->scope == NULL || ps->scope->flag) { 362 $$ = $1; 363 TAILQ_INSERT_TAIL($$, $3, entry); 364 } else { 365 $$ = cmd_parse_new_commands(); 366 cmd_parse_free_commands($1); 367 cmd_parse_free_command($3); 368 } 369 } 370 | condition1 371 { 372 $$ = $1; 373 } 374 375 command : assignment TOKEN 376 { 377 struct cmd_parse_state *ps = &parse_state; 378 379 $$ = xcalloc(1, sizeof *$$); 380 $$->name = $2; 381 $$->line = ps->input->line; 382 383 } 384 | assignment TOKEN arguments 385 { 386 struct cmd_parse_state *ps = &parse_state; 387 388 $$ = xcalloc(1, sizeof *$$); 389 $$->name = $2; 390 $$->line = ps->input->line; 391 392 $$->argc = $3.argc; 393 $$->argv = $3.argv; 394 } 395 396 condition1 : if_open commands if_close 397 { 398 if ($1) 399 $$ = $2; 400 else { 401 $$ = cmd_parse_new_commands(); 402 cmd_parse_free_commands($2); 403 } 404 } 405 | if_open commands if_else commands if_close 406 { 407 if ($1) { 408 $$ = $2; 409 cmd_parse_free_commands($4); 410 } else { 411 $$ = $4; 412 cmd_parse_free_commands($2); 413 } 414 } 415 | if_open commands elif1 if_close 416 { 417 if ($1) { 418 $$ = $2; 419 cmd_parse_free_commands($3.commands); 420 } else if ($3.flag) { 421 $$ = $3.commands; 422 cmd_parse_free_commands($2); 423 } else { 424 $$ = cmd_parse_new_commands(); 425 cmd_parse_free_commands($2); 426 cmd_parse_free_commands($3.commands); 427 } 428 } 429 | if_open commands elif1 if_else commands if_close 430 { 431 if ($1) { 432 $$ = $2; 433 cmd_parse_free_commands($3.commands); 434 cmd_parse_free_commands($5); 435 } else if ($3.flag) { 436 $$ = $3.commands; 437 cmd_parse_free_commands($2); 438 cmd_parse_free_commands($5); 439 } else { 440 $$ = $5; 441 cmd_parse_free_commands($2); 442 cmd_parse_free_commands($3.commands); 443 } 444 } 445 446 elif1 : if_elif commands 447 { 448 if ($1) { 449 $$.flag = 1; 450 $$.commands = $2; 451 } else { 452 $$.flag = 0; 453 $$.commands = cmd_parse_new_commands(); 454 cmd_parse_free_commands($2); 455 } 456 } 457 | if_elif commands elif1 458 { 459 if ($1) { 460 $$.flag = 1; 461 $$.commands = $2; 462 cmd_parse_free_commands($3.commands); 463 } else if ($3.flag) { 464 $$.flag = 1; 465 $$.commands = $3.commands; 466 cmd_parse_free_commands($2); 467 } else { 468 $$.flag = 0; 469 $$.commands = cmd_parse_new_commands(); 470 cmd_parse_free_commands($2); 471 cmd_parse_free_commands($3.commands); 472 } 473 } 474 475 arguments : argument 476 { 477 $$.argc = 1; 478 $$.argv = xreallocarray(NULL, 1, sizeof *$$.argv); 479 480 $$.argv[0] = $1; 481 } 482 | argument arguments 483 { 484 cmd_prepend_argv(&$2.argc, &$2.argv, $1); 485 free($1); 486 $$ = $2; 487 } 488 489 argument : TOKEN 490 { 491 $$ = $1; 492 } 493 | EQUALS 494 { 495 $$ = $1; 496 } 497 498 %% 499 500 static char * 501 cmd_parse_get_error(const char *file, u_int line, const char *error) 502 { 503 char *s; 504 505 if (file == NULL) 506 s = xstrdup(error); 507 else 508 xasprintf (&s, "%s:%u: %s", file, line, error); 509 return (s); 510 } 511 512 static void 513 cmd_parse_print_commands(struct cmd_parse_input *pi, u_int line, 514 struct cmd_list *cmdlist) 515 { 516 char *s; 517 518 if (pi->item != NULL && (pi->flags & CMD_PARSE_VERBOSE)) { 519 s = cmd_list_print(cmdlist, 0); 520 if (pi->file != NULL) 521 cmdq_print(pi->item, "%s:%u: %s", pi->file, line, s); 522 else 523 cmdq_print(pi->item, "%u: %s", line, s); 524 free(s); 525 } 526 } 527 528 static void 529 cmd_parse_free_command(struct cmd_parse_command *cmd) 530 { 531 free(cmd->name); 532 cmd_free_argv(cmd->argc, cmd->argv); 533 free(cmd); 534 } 535 536 static struct cmd_parse_commands * 537 cmd_parse_new_commands(void) 538 { 539 struct cmd_parse_commands *cmds; 540 541 cmds = xmalloc(sizeof *cmds); 542 TAILQ_INIT (cmds); 543 return (cmds); 544 } 545 546 static void 547 cmd_parse_free_commands(struct cmd_parse_commands *cmds) 548 { 549 struct cmd_parse_command *cmd, *cmd1; 550 551 TAILQ_FOREACH_SAFE(cmd, cmds, entry, cmd1) { 552 TAILQ_REMOVE(cmds, cmd, entry); 553 cmd_parse_free_command(cmd); 554 } 555 free(cmds); 556 } 557 558 static struct cmd_parse_commands * 559 cmd_parse_run_parser(char **cause) 560 { 561 struct cmd_parse_state *ps = &parse_state; 562 struct cmd_parse_scope *scope, *scope1; 563 int retval; 564 565 ps->commands = NULL; 566 TAILQ_INIT(&ps->stack); 567 568 retval = yyparse(); 569 TAILQ_FOREACH_SAFE(scope, &ps->stack, entry, scope1) { 570 TAILQ_REMOVE(&ps->stack, scope, entry); 571 free(scope); 572 } 573 if (retval != 0) { 574 *cause = ps->error; 575 return (NULL); 576 } 577 578 if (ps->commands == NULL) 579 return (cmd_parse_new_commands()); 580 return (ps->commands); 581 } 582 583 static struct cmd_parse_commands * 584 cmd_parse_do_file(FILE *f, struct cmd_parse_input *pi, char **cause) 585 { 586 struct cmd_parse_state *ps = &parse_state; 587 588 memset(ps, 0, sizeof *ps); 589 ps->input = pi; 590 ps->f = f; 591 return (cmd_parse_run_parser(cause)); 592 } 593 594 static struct cmd_parse_commands * 595 cmd_parse_do_buffer(const char *buf, size_t len, struct cmd_parse_input *pi, 596 char **cause) 597 { 598 struct cmd_parse_state *ps = &parse_state; 599 600 memset(ps, 0, sizeof *ps); 601 ps->input = pi; 602 ps->buf = buf; 603 ps->len = len; 604 return (cmd_parse_run_parser(cause)); 605 } 606 607 static struct cmd_parse_result * 608 cmd_parse_build_commands(struct cmd_parse_commands *cmds, 609 struct cmd_parse_input *pi) 610 { 611 static struct cmd_parse_result pr; 612 struct cmd_parse_commands *cmds2; 613 struct cmd_parse_command *cmd, *cmd2, *next, *next2, *after; 614 u_int line = UINT_MAX; 615 int i; 616 struct cmd_list *cmdlist = NULL, *result; 617 struct cmd *add; 618 char *alias, *cause, *s; 619 620 /* Check for an empty list. */ 621 if (TAILQ_EMPTY(cmds)) { 622 cmd_parse_free_commands(cmds); 623 pr.status = CMD_PARSE_EMPTY; 624 return (&pr); 625 } 626 627 /* 628 * Walk the commands and expand any aliases. Each alias is parsed 629 * individually to a new command list, any trailing arguments appended 630 * to the last command, and all commands inserted into the original 631 * command list. 632 */ 633 TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) { 634 alias = cmd_get_alias(cmd->name); 635 if (alias == NULL) 636 continue; 637 638 line = cmd->line; 639 log_debug("%s: %u %s = %s", __func__, line, cmd->name, alias); 640 641 pi->line = line; 642 cmds2 = cmd_parse_do_buffer(alias, strlen(alias), pi, &cause); 643 free(alias); 644 if (cmds2 == NULL) { 645 pr.status = CMD_PARSE_ERROR; 646 pr.error = cause; 647 goto out; 648 } 649 650 cmd2 = TAILQ_LAST(cmds2, cmd_parse_commands); 651 if (cmd2 == NULL) { 652 TAILQ_REMOVE(cmds, cmd, entry); 653 cmd_parse_free_command(cmd); 654 continue; 655 } 656 for (i = 0; i < cmd->argc; i++) 657 cmd_append_argv(&cmd2->argc, &cmd2->argv, cmd->argv[i]); 658 659 after = cmd; 660 TAILQ_FOREACH_SAFE(cmd2, cmds2, entry, next2) { 661 cmd2->line = line; 662 TAILQ_REMOVE(cmds2, cmd2, entry); 663 TAILQ_INSERT_AFTER(cmds, after, cmd2, entry); 664 after = cmd2; 665 } 666 cmd_parse_free_commands(cmds2); 667 668 TAILQ_REMOVE(cmds, cmd, entry); 669 cmd_parse_free_command(cmd); 670 } 671 672 /* 673 * Parse each command into a command list. Create a new command list 674 * for each line so they get a new group (so the queue knows which ones 675 * to remove if a command fails when executed). 676 */ 677 result = cmd_list_new(); 678 TAILQ_FOREACH(cmd, cmds, entry) { 679 log_debug("%s: %u %s", __func__, cmd->line, cmd->name); 680 cmd_log_argv(cmd->argc, cmd->argv, __func__); 681 682 if (cmdlist == NULL || cmd->line != line) { 683 if (cmdlist != NULL) { 684 cmd_parse_print_commands(pi, line, cmdlist); 685 cmd_list_move(result, cmdlist); 686 cmd_list_free(cmdlist); 687 } 688 cmdlist = cmd_list_new(); 689 } 690 line = cmd->line; 691 692 cmd_prepend_argv(&cmd->argc, &cmd->argv, cmd->name); 693 add = cmd_parse(cmd->argc, cmd->argv, pi->file, line, &cause); 694 if (add == NULL) { 695 cmd_list_free(result); 696 pr.status = CMD_PARSE_ERROR; 697 pr.error = cmd_parse_get_error(pi->file, line, cause); 698 free(cause); 699 goto out; 700 } 701 cmd_list_append(cmdlist, add); 702 } 703 if (cmdlist != NULL) { 704 cmd_parse_print_commands(pi, line, cmdlist); 705 cmd_list_move(result, cmdlist); 706 cmd_list_free(cmdlist); 707 } 708 709 s = cmd_list_print(result, 0); 710 log_debug("%s: %s", __func__, s); 711 free(s); 712 713 pr.status = CMD_PARSE_SUCCESS; 714 pr.cmdlist = result; 715 716 out: 717 cmd_parse_free_commands(cmds); 718 719 return (&pr); 720 } 721 722 struct cmd_parse_result * 723 cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi) 724 { 725 static struct cmd_parse_result pr; 726 struct cmd_parse_input input; 727 struct cmd_parse_commands *cmds; 728 char *cause; 729 730 if (pi == NULL) { 731 memset(&input, 0, sizeof input); 732 pi = &input; 733 } 734 memset(&pr, 0, sizeof pr); 735 736 cmds = cmd_parse_do_file(f, pi, &cause); 737 if (cmds == NULL) { 738 pr.status = CMD_PARSE_ERROR; 739 pr.error = cause; 740 return (&pr); 741 } 742 return (cmd_parse_build_commands(cmds, pi)); 743 } 744 745 struct cmd_parse_result * 746 cmd_parse_from_string(const char *s, struct cmd_parse_input *pi) 747 { 748 static struct cmd_parse_result pr; 749 struct cmd_parse_input input; 750 struct cmd_parse_commands *cmds; 751 char *cause; 752 753 if (pi == NULL) { 754 memset(&input, 0, sizeof input); 755 pi = &input; 756 } 757 memset(&pr, 0, sizeof pr); 758 759 if (*s == '\0') { 760 pr.status = CMD_PARSE_EMPTY; 761 pr.cmdlist = NULL; 762 pr.error = NULL; 763 return (&pr); 764 } 765 766 cmds = cmd_parse_do_buffer(s, strlen(s), pi, &cause); 767 if (cmds == NULL) { 768 pr.status = CMD_PARSE_ERROR; 769 pr.error = cause; 770 return (&pr); 771 } 772 return (cmd_parse_build_commands(cmds, pi)); 773 } 774 775 struct cmd_parse_result * 776 cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi) 777 { 778 struct cmd_parse_input input; 779 struct cmd_parse_commands *cmds; 780 struct cmd_parse_command *cmd; 781 char **copy, **new_argv; 782 size_t size; 783 int i, last, new_argc; 784 785 /* 786 * The commands are already split up into arguments, so just separate 787 * into a set of commands by ';'. 788 */ 789 790 if (pi == NULL) { 791 memset(&input, 0, sizeof input); 792 pi = &input; 793 } 794 cmd_log_argv(argc, argv, "%s", __func__); 795 796 cmds = cmd_parse_new_commands(); 797 copy = cmd_copy_argv(argc, argv); 798 799 last = 0; 800 for (i = 0; i < argc; i++) { 801 size = strlen(copy[i]); 802 if (size == 0 || copy[i][size - 1] != ';') 803 continue; 804 copy[i][--size] = '\0'; 805 if (size > 0 && copy[i][size - 1] == '\\') { 806 copy[i][size - 1] = ';'; 807 continue; 808 } 809 810 new_argc = i - last; 811 new_argv = copy + last; 812 if (size != 0) 813 new_argc++; 814 815 if (new_argc != 0) { 816 cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__, 817 i); 818 819 cmd = xcalloc(1, sizeof *cmd); 820 cmd->name = xstrdup(new_argv[0]); 821 cmd->line = pi->line; 822 823 cmd->argc = new_argc - 1; 824 cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1); 825 826 TAILQ_INSERT_TAIL(cmds, cmd, entry); 827 } 828 829 last = i + 1; 830 } 831 if (last != argc) { 832 new_argv = copy + last; 833 new_argc = argc - last; 834 835 if (new_argc != 0) { 836 cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__, 837 last); 838 839 cmd = xcalloc(1, sizeof *cmd); 840 cmd->name = xstrdup(new_argv[0]); 841 cmd->line = pi->line; 842 843 cmd->argc = new_argc - 1; 844 cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1); 845 846 TAILQ_INSERT_TAIL(cmds, cmd, entry); 847 } 848 } 849 850 cmd_free_argv(argc, copy); 851 return (cmd_parse_build_commands(cmds, pi)); 852 } 853 854 static int printflike(1, 2) 855 yyerror(const char *fmt, ...) 856 { 857 struct cmd_parse_state *ps = &parse_state; 858 struct cmd_parse_input *pi = ps->input; 859 va_list ap; 860 char *error; 861 862 if (ps->error != NULL) 863 return (0); 864 865 va_start(ap, fmt); 866 xvasprintf(&error, fmt, ap); 867 va_end(ap); 868 869 ps->error = cmd_parse_get_error(pi->file, pi->line, error); 870 free(error); 871 return (0); 872 } 873 874 static int 875 yylex_is_var(char ch, int first) 876 { 877 if (ch == '=') 878 return (0); 879 if (first && isdigit((u_char)ch)) 880 return (0); 881 return (isalnum((u_char)ch) || ch == '_'); 882 } 883 884 static void 885 yylex_append(char **buf, size_t *len, const char *add, size_t addlen) 886 { 887 if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen) 888 fatalx("buffer is too big"); 889 *buf = xrealloc(*buf, (*len) + 1 + addlen); 890 memcpy((*buf) + *len, add, addlen); 891 (*len) += addlen; 892 } 893 894 static void 895 yylex_append1(char **buf, size_t *len, char add) 896 { 897 yylex_append(buf, len, &add, 1); 898 } 899 900 static int 901 yylex_getc1(void) 902 { 903 struct cmd_parse_state *ps = &parse_state; 904 int ch; 905 906 if (ps->f != NULL) 907 ch = getc(ps->f); 908 else { 909 if (ps->off == ps->len) 910 ch = EOF; 911 else 912 ch = ps->buf[ps->off++]; 913 } 914 return (ch); 915 } 916 917 static void 918 yylex_ungetc(int ch) 919 { 920 struct cmd_parse_state *ps = &parse_state; 921 922 if (ps->f != NULL) 923 ungetc(ch, ps->f); 924 else if (ps->off > 0 && ch != EOF) 925 ps->off--; 926 } 927 928 static int 929 yylex_getc(void) 930 { 931 struct cmd_parse_state *ps = &parse_state; 932 int ch; 933 934 if (ps->escapes != 0) { 935 ps->escapes--; 936 return ('\\'); 937 } 938 for (;;) { 939 ch = yylex_getc1(); 940 if (ch == '\\') { 941 ps->escapes++; 942 continue; 943 } 944 if (ch == '\n' && (ps->escapes % 2) == 1) { 945 ps->input->line++; 946 ps->escapes--; 947 continue; 948 } 949 950 if (ps->escapes != 0) { 951 yylex_ungetc(ch); 952 ps->escapes--; 953 return ('\\'); 954 } 955 return (ch); 956 } 957 } 958 959 static char * 960 yylex_get_word(int ch) 961 { 962 char *buf; 963 size_t len; 964 965 len = 0; 966 buf = xmalloc(1); 967 968 do 969 yylex_append1(&buf, &len, ch); 970 while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL); 971 yylex_ungetc(ch); 972 973 buf[len] = '\0'; 974 log_debug("%s: %s", __func__, buf); 975 return (buf); 976 } 977 978 static int 979 yylex(void) 980 { 981 struct cmd_parse_state *ps = &parse_state; 982 char *token, *cp; 983 int ch, next, condition; 984 985 if (ps->eol) 986 ps->input->line++; 987 ps->eol = 0; 988 989 condition = ps->condition; 990 ps->condition = 0; 991 992 for (;;) { 993 ch = yylex_getc(); 994 995 if (ch == EOF) { 996 /* 997 * Ensure every file or string is terminated by a 998 * newline. This keeps the parser simpler and avoids 999 * having to add a newline to each string. 1000 */ 1001 if (ps->eof) 1002 break; 1003 ps->eof = 1; 1004 return ('\n'); 1005 } 1006 1007 if (ch == ' ' || ch == '\t') { 1008 /* 1009 * Ignore whitespace. 1010 */ 1011 continue; 1012 } 1013 1014 if (ch == '\n') { 1015 /* 1016 * End of line. Update the line number. 1017 */ 1018 ps->eol = 1; 1019 return ('\n'); 1020 } 1021 1022 if (ch == ';') { 1023 /* 1024 * A semicolon is itself. 1025 */ 1026 return (';'); 1027 } 1028 1029 if (ch == '#') { 1030 /* 1031 * #{ after a condition opens a format; anything else 1032 * is a comment, ignore up to the end of the line. 1033 */ 1034 next = yylex_getc(); 1035 if (condition && next == '{') { 1036 yylval.token = yylex_format(); 1037 if (yylval.token == NULL) 1038 return (ERROR); 1039 return (FORMAT); 1040 } 1041 while (next != '\n' && next != EOF) 1042 next = yylex_getc(); 1043 if (next == '\n') { 1044 ps->input->line++; 1045 return ('\n'); 1046 } 1047 continue; 1048 } 1049 1050 if (ch == '%') { 1051 /* 1052 * % is a condition unless it is all % or all numbers, 1053 * then it is a token. 1054 */ 1055 yylval.token = yylex_get_word('%'); 1056 for (cp = yylval.token; *cp != '\0'; cp++) { 1057 if (*cp != '%' && !isdigit((u_char)*cp)) 1058 break; 1059 } 1060 if (*cp == '\0') 1061 return (TOKEN); 1062 ps->condition = 1; 1063 if (strcmp(yylval.token, "%if") == 0) { 1064 free(yylval.token); 1065 return (IF); 1066 } 1067 if (strcmp(yylval.token, "%else") == 0) { 1068 free(yylval.token); 1069 return (ELSE); 1070 } 1071 if (strcmp(yylval.token, "%elif") == 0) { 1072 free(yylval.token); 1073 return (ELIF); 1074 } 1075 if (strcmp(yylval.token, "%endif") == 0) { 1076 free(yylval.token); 1077 return (ENDIF); 1078 } 1079 free(yylval.token); 1080 return (ERROR); 1081 } 1082 1083 /* 1084 * Otherwise this is a token. 1085 */ 1086 token = yylex_token(ch); 1087 if (token == NULL) 1088 return (ERROR); 1089 yylval.token = token; 1090 1091 if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) { 1092 for (cp = token + 1; *cp != '='; cp++) { 1093 if (!yylex_is_var(*cp, 0)) 1094 break; 1095 } 1096 if (*cp == '=') 1097 return (EQUALS); 1098 } 1099 return (TOKEN); 1100 } 1101 return (0); 1102 } 1103 1104 static char * 1105 yylex_format(void) 1106 { 1107 char *buf; 1108 size_t len; 1109 int ch, brackets = 1; 1110 1111 len = 0; 1112 buf = xmalloc(1); 1113 1114 yylex_append(&buf, &len, "#{", 2); 1115 for (;;) { 1116 if ((ch = yylex_getc()) == EOF || ch == '\n') 1117 goto error; 1118 if (ch == '#') { 1119 if ((ch = yylex_getc()) == EOF || ch == '\n') 1120 goto error; 1121 if (ch == '{') 1122 brackets++; 1123 yylex_append1(&buf, &len, '#'); 1124 } else if (ch == '}') { 1125 if (brackets != 0 && --brackets == 0) { 1126 yylex_append1(&buf, &len, ch); 1127 break; 1128 } 1129 } 1130 yylex_append1(&buf, &len, ch); 1131 } 1132 if (brackets != 0) 1133 goto error; 1134 1135 buf[len] = '\0'; 1136 log_debug("%s: %s", __func__, buf); 1137 return (buf); 1138 1139 error: 1140 free(buf); 1141 return (NULL); 1142 } 1143 1144 static int 1145 yylex_token_escape(char **buf, size_t *len) 1146 { 1147 int ch, type, o2, o3; 1148 u_int size, i, tmp; 1149 char s[9]; 1150 struct utf8_data ud; 1151 1152 ch = yylex_getc(); 1153 1154 if (ch >= '4' && ch <= '7') { 1155 yyerror("invalid octal escape"); 1156 return (0); 1157 } 1158 if (ch >= '0' && ch <= '3') { 1159 o2 = yylex_getc(); 1160 if (o2 >= '0' && o2 <= '7') { 1161 o3 = yylex_getc(); 1162 if (o3 >= '0' && o3 <= '7') { 1163 ch = 64 * (ch - '0') + 1164 8 * (o2 - '0') + 1165 (o3 - '0'); 1166 yylex_append1(buf, len, ch); 1167 return (1); 1168 } 1169 } 1170 yyerror("invalid octal escape"); 1171 return (0); 1172 } 1173 1174 switch (ch) { 1175 case EOF: 1176 return (0); 1177 case 'a': 1178 ch = '\a'; 1179 break; 1180 case 'b': 1181 ch = '\b'; 1182 break; 1183 case 'e': 1184 ch = '\033'; 1185 break; 1186 case 'f': 1187 ch = '\f'; 1188 break; 1189 case 's': 1190 ch = ' '; 1191 break; 1192 case 'v': 1193 ch = '\v'; 1194 break; 1195 case 'r': 1196 ch = '\r'; 1197 break; 1198 case 'n': 1199 ch = '\n'; 1200 break; 1201 case 't': 1202 ch = '\t'; 1203 break; 1204 case 'u': 1205 type = 'u'; 1206 size = 4; 1207 goto unicode; 1208 case 'U': 1209 type = 'U'; 1210 size = 8; 1211 goto unicode; 1212 } 1213 1214 yylex_append1(buf, len, ch); 1215 return (1); 1216 1217 unicode: 1218 for (i = 0; i < size; i++) { 1219 ch = yylex_getc(); 1220 if (ch == EOF || ch == '\n') 1221 return (0); 1222 if (!isxdigit((u_char)ch)) { 1223 yyerror("invalid \\%c argument", type); 1224 return (0); 1225 } 1226 s[i] = ch; 1227 } 1228 s[i] = '\0'; 1229 1230 if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) || 1231 (size == 8 && sscanf(s, "%8x", &tmp) != 1)) { 1232 yyerror("invalid \\%c argument", type); 1233 return (0); 1234 } 1235 if (utf8_split(tmp, &ud) != UTF8_DONE) { 1236 yyerror("invalid \\%c argument", type); 1237 return (0); 1238 } 1239 yylex_append(buf, len, ud.data, ud.size); 1240 return (1); 1241 } 1242 1243 static int 1244 yylex_token_variable(char **buf, size_t *len) 1245 { 1246 struct environ_entry *envent; 1247 int ch, brackets = 0; 1248 char name[BUFSIZ]; 1249 size_t namelen = 0; 1250 const char *value; 1251 1252 ch = yylex_getc(); 1253 if (ch == EOF) 1254 return (0); 1255 if (ch == '{') 1256 brackets = 1; 1257 else { 1258 if (!yylex_is_var(ch, 1)) { 1259 yylex_append1(buf, len, '$'); 1260 yylex_ungetc(ch); 1261 return (1); 1262 } 1263 name[namelen++] = ch; 1264 } 1265 1266 for (;;) { 1267 ch = yylex_getc(); 1268 if (brackets && ch == '}') 1269 break; 1270 if (ch == EOF || !yylex_is_var(ch, 0)) { 1271 if (!brackets) { 1272 yylex_ungetc(ch); 1273 break; 1274 } 1275 yyerror("invalid environment variable"); 1276 return (0); 1277 } 1278 if (namelen == (sizeof name) - 2) { 1279 yyerror("environment variable is too long"); 1280 return (0); 1281 } 1282 name[namelen++] = ch; 1283 } 1284 name[namelen] = '\0'; 1285 1286 envent = environ_find(global_environ, name); 1287 if (envent != NULL) { 1288 value = envent->value; 1289 log_debug("%s: %s -> %s", __func__, name, value); 1290 yylex_append(buf, len, value, strlen(value)); 1291 } 1292 return (1); 1293 } 1294 1295 static int 1296 yylex_token_tilde(char **buf, size_t *len) 1297 { 1298 struct environ_entry *envent; 1299 int ch; 1300 char name[BUFSIZ]; 1301 size_t namelen = 0; 1302 struct passwd *pw; 1303 const char *home = NULL; 1304 1305 for (;;) { 1306 ch = yylex_getc(); 1307 if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) { 1308 yylex_ungetc(ch); 1309 break; 1310 } 1311 if (namelen == (sizeof name) - 2) { 1312 yyerror("user name is too long"); 1313 return (0); 1314 } 1315 name[namelen++] = ch; 1316 } 1317 name[namelen] = '\0'; 1318 1319 if (*name == '\0') { 1320 envent = environ_find(global_environ, "HOME"); 1321 if (envent != NULL && *envent->value != '\0') 1322 home = envent->value; 1323 else if ((pw = getpwuid(getuid())) != NULL) 1324 home = pw->pw_dir; 1325 } else { 1326 if ((pw = getpwnam(name)) != NULL) 1327 home = pw->pw_dir; 1328 } 1329 if (home == NULL) 1330 return (0); 1331 1332 log_debug("%s: ~%s -> %s", __func__, name, home); 1333 yylex_append(buf, len, home, strlen(home)); 1334 return (1); 1335 } 1336 1337 static int 1338 yylex_token_brace(char **buf, size_t *len) 1339 { 1340 struct cmd_parse_state *ps = &parse_state; 1341 int ch, lines = 0, nesting = 1, escape = 0; 1342 int quote = '\0', token = 0; 1343 1344 /* 1345 * Extract a string up to the matching unquoted '}', including newlines 1346 * and handling nested braces. 1347 * 1348 * To detect the final and intermediate braces which affect the nesting 1349 * depth, we scan the input as if it was a tmux config file, and ignore 1350 * braces which would be considered quoted, escaped, or in a comment. 1351 * 1352 * We update the token state after every character because '#' begins a 1353 * comment only when it begins a token. For simplicity, we treat an 1354 * unquoted directive format as comment. 1355 * 1356 * The result is verbatim copy of the input excluding the final brace. 1357 */ 1358 1359 for (ch = yylex_getc1(); ch != EOF; ch = yylex_getc1()) { 1360 yylex_append1(buf, len, ch); 1361 if (ch == '\n') 1362 lines++; 1363 1364 /* 1365 * If the previous character was a backslash (escape is set), 1366 * escape anything if unquoted or in double quotes, otherwise 1367 * escape only '\n' and '\\'. 1368 */ 1369 if (escape && 1370 (quote == '\0' || 1371 quote == '"' || 1372 ch == '\n' || 1373 ch == '\\')) { 1374 escape = 0; 1375 if (ch != '\n') 1376 token = 1; 1377 continue; 1378 } 1379 1380 /* 1381 * The character is not escaped. If it is a backslash, set the 1382 * escape flag. 1383 */ 1384 if (ch == '\\') { 1385 escape = 1; 1386 continue; 1387 } 1388 escape = 0; 1389 1390 /* A newline always resets to unquoted. */ 1391 if (ch == '\n') { 1392 quote = token = 0; 1393 continue; 1394 } 1395 1396 if (quote) { 1397 /* 1398 * Inside quotes or comment. Check if this is the 1399 * closing quote. 1400 */ 1401 if (ch == quote && quote != '#') 1402 quote = 0; 1403 token = 1; /* token continues regardless */ 1404 } else { 1405 /* Not inside quotes or comment. */ 1406 switch (ch) { 1407 case '"': 1408 case '\'': 1409 case '#': 1410 /* Beginning of quote or maybe comment. */ 1411 if (ch != '#' || !token) 1412 quote = ch; 1413 token = 1; 1414 break; 1415 case ' ': 1416 case '\t': 1417 case ';': 1418 /* Delimiter - token resets. */ 1419 token = 0; 1420 break; 1421 case '{': 1422 nesting++; 1423 token = 0; /* new commands set - token resets */ 1424 break; 1425 case '}': 1426 nesting--; 1427 token = 1; /* same as after quotes */ 1428 if (nesting == 0) { 1429 (*len)--; /* remove closing } */ 1430 ps->input->line += lines; 1431 return (1); 1432 } 1433 break; 1434 default: 1435 token = 1; 1436 break; 1437 } 1438 } 1439 } 1440 1441 /* 1442 * Update line count after error as reporting the opening line is more 1443 * useful than EOF. 1444 */ 1445 yyerror("unterminated brace string"); 1446 ps->input->line += lines; 1447 return (0); 1448 } 1449 1450 static char * 1451 yylex_token(int ch) 1452 { 1453 char *buf; 1454 size_t len; 1455 enum { START, 1456 NONE, 1457 DOUBLE_QUOTES, 1458 SINGLE_QUOTES } state = NONE, last = START; 1459 1460 len = 0; 1461 buf = xmalloc(1); 1462 1463 for (;;) { 1464 /* 1465 * EOF or \n are always the end of the token. If inside quotes 1466 * they are an error. 1467 */ 1468 if (ch == EOF || ch == '\n') { 1469 if (state != NONE) 1470 goto error; 1471 break; 1472 } 1473 1474 /* Whitespace or ; ends a token unless inside quotes. */ 1475 if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE) 1476 break; 1477 1478 /* 1479 * \ ~ and $ are expanded except in single quotes. 1480 */ 1481 if (ch == '\\' && state != SINGLE_QUOTES) { 1482 if (!yylex_token_escape(&buf, &len)) 1483 goto error; 1484 goto skip; 1485 } 1486 if (ch == '~' && last != state && state != SINGLE_QUOTES) { 1487 if (!yylex_token_tilde(&buf, &len)) 1488 goto error; 1489 goto skip; 1490 } 1491 if (ch == '$' && state != SINGLE_QUOTES) { 1492 if (!yylex_token_variable(&buf, &len)) 1493 goto error; 1494 goto skip; 1495 } 1496 if (ch == '{' && state == NONE) { 1497 if (!yylex_token_brace(&buf, &len)) 1498 goto error; 1499 goto skip; 1500 } 1501 if (ch == '}' && state == NONE) 1502 goto error; /* unmatched (matched ones were handled) */ 1503 1504 /* 1505 * ' and " starts or end quotes (and is consumed). 1506 */ 1507 if (ch == '\'') { 1508 if (state == NONE) { 1509 state = SINGLE_QUOTES; 1510 goto next; 1511 } 1512 if (state == SINGLE_QUOTES) { 1513 state = NONE; 1514 goto next; 1515 } 1516 } 1517 if (ch == '"') { 1518 if (state == NONE) { 1519 state = DOUBLE_QUOTES; 1520 goto next; 1521 } 1522 if (state == DOUBLE_QUOTES) { 1523 state = NONE; 1524 goto next; 1525 } 1526 } 1527 1528 /* 1529 * Otherwise add the character to the buffer. 1530 */ 1531 yylex_append1(&buf, &len, ch); 1532 1533 skip: 1534 last = state; 1535 1536 next: 1537 ch = yylex_getc(); 1538 } 1539 yylex_ungetc(ch); 1540 1541 buf[len] = '\0'; 1542 log_debug("%s: %s", __func__, buf); 1543 return (buf); 1544 1545 error: 1546 free(buf); 1547 return (NULL); 1548 } 1549