Annotation of src/usr.bin/tmux/cmd-parse.y, Revision 1.1
1.1 ! nicm 1: /* $OpenBSD$ */
! 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 <string.h>
! 27: #include <unistd.h>
! 28:
! 29: #include "tmux.h"
! 30:
! 31: static int yylex(void);
! 32: static int yyparse(void);
! 33: static int printflike(1,2) yyerror(const char *, ...);
! 34:
! 35: static char *yylex_token(int);
! 36: static char *yylex_format(void);
! 37:
! 38: struct cmd_parse_scope {
! 39: int flag;
! 40: TAILQ_ENTRY (cmd_parse_scope) entry;
! 41: };
! 42:
! 43: struct cmd_parse_command {
! 44: char *name;
! 45: u_int line;
! 46:
! 47: int argc;
! 48: char **argv;
! 49:
! 50: TAILQ_ENTRY(cmd_parse_command) entry;
! 51: };
! 52: TAILQ_HEAD(cmd_parse_commands, cmd_parse_command);
! 53:
! 54: struct cmd_parse_state {
! 55: FILE *f;
! 56: int eof;
! 57: struct cmd_parse_input *input;
! 58: u_int escapes;
! 59:
! 60: char *error;
! 61: struct cmd_parse_commands commands;
! 62:
! 63: struct cmd_parse_scope *scope;
! 64: TAILQ_HEAD(, cmd_parse_scope) stack;
! 65: };
! 66: static struct cmd_parse_state parse_state;
! 67:
! 68: static char *cmd_parse_get_error(const char *, u_int, const char *);
! 69: static char *cmd_parse_get_strerror(const char *, u_int);
! 70: static void cmd_parse_free_command(struct cmd_parse_command *);
! 71: static void cmd_parse_free_commands(struct cmd_parse_commands *);
! 72:
! 73: %}
! 74:
! 75: %union
! 76: {
! 77: char *token;
! 78: struct {
! 79: int argc;
! 80: char **argv;
! 81: } arguments;
! 82: int flag;
! 83: struct {
! 84: int flag;
! 85: struct cmd_parse_commands commands;
! 86: } elif;
! 87: struct cmd_parse_commands commands;
! 88: struct cmd_parse_command *command;
! 89: }
! 90:
! 91: %token ERROR
! 92: %token IF
! 93: %token ELSE
! 94: %token ELIF
! 95: %token ENDIF
! 96: %token <token> FORMAT TOKEN EQUALS
! 97:
! 98: %type <token> argument expanded
! 99: %type <arguments> arguments
! 100: %type <flag> if_open if_elif
! 101: %type <elif> elif elif1
! 102: %type <commands> statements statement commands condition condition1
! 103: %type <command> command
! 104:
! 105: %%
! 106:
! 107: lines : /* empty */
! 108: | statements
! 109: {
! 110: struct cmd_parse_state *ps = &parse_state;
! 111:
! 112: TAILQ_CONCAT(&ps->commands, &$1, entry);
! 113: }
! 114:
! 115: statements : statement '\n'
! 116: {
! 117: TAILQ_INIT(&$$);
! 118: TAILQ_CONCAT(&$$, &$1, entry);
! 119: }
! 120: | statements statement '\n'
! 121: {
! 122: TAILQ_INIT(&$$);
! 123: TAILQ_CONCAT(&$$, &$1, entry);
! 124: TAILQ_CONCAT(&$$, &$2, entry);
! 125: }
! 126:
! 127:
! 128: statement : condition
! 129: {
! 130: struct cmd_parse_state *ps = &parse_state;
! 131:
! 132: TAILQ_INIT(&$$);
! 133: if (ps->scope == NULL || ps->scope->flag)
! 134: TAILQ_CONCAT(&$$, &$1, entry);
! 135: else
! 136: cmd_parse_free_commands(&$1);
! 137: }
! 138: | assignment
! 139: {
! 140: TAILQ_INIT(&$$);
! 141: }
! 142: | commands
! 143: {
! 144: struct cmd_parse_state *ps = &parse_state;
! 145:
! 146: TAILQ_INIT(&$$);
! 147: if (ps->scope == NULL || ps->scope->flag)
! 148: TAILQ_CONCAT(&$$, &$1, entry);
! 149: else
! 150: cmd_parse_free_commands(&$1);
! 151: }
! 152:
! 153: expanded : FORMAT
! 154: {
! 155: struct cmd_parse_state *ps = &parse_state;
! 156: struct cmd_parse_input *pi = ps->input;
! 157: struct format_tree *ft;
! 158: struct client *c = pi->c;
! 159: struct cmd_find_state *fs;
! 160: int flags = FORMAT_NOJOBS;
! 161:
! 162: if (cmd_find_valid_state(&pi->fs))
! 163: fs = &pi->fs;
! 164: else
! 165: fs = NULL;
! 166: ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
! 167: if (fs != NULL)
! 168: format_defaults(ft, c, fs->s, fs->wl, fs->wp);
! 169: else
! 170: format_defaults(ft, c, NULL, NULL, NULL);
! 171:
! 172: $$ = format_expand(ft, $1);
! 173: format_free(ft);
! 174: free($1);
! 175: }
! 176:
! 177: assignment : /* empty */
! 178: | EQUALS
! 179: {
! 180: struct cmd_parse_state *ps = &parse_state;
! 181: int flags = ps->input->flags;
! 182:
! 183: if ((~flags & CMD_PARSE_PARSEONLY) &&
! 184: (ps->scope == NULL || ps->scope->flag))
! 185: environ_put(global_environ, $1);
! 186: free($1);
! 187: }
! 188:
! 189: if_open : IF expanded
! 190: {
! 191: struct cmd_parse_state *ps = &parse_state;
! 192: struct cmd_parse_scope *scope;
! 193:
! 194: scope = xmalloc(sizeof *scope);
! 195: $$ = scope->flag = format_true($2);
! 196: free($2);
! 197:
! 198: if (ps->scope != NULL)
! 199: TAILQ_INSERT_HEAD(&ps->stack, ps->scope, entry);
! 200: ps->scope = scope;
! 201: }
! 202:
! 203: if_else : ELSE
! 204: {
! 205: struct cmd_parse_state *ps = &parse_state;
! 206: struct cmd_parse_scope *scope;
! 207:
! 208: scope = xmalloc(sizeof *scope);
! 209: scope->flag = !ps->scope->flag;
! 210:
! 211: free(ps->scope);
! 212: ps->scope = scope;
! 213: }
! 214:
! 215: if_elif : ELIF expanded
! 216: {
! 217: struct cmd_parse_state *ps = &parse_state;
! 218: struct cmd_parse_scope *scope;
! 219:
! 220: scope = xmalloc(sizeof *scope);
! 221: $$ = scope->flag = format_true($2);
! 222: free($2);
! 223:
! 224: free(ps->scope);
! 225: ps->scope = scope;
! 226: }
! 227:
! 228: if_close : ENDIF
! 229: {
! 230: struct cmd_parse_state *ps = &parse_state;
! 231:
! 232: free(ps->scope);
! 233: ps->scope = TAILQ_FIRST(&ps->stack);
! 234: if (ps->scope != NULL)
! 235: TAILQ_REMOVE(&ps->stack, ps->scope, entry);
! 236: }
! 237:
! 238: condition : if_open '\n' statements if_close
! 239: {
! 240: TAILQ_INIT(&$$);
! 241: if ($1)
! 242: TAILQ_CONCAT(&$$, &$3, entry);
! 243: else
! 244: cmd_parse_free_commands(&$3);
! 245: }
! 246: | if_open '\n' statements if_else '\n' statements if_close
! 247: {
! 248: TAILQ_INIT(&$$);
! 249: if ($1) {
! 250: TAILQ_CONCAT(&$$, &$3, entry);
! 251: cmd_parse_free_commands(&$6);
! 252: } else {
! 253: TAILQ_CONCAT(&$$, &$6, entry);
! 254: cmd_parse_free_commands(&$3);
! 255: }
! 256: }
! 257: | if_open '\n' statements elif if_close
! 258: {
! 259: TAILQ_INIT(&$$);
! 260: if ($1) {
! 261: TAILQ_CONCAT(&$$, &$3, entry);
! 262: cmd_parse_free_commands(&$4.commands);
! 263: } else if ($4.flag) {
! 264: TAILQ_CONCAT(&$$, &$4.commands, entry);
! 265: cmd_parse_free_commands(&$3);
! 266: } else {
! 267: cmd_parse_free_commands(&$3);
! 268: cmd_parse_free_commands(&$4.commands);
! 269: }
! 270: }
! 271: | if_open '\n' statements elif if_else '\n' statements if_close
! 272: {
! 273: TAILQ_INIT(&$$);
! 274: if ($1) {
! 275: TAILQ_CONCAT(&$$, &$3, entry);
! 276: cmd_parse_free_commands(&$4.commands);
! 277: cmd_parse_free_commands(&$7);
! 278: } else if ($4.flag) {
! 279: TAILQ_CONCAT(&$$, &$4.commands, entry);
! 280: cmd_parse_free_commands(&$3);
! 281: cmd_parse_free_commands(&$7);
! 282: } else {
! 283: TAILQ_CONCAT(&$$, &$7, entry);
! 284: cmd_parse_free_commands(&$3);
! 285: cmd_parse_free_commands(&$4.commands);
! 286: }
! 287: }
! 288:
! 289: elif : if_elif '\n' statements
! 290: {
! 291: TAILQ_INIT(&$$.commands);
! 292: if ($1)
! 293: TAILQ_CONCAT(&$$.commands, &$3, entry);
! 294: else
! 295: cmd_parse_free_commands(&$3);
! 296: $$.flag = $1;
! 297: }
! 298: | if_elif '\n' statements elif
! 299: {
! 300: TAILQ_INIT(&$$.commands);
! 301: if ($1) {
! 302: $$.flag = 1;
! 303: TAILQ_CONCAT(&$$.commands, &$3, entry);
! 304: cmd_parse_free_commands(&$4.commands);
! 305: } else {
! 306: $$.flag = $4.flag;
! 307: TAILQ_CONCAT(&$$.commands, &$4.commands, entry);
! 308: cmd_parse_free_commands(&$3);
! 309: }
! 310: }
! 311:
! 312:
! 313: commands : command
! 314: {
! 315: struct cmd_parse_state *ps = &parse_state;
! 316:
! 317: TAILQ_INIT(&$$);
! 318: if (ps->scope == NULL || ps->scope->flag)
! 319: TAILQ_INSERT_TAIL(&$$, $1, entry);
! 320: else
! 321: cmd_parse_free_command($1);
! 322: }
! 323: | commands ';'
! 324: {
! 325: TAILQ_INIT(&$$);
! 326: TAILQ_CONCAT(&$$, &$1, entry);
! 327: }
! 328: | commands ';' condition1
! 329: {
! 330: TAILQ_INIT(&$$);
! 331: TAILQ_CONCAT(&$$, &$1, entry);
! 332: TAILQ_CONCAT(&$$, &$3, entry);
! 333: }
! 334: | commands ';' command
! 335: {
! 336: struct cmd_parse_state *ps = &parse_state;
! 337:
! 338: TAILQ_INIT(&$$);
! 339: if (ps->scope == NULL || ps->scope->flag) {
! 340: TAILQ_CONCAT(&$$, &$1, entry);
! 341: TAILQ_INSERT_TAIL(&$$, $3, entry);
! 342: } else {
! 343: cmd_parse_free_commands(&$1);
! 344: cmd_parse_free_command($3);
! 345: }
! 346: }
! 347: | condition1
! 348: {
! 349: TAILQ_INIT(&$$);
! 350: TAILQ_CONCAT(&$$, &$1, entry);
! 351: }
! 352:
! 353: command : assignment TOKEN
! 354: {
! 355: struct cmd_parse_state *ps = &parse_state;
! 356:
! 357: $$ = xcalloc(1, sizeof *$$);
! 358: $$->name = $2;
! 359: $$->line = ps->input->line;
! 360:
! 361: }
! 362: | assignment TOKEN arguments
! 363: {
! 364: struct cmd_parse_state *ps = &parse_state;
! 365:
! 366: $$ = xcalloc(1, sizeof *$$);
! 367: $$->name = $2;
! 368: $$->line = ps->input->line;
! 369:
! 370: $$->argc = $3.argc;
! 371: $$->argv = $3.argv;
! 372: }
! 373:
! 374: condition1 : if_open commands if_close
! 375: {
! 376: TAILQ_INIT(&$$);
! 377: if ($1)
! 378: TAILQ_CONCAT(&$$, &$2, entry);
! 379: else
! 380: cmd_parse_free_commands(&$2);
! 381: }
! 382: | if_open commands if_else commands if_close
! 383: {
! 384: TAILQ_INIT(&$$);
! 385: if ($1) {
! 386: TAILQ_CONCAT(&$$, &$2, entry);
! 387: cmd_parse_free_commands(&$4);
! 388: } else {
! 389: TAILQ_CONCAT(&$$, &$4, entry);
! 390: cmd_parse_free_commands(&$2);
! 391: }
! 392: }
! 393: | if_open commands elif1 if_close
! 394: {
! 395: TAILQ_INIT(&$$);
! 396: if ($1) {
! 397: TAILQ_CONCAT(&$$, &$2, entry);
! 398: cmd_parse_free_commands(&$3.commands);
! 399: } else if ($3.flag) {
! 400: TAILQ_CONCAT(&$$, &$3.commands, entry);
! 401: cmd_parse_free_commands(&$2);
! 402: } else {
! 403: cmd_parse_free_commands(&$2);
! 404: cmd_parse_free_commands(&$3.commands);
! 405: }
! 406: }
! 407: | if_open commands elif1 if_else commands if_close
! 408: {
! 409: TAILQ_INIT(&$$);
! 410: if ($1) {
! 411: TAILQ_CONCAT(&$$, &$2, entry);
! 412: cmd_parse_free_commands(&$3.commands);
! 413: cmd_parse_free_commands(&$5);
! 414: } else if ($3.flag) {
! 415: TAILQ_CONCAT(&$$, &$3.commands, entry);
! 416: cmd_parse_free_commands(&$2);
! 417: cmd_parse_free_commands(&$5);
! 418: } else {
! 419: TAILQ_CONCAT(&$$, &$5, entry);
! 420: cmd_parse_free_commands(&$2);
! 421: cmd_parse_free_commands(&$3.commands);
! 422: }
! 423:
! 424: }
! 425:
! 426: elif1 : if_elif commands
! 427: {
! 428: TAILQ_INIT(&$$.commands);
! 429: if ($1)
! 430: TAILQ_CONCAT(&$$.commands, &$2, entry);
! 431: else
! 432: cmd_parse_free_commands(&$2);
! 433: $$.flag = $1;
! 434: }
! 435: | if_elif commands elif1
! 436: {
! 437: TAILQ_INIT(&$$.commands);
! 438: if ($1) {
! 439: $$.flag = 1;
! 440: TAILQ_CONCAT(&$$.commands, &$2, entry);
! 441: cmd_parse_free_commands(&$3.commands);
! 442: } else {
! 443: $$.flag = $3.flag;
! 444: TAILQ_CONCAT(&$$.commands, &$3.commands, entry);
! 445: cmd_parse_free_commands(&$2);
! 446: }
! 447: }
! 448:
! 449: arguments : argument
! 450: {
! 451: $$.argc = 1;
! 452: $$.argv = xreallocarray(NULL, 1, sizeof *$$.argv);
! 453:
! 454: $$.argv[0] = $1;
! 455: }
! 456: | argument arguments
! 457: {
! 458: cmd_prepend_argv(&$2.argc, &$2.argv, $1);
! 459: free($1);
! 460: $$ = $2;
! 461: }
! 462:
! 463: argument : TOKEN
! 464: {
! 465: $$ = $1;
! 466: }
! 467: | EQUALS
! 468: {
! 469: $$ = $1;
! 470: }
! 471:
! 472: %%
! 473:
! 474: static char *
! 475: cmd_parse_get_error(const char *file, u_int line, const char *error)
! 476: {
! 477: char *s;
! 478:
! 479: if (file == NULL)
! 480: s = xstrdup(error);
! 481: else
! 482: xasprintf (&s, "%s:%u: %s", file, line, error);
! 483: return (s);
! 484: }
! 485:
! 486: static char *
! 487: cmd_parse_get_strerror(const char *file, u_int line)
! 488: {
! 489: return (cmd_parse_get_error(file, line, strerror(errno)));
! 490: }
! 491:
! 492: static void
! 493: cmd_parse_free_command(struct cmd_parse_command *cmd)
! 494: {
! 495: free(cmd->name);
! 496: cmd_free_argv(cmd->argc, cmd->argv);
! 497: free(cmd);
! 498: }
! 499:
! 500: static void
! 501: cmd_parse_free_commands(struct cmd_parse_commands *cmds)
! 502: {
! 503: struct cmd_parse_command *cmd, *cmd1;
! 504:
! 505: TAILQ_FOREACH_SAFE(cmd, cmds, entry, cmd1) {
! 506: TAILQ_REMOVE(cmds, cmd, entry);
! 507: cmd_parse_free_command(cmd);
! 508: }
! 509: }
! 510:
! 511: static struct cmd_parse_commands *
! 512: cmd_parse_run_parser(FILE *f, struct cmd_parse_input *pi, char **cause)
! 513: {
! 514: struct cmd_parse_state *ps = &parse_state;
! 515: struct cmd_parse_commands *cmds;
! 516: struct cmd_parse_scope *scope, *scope1;
! 517: int retval;
! 518:
! 519: memset(ps, 0, sizeof *ps);
! 520:
! 521: ps->f = f;
! 522: ps->eof = 0;
! 523: ps->input = pi;
! 524:
! 525: TAILQ_INIT(&ps->commands);
! 526: TAILQ_INIT(&ps->stack);
! 527:
! 528: retval = yyparse();
! 529: TAILQ_FOREACH_SAFE(scope, &ps->stack, entry, scope1) {
! 530: TAILQ_REMOVE(&ps->stack, scope, entry);
! 531: free(scope);
! 532: }
! 533: if (retval != 0) {
! 534: *cause = ps->error;
! 535: return (NULL);
! 536: }
! 537:
! 538: cmds = xmalloc(sizeof *cmds);
! 539: TAILQ_INIT(cmds);
! 540: TAILQ_CONCAT(cmds, &ps->commands, entry);
! 541: return (cmds);
! 542: }
! 543:
! 544: struct cmd_parse_result *
! 545: cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi)
! 546: {
! 547: static struct cmd_parse_result pr;
! 548: struct cmd_parse_input input;
! 549: struct cmd_parse_commands *cmds, *cmds2;
! 550: struct cmd_parse_command *cmd, *cmd2, *next, *next2, *after;
! 551: u_int line = UINT_MAX;
! 552: int i;
! 553: struct cmd_list *cmdlist = NULL, *result;
! 554: struct cmd *add;
! 555: char *alias, *cause, *s;
! 556:
! 557: if (pi == NULL) {
! 558: memset(&input, 0, sizeof input);
! 559: pi = &input;
! 560: }
! 561: memset(&pr, 0, sizeof pr);
! 562:
! 563: /*
! 564: * Parse the file into a list of commands.
! 565: */
! 566: cmds = cmd_parse_run_parser(f, pi, &cause);
! 567: if (cmds == NULL) {
! 568: pr.status = CMD_PARSE_ERROR;
! 569: pr.error = cause;
! 570: return (&pr);
! 571: }
! 572: if (TAILQ_EMPTY(cmds)) {
! 573: free(cmds);
! 574: pr.status = CMD_PARSE_EMPTY;
! 575: return (&pr);
! 576: }
! 577:
! 578: /*
! 579: * Walk the commands and expand any aliases. Each alias is parsed
! 580: * individually to a new command list, any trailing arguments appended
! 581: * to the last command, and all commands inserted into the original
! 582: * command list.
! 583: */
! 584: TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) {
! 585: alias = cmd_get_alias(cmd->name);
! 586: if (alias == NULL)
! 587: continue;
! 588:
! 589: line = cmd->line;
! 590: log_debug("%s: %u %s = %s", __func__, line, cmd->name, alias);
! 591:
! 592: f = fmemopen(alias, strlen(alias), "r");
! 593: if (f == NULL) {
! 594: free(alias);
! 595: pr.status = CMD_PARSE_ERROR;
! 596: pr.error = cmd_parse_get_strerror(pi->file, line);
! 597: goto out;
! 598: }
! 599: pi->line = line;
! 600: cmds2 = cmd_parse_run_parser(f, pi, &cause);
! 601: fclose(f);
! 602: free(alias);
! 603: if (cmds2 == NULL) {
! 604: pr.status = CMD_PARSE_ERROR;
! 605: pr.error = cause;
! 606: goto out;
! 607: }
! 608:
! 609: cmd2 = TAILQ_LAST(cmds2, cmd_parse_commands);
! 610: if (cmd2 == NULL) {
! 611: TAILQ_REMOVE(cmds, cmd, entry);
! 612: cmd_parse_free_command(cmd);
! 613: continue;
! 614: }
! 615: for (i = 0; i < cmd->argc; i++)
! 616: cmd_append_argv(&cmd2->argc, &cmd2->argv, cmd->argv[i]);
! 617:
! 618: after = cmd;
! 619: TAILQ_FOREACH_SAFE(cmd2, cmds2, entry, next2) {
! 620: cmd2->line = line;
! 621: TAILQ_REMOVE(cmds2, cmd2, entry);
! 622: TAILQ_INSERT_AFTER(cmds, after, cmd2, entry);
! 623: after = cmd2;
! 624: }
! 625: cmd_parse_free_commands(cmds2);
! 626:
! 627: TAILQ_REMOVE(cmds, cmd, entry);
! 628: cmd_parse_free_command(cmd);
! 629: }
! 630:
! 631: /*
! 632: * Parse each command into a command list. Create a new command list
! 633: * for each line so they get a new group (so the queue knows which ones
! 634: * to remove if a command fails when executed).
! 635: */
! 636: result = cmd_list_new();
! 637: TAILQ_FOREACH(cmd, cmds, entry) {
! 638: log_debug("%s: %u %s", __func__, cmd->line, cmd->name);
! 639: cmd_log_argv(cmd->argc, cmd->argv, __func__);
! 640:
! 641: if (cmdlist == NULL || cmd->line != line) {
! 642: if (cmdlist != NULL) {
! 643: cmd_list_move(result, cmdlist);
! 644: cmd_list_free(cmdlist);
! 645: }
! 646: cmdlist = cmd_list_new();
! 647: }
! 648: line = cmd->line;
! 649:
! 650: cmd_prepend_argv(&cmd->argc, &cmd->argv, cmd->name);
! 651: add = cmd_parse(cmd->argc, cmd->argv, pi->file, line, &cause);
! 652: if (add == NULL) {
! 653: cmd_list_free(result);
! 654: pr.status = CMD_PARSE_ERROR;
! 655: pr.error = cmd_parse_get_error(pi->file, line, cause);
! 656: free(cause);
! 657: goto out;
! 658: }
! 659: cmd_list_append(cmdlist, add);
! 660: }
! 661: if (cmdlist != NULL) {
! 662: cmd_list_move(result, cmdlist);
! 663: cmd_list_free(cmdlist);
! 664: }
! 665:
! 666: s = cmd_list_print(result);
! 667: log_debug("%s: %s", __func__, s);
! 668: free(s);
! 669:
! 670: pr.status = CMD_PARSE_SUCCESS;
! 671: pr.cmdlist = result;
! 672:
! 673: out:
! 674: cmd_parse_free_commands(cmds);
! 675: free(cmds);
! 676:
! 677: return (&pr);
! 678: }
! 679:
! 680: struct cmd_parse_result *
! 681: cmd_parse_from_string(const char *s, struct cmd_parse_input *pi)
! 682: {
! 683: static struct cmd_parse_result pr;
! 684: struct cmd_parse_result *prp;
! 685: FILE *f;
! 686:
! 687: if (*s == '\0') {
! 688: pr.status = CMD_PARSE_EMPTY;
! 689: pr.cmdlist = NULL;
! 690: pr.error = NULL;
! 691: return (&pr);
! 692: }
! 693:
! 694: f = fmemopen((void *)s, strlen(s), "r");
! 695: if (f == NULL) {
! 696: pr.status = CMD_PARSE_ERROR;
! 697: pr.cmdlist = NULL;
! 698: pr.error = cmd_parse_get_strerror(pi->file, pi->line);
! 699: return (NULL);
! 700: }
! 701: prp = cmd_parse_from_file(f, pi);
! 702: fclose(f);
! 703: return (prp);
! 704: }
! 705:
! 706: static int printflike(1, 2)
! 707: yyerror(const char *fmt, ...)
! 708: {
! 709: struct cmd_parse_state *ps = &parse_state;
! 710: struct cmd_parse_input *pi = ps->input;
! 711: va_list ap;
! 712: char *error;
! 713:
! 714: if (ps->error != NULL)
! 715: return (0);
! 716:
! 717: va_start(ap, fmt);
! 718: xvasprintf(&error, fmt, ap);
! 719: va_end(ap);
! 720:
! 721: ps->error = cmd_parse_get_error(pi->file, pi->line, error);
! 722: free(error);
! 723: return (0);
! 724: }
! 725:
! 726: static int
! 727: yylex_is_var(char ch, int first)
! 728: {
! 729: if (ch == '=')
! 730: return (0);
! 731: if (first && isdigit((u_char)ch))
! 732: return (0);
! 733: return (isalnum((u_char)ch) || ch == '_');
! 734: }
! 735:
! 736: static void
! 737: yylex_append(char **buf, size_t *len, const char *add, size_t addlen)
! 738: {
! 739: if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen)
! 740: fatalx("buffer is too big");
! 741: *buf = xrealloc(*buf, (*len) + 1 + addlen);
! 742: memcpy((*buf) + *len, add, addlen);
! 743: (*len) += addlen;
! 744: }
! 745:
! 746: static void
! 747: yylex_append1(char **buf, size_t *len, char add)
! 748: {
! 749: yylex_append(buf, len, &add, 1);
! 750: }
! 751:
! 752: static int
! 753: yylex_getc(void)
! 754: {
! 755: struct cmd_parse_state *ps = &parse_state;
! 756: int ch;
! 757:
! 758: if (ps->escapes != 0) {
! 759: ps->escapes--;
! 760: return ('\\');
! 761: }
! 762: for (;;) {
! 763: ch = getc(ps->f);
! 764: if (ch == '\\') {
! 765: ps->escapes++;
! 766: continue;
! 767: }
! 768: if (ch == '\n' && (ps->escapes % 2) == 1) {
! 769: ps->input->line++;
! 770: ps->escapes--;
! 771: continue;
! 772: }
! 773:
! 774: if (ps->escapes != 0) {
! 775: ungetc(ch, ps->f);
! 776: ps->escapes--;
! 777: return ('\\');
! 778: }
! 779: return (ch);
! 780: }
! 781: }
! 782:
! 783: static char *
! 784: yylex_get_word(int ch)
! 785: {
! 786: struct cmd_parse_state *ps = &parse_state;
! 787: char *buf;
! 788: size_t len;
! 789:
! 790: len = 0;
! 791: buf = xmalloc(1);
! 792:
! 793: do
! 794: yylex_append1(&buf, &len, ch);
! 795: while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL);
! 796: ungetc(ch, ps->f);
! 797:
! 798: buf[len] = '\0';
! 799: log_debug("%s: %s", __func__, buf);
! 800: return (buf);
! 801: }
! 802:
! 803: static int
! 804: yylex(void)
! 805: {
! 806: struct cmd_parse_state *ps = &parse_state;
! 807: char *token, *cp;
! 808: int ch, next;
! 809:
! 810: for (;;) {
! 811: ch = yylex_getc();
! 812:
! 813: if (ch == EOF) {
! 814: /*
! 815: * Ensure every file or string is terminated by a
! 816: * newline. This keeps the parser simpler and avoids
! 817: * having to add a newline to each string.
! 818: */
! 819: if (ps->eof)
! 820: break;
! 821: ps->eof = 1;
! 822: return ('\n');
! 823: }
! 824:
! 825: if (ch == ' ' || ch == '\t') {
! 826: /*
! 827: * Ignore whitespace.
! 828: */
! 829: continue;
! 830: }
! 831:
! 832: if (ch == '\n') {
! 833: /*
! 834: * End of line. Update the line number.
! 835: */
! 836: ps->input->line++;
! 837: return ('\n');
! 838: }
! 839:
! 840: if (ch == ';') {
! 841: /*
! 842: * A semicolon is itself.
! 843: */
! 844: return (';');
! 845: }
! 846:
! 847: if (ch == '#') {
! 848: /*
! 849: * #{ opens a format; anything else is a comment,
! 850: * ignore up to the end of the line.
! 851: */
! 852: next = yylex_getc();
! 853: if (next == '{') {
! 854: yylval.token = yylex_format();
! 855: if (yylval.token == NULL)
! 856: return (ERROR);
! 857: return (FORMAT);
! 858: }
! 859: while (next != '\n' && next != EOF)
! 860: next = yylex_getc();
! 861: if (next == '\n') {
! 862: ps->input->line++;
! 863: return ('\n');
! 864: }
! 865: continue;
! 866: }
! 867:
! 868: if (ch == '%') {
! 869: /*
! 870: * % is a condition unless it is alone, then it is a
! 871: * token.
! 872: */
! 873: yylval.token = yylex_get_word('%');
! 874: if (strcmp(yylval.token, "%") == 0)
! 875: return (TOKEN);
! 876: if (strcmp(yylval.token, "%if") == 0) {
! 877: free(yylval.token);
! 878: return (IF);
! 879: }
! 880: if (strcmp(yylval.token, "%else") == 0) {
! 881: free(yylval.token);
! 882: return (ELSE);
! 883: }
! 884: if (strcmp(yylval.token, "%elif") == 0) {
! 885: free(yylval.token);
! 886: return (ELIF);
! 887: }
! 888: if (strcmp(yylval.token, "%endif") == 0) {
! 889: free(yylval.token);
! 890: return (ENDIF);
! 891: }
! 892: free(yylval.token);
! 893: return (ERROR);
! 894: }
! 895:
! 896: /*
! 897: * Otherwise this is a token.
! 898: */
! 899: token = yylex_token(ch);
! 900: if (token == NULL)
! 901: return (ERROR);
! 902: yylval.token = token;
! 903:
! 904: if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) {
! 905: for (cp = token + 1; *cp != '='; cp++) {
! 906: if (!yylex_is_var(*cp, 0))
! 907: break;
! 908: }
! 909: if (*cp == '=')
! 910: return (EQUALS);
! 911: }
! 912: return (TOKEN);
! 913: }
! 914: return (0);
! 915: }
! 916:
! 917: static char *
! 918: yylex_format(void)
! 919: {
! 920: char *buf;
! 921: size_t len;
! 922: int ch, brackets = 1;
! 923:
! 924: len = 0;
! 925: buf = xmalloc(1);
! 926:
! 927: yylex_append(&buf, &len, "#{", 2);
! 928: for (;;) {
! 929: if ((ch = yylex_getc()) == EOF || ch == '\n')
! 930: goto error;
! 931: if (ch == '#') {
! 932: if ((ch = yylex_getc()) == EOF || ch == '\n')
! 933: goto error;
! 934: if (ch == '{')
! 935: brackets++;
! 936: yylex_append1(&buf, &len, '#');
! 937: } else if (ch == '}') {
! 938: if (brackets != 0 && --brackets == 0) {
! 939: yylex_append1(&buf, &len, ch);
! 940: break;
! 941: }
! 942: }
! 943: yylex_append1(&buf, &len, ch);
! 944: }
! 945: if (brackets != 0)
! 946: goto error;
! 947:
! 948: buf[len] = '\0';
! 949: log_debug("%s: %s", __func__, buf);
! 950: return (buf);
! 951:
! 952: error:
! 953: free(buf);
! 954: return (NULL);
! 955: }
! 956:
! 957: static int
! 958: yylex_token_escape(char **buf, size_t *len)
! 959: {
! 960: int ch, type;
! 961: u_int size, i, tmp;
! 962: char s[9];
! 963: struct utf8_data ud;
! 964:
! 965: switch (ch = yylex_getc()) {
! 966: case EOF:
! 967: return (0);
! 968: case 'e':
! 969: ch = '\033';
! 970: break;
! 971: case 'r':
! 972: ch = '\r';
! 973: break;
! 974: case 'n':
! 975: ch = '\n';
! 976: break;
! 977: case 't':
! 978: ch = '\t';
! 979: break;
! 980: case 'u':
! 981: type = 'u';
! 982: size = 4;
! 983: goto unicode;
! 984: case 'U':
! 985: type = 'U';
! 986: size = 8;
! 987: goto unicode;
! 988: }
! 989:
! 990: yylex_append1(buf, len, ch);
! 991: return (1);
! 992:
! 993: unicode:
! 994: for (i = 0; i < size; i++) {
! 995: ch = yylex_getc();
! 996: if (ch == EOF || ch == '\n')
! 997: return (0);
! 998: if (!isxdigit((u_char)ch)) {
! 999: yyerror("invalid \\%c argument", type);
! 1000: return (0);
! 1001: }
! 1002: s[i] = ch;
! 1003: }
! 1004: s[i] = '\0';
! 1005:
! 1006: if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) ||
! 1007: (size == 8 && sscanf(s, "%8x", &tmp) != 1)) {
! 1008: yyerror("invalid \\%c argument", type);
! 1009: return (0);
! 1010: }
! 1011: if (utf8_split(tmp, &ud) != UTF8_DONE) {
! 1012: yyerror("invalid \\%c argument", type);
! 1013: return (0);
! 1014: }
! 1015: yylex_append(buf, len, ud.data, ud.size);
! 1016: return (1);
! 1017: }
! 1018:
! 1019: static int
! 1020: yylex_token_variable(char **buf, size_t *len)
! 1021: {
! 1022: struct cmd_parse_state *ps = &parse_state;
! 1023: struct environ_entry *envent;
! 1024: int ch, brackets = 0;
! 1025: char name[BUFSIZ];
! 1026: size_t namelen = 0;
! 1027: const char *value;
! 1028:
! 1029: ch = yylex_getc();
! 1030: if (ch == EOF)
! 1031: return (0);
! 1032: if (ch == '{')
! 1033: brackets = 1;
! 1034: else {
! 1035: if (!yylex_is_var(ch, 1)) {
! 1036: yylex_append1(buf, len, '$');
! 1037: ungetc(ch, ps->f);
! 1038: return (1);
! 1039: }
! 1040: name[namelen++] = ch;
! 1041: }
! 1042:
! 1043: for (;;) {
! 1044: ch = yylex_getc();
! 1045: if (brackets && ch == '}')
! 1046: break;
! 1047: if (ch == EOF || !yylex_is_var(ch, 0)) {
! 1048: if (!brackets) {
! 1049: ungetc(ch, ps->f);
! 1050: break;
! 1051: }
! 1052: yyerror("invalid environment variable");
! 1053: return (0);
! 1054: }
! 1055: if (namelen == (sizeof name) - 2) {
! 1056: yyerror("environment variable is too long");
! 1057: return (0);
! 1058: }
! 1059: name[namelen++] = ch;
! 1060: }
! 1061: name[namelen] = '\0';
! 1062:
! 1063: envent = environ_find(global_environ, name);
! 1064: if (envent != NULL) {
! 1065: value = envent->value;
! 1066: log_debug("%s: %s -> %s", __func__, name, value);
! 1067: yylex_append(buf, len, value, strlen(value));
! 1068: }
! 1069: return (1);
! 1070: }
! 1071:
! 1072: static int
! 1073: yylex_token_tilde(char **buf, size_t *len)
! 1074: {
! 1075: struct cmd_parse_state *ps = &parse_state;
! 1076: struct environ_entry *envent;
! 1077: int ch;
! 1078: char name[BUFSIZ];
! 1079: size_t namelen = 0;
! 1080: struct passwd *pw;
! 1081: const char *home = NULL;
! 1082:
! 1083: for (;;) {
! 1084: ch = yylex_getc();
! 1085: if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) {
! 1086: ungetc(ch, ps->f);
! 1087: break;
! 1088: }
! 1089: if (namelen == (sizeof name) - 2) {
! 1090: yyerror("user name is too long");
! 1091: return (0);
! 1092: }
! 1093: name[namelen++] = ch;
! 1094: }
! 1095: name[namelen] = '\0';
! 1096:
! 1097: if (*name == '\0') {
! 1098: envent = environ_find(global_environ, "HOME");
! 1099: if (envent != NULL && *envent->value != '\0')
! 1100: home = envent->value;
! 1101: else if ((pw = getpwuid(getuid())) != NULL)
! 1102: home = pw->pw_dir;
! 1103: } else {
! 1104: if ((pw = getpwnam(name)) != NULL)
! 1105: home = pw->pw_dir;
! 1106: }
! 1107: if (home == NULL)
! 1108: return (0);
! 1109:
! 1110: log_debug("%s: ~%s -> %s", __func__, name, home);
! 1111: yylex_append(buf, len, home, strlen(home));
! 1112: return (1);
! 1113: }
! 1114:
! 1115: static char *
! 1116: yylex_token(int ch)
! 1117: {
! 1118: struct cmd_parse_state *ps = &parse_state;
! 1119: char *buf;
! 1120: size_t len;
! 1121: enum { START,
! 1122: NONE,
! 1123: DOUBLE_QUOTES,
! 1124: SINGLE_QUOTES } state = NONE, last = START;
! 1125:
! 1126: len = 0;
! 1127: buf = xmalloc(1);
! 1128:
! 1129: for (;;) {
! 1130: /*
! 1131: * EOF or \n are always the end of the token. If inside quotes
! 1132: * they are an error.
! 1133: */
! 1134: if (ch == EOF || ch == '\n') {
! 1135: if (state != NONE)
! 1136: goto error;
! 1137: break;
! 1138: }
! 1139:
! 1140: /* Whitespace or ; ends a token unless inside quotes. */
! 1141: if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE)
! 1142: break;
! 1143:
! 1144: /*
! 1145: * \ ~ and $ are expanded except in single quotes.
! 1146: */
! 1147: if (ch == '\\' && state != SINGLE_QUOTES) {
! 1148: if (!yylex_token_escape(&buf, &len))
! 1149: goto error;
! 1150: goto skip;
! 1151: }
! 1152: if (ch == '~' && last != state && state != SINGLE_QUOTES) {
! 1153: if (!yylex_token_tilde(&buf, &len))
! 1154: goto error;
! 1155: goto skip;
! 1156: }
! 1157: if (ch == '$' && state != SINGLE_QUOTES) {
! 1158: if (!yylex_token_variable(&buf, &len))
! 1159: goto error;
! 1160: goto skip;
! 1161: }
! 1162:
! 1163: /*
! 1164: * ' and " starts or end quotes (and is consumed).
! 1165: */
! 1166: if (ch == '\'') {
! 1167: if (state == NONE) {
! 1168: state = SINGLE_QUOTES;
! 1169: goto next;
! 1170: }
! 1171: if (state == SINGLE_QUOTES) {
! 1172: state = NONE;
! 1173: goto next;
! 1174: }
! 1175: }
! 1176: if (ch == '"') {
! 1177: if (state == NONE) {
! 1178: state = DOUBLE_QUOTES;
! 1179: goto next;
! 1180: }
! 1181: if (state == DOUBLE_QUOTES) {
! 1182: state = NONE;
! 1183: goto next;
! 1184: }
! 1185: }
! 1186:
! 1187: /*
! 1188: * Otherwise add the character to the buffer.
! 1189: */
! 1190: yylex_append1(&buf, &len, ch);
! 1191:
! 1192: skip:
! 1193: last = state;
! 1194:
! 1195: next:
! 1196: ch = yylex_getc();
! 1197: }
! 1198: ungetc(ch, ps->f);
! 1199:
! 1200: buf[len] = '\0';
! 1201: log_debug("%s: %s", __func__, buf);
! 1202: return (buf);
! 1203:
! 1204: error:
! 1205: free(buf);
! 1206: return (NULL);
! 1207: }