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