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