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