Annotation of src/usr.bin/tmux/cmd-parse.y, Revision 1.18
1.18 ! nicm 1: /* $OpenBSD: cmd-parse.y,v 1.17 2019/06/18 11:17:40 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);
699: goto out;
700: }
701: cmd_list_append(cmdlist, add);
702: }
703: if (cmdlist != NULL) {
1.14 nicm 704: cmd_parse_print_commands(pi, line, cmdlist);
1.1 nicm 705: cmd_list_move(result, cmdlist);
706: cmd_list_free(cmdlist);
707: }
708:
1.2 nicm 709: s = cmd_list_print(result, 0);
1.1 nicm 710: log_debug("%s: %s", __func__, s);
711: free(s);
712:
713: pr.status = CMD_PARSE_SUCCESS;
714: pr.cmdlist = result;
715:
716: out:
717: cmd_parse_free_commands(cmds);
718:
719: return (&pr);
720: }
721:
722: struct cmd_parse_result *
1.4 nicm 723: cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi)
724: {
725: static struct cmd_parse_result pr;
726: struct cmd_parse_input input;
727: struct cmd_parse_commands *cmds;
728: char *cause;
729:
730: if (pi == NULL) {
731: memset(&input, 0, sizeof input);
732: pi = &input;
733: }
734: memset(&pr, 0, sizeof pr);
735:
1.5 nicm 736: cmds = cmd_parse_do_file(f, pi, &cause);
1.4 nicm 737: if (cmds == NULL) {
738: pr.status = CMD_PARSE_ERROR;
739: pr.error = cause;
740: return (&pr);
741: }
742: return (cmd_parse_build_commands(cmds, pi));
743: }
744:
745: struct cmd_parse_result *
1.1 nicm 746: cmd_parse_from_string(const char *s, struct cmd_parse_input *pi)
747: {
748: static struct cmd_parse_result pr;
1.4 nicm 749: struct cmd_parse_input input;
1.5 nicm 750: struct cmd_parse_commands *cmds;
751: char *cause;
1.1 nicm 752:
1.4 nicm 753: if (pi == NULL) {
754: memset(&input, 0, sizeof input);
755: pi = &input;
756: }
757: memset(&pr, 0, sizeof pr);
758:
1.1 nicm 759: if (*s == '\0') {
760: pr.status = CMD_PARSE_EMPTY;
761: pr.cmdlist = NULL;
762: pr.error = NULL;
763: return (&pr);
764: }
765:
1.5 nicm 766: cmds = cmd_parse_do_buffer(s, strlen(s), pi, &cause);
767: if (cmds == NULL) {
1.1 nicm 768: pr.status = CMD_PARSE_ERROR;
1.5 nicm 769: pr.error = cause;
770: return (&pr);
1.1 nicm 771: }
1.5 nicm 772: return (cmd_parse_build_commands(cmds, pi));
1.4 nicm 773: }
774:
775: struct cmd_parse_result *
776: cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi)
777: {
778: struct cmd_parse_input input;
779: struct cmd_parse_commands *cmds;
780: struct cmd_parse_command *cmd;
781: char **copy, **new_argv;
782: size_t size;
783: int i, last, new_argc;
784:
785: /*
786: * The commands are already split up into arguments, so just separate
787: * into a set of commands by ';'.
788: */
789:
790: if (pi == NULL) {
791: memset(&input, 0, sizeof input);
792: pi = &input;
793: }
794: cmd_log_argv(argc, argv, "%s", __func__);
795:
1.13 nicm 796: cmds = cmd_parse_new_commands();
1.4 nicm 797: copy = cmd_copy_argv(argc, argv);
798:
799: last = 0;
800: for (i = 0; i < argc; i++) {
801: size = strlen(copy[i]);
802: if (size == 0 || copy[i][size - 1] != ';')
803: continue;
804: copy[i][--size] = '\0';
805: if (size > 0 && copy[i][size - 1] == '\\') {
806: copy[i][size - 1] = ';';
807: continue;
808: }
809:
810: new_argc = i - last;
811: new_argv = copy + last;
812: if (size != 0)
813: new_argc++;
814:
815: if (new_argc != 0) {
816: cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__,
817: i);
818:
819: cmd = xcalloc(1, sizeof *cmd);
820: cmd->name = xstrdup(new_argv[0]);
821: cmd->line = pi->line;
822:
823: cmd->argc = new_argc - 1;
824: cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
825:
826: TAILQ_INSERT_TAIL(cmds, cmd, entry);
827: }
828:
829: last = i + 1;
830: }
831: if (last != argc) {
832: new_argv = copy + last;
833: new_argc = argc - last;
834:
835: if (new_argc != 0) {
836: cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__,
837: last);
838:
839: cmd = xcalloc(1, sizeof *cmd);
840: cmd->name = xstrdup(new_argv[0]);
841: cmd->line = pi->line;
842:
843: cmd->argc = new_argc - 1;
844: cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
845:
846: TAILQ_INSERT_TAIL(cmds, cmd, entry);
847: }
848: }
1.13 nicm 849:
850: cmd_free_argv(argc, copy);
1.4 nicm 851: return (cmd_parse_build_commands(cmds, pi));
1.1 nicm 852: }
853:
854: static int printflike(1, 2)
855: yyerror(const char *fmt, ...)
856: {
857: struct cmd_parse_state *ps = &parse_state;
858: struct cmd_parse_input *pi = ps->input;
859: va_list ap;
860: char *error;
861:
862: if (ps->error != NULL)
863: return (0);
864:
865: va_start(ap, fmt);
866: xvasprintf(&error, fmt, ap);
867: va_end(ap);
868:
869: ps->error = cmd_parse_get_error(pi->file, pi->line, error);
870: free(error);
871: return (0);
872: }
873:
874: static int
875: yylex_is_var(char ch, int first)
876: {
877: if (ch == '=')
878: return (0);
879: if (first && isdigit((u_char)ch))
880: return (0);
881: return (isalnum((u_char)ch) || ch == '_');
882: }
883:
884: static void
885: yylex_append(char **buf, size_t *len, const char *add, size_t addlen)
886: {
887: if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen)
888: fatalx("buffer is too big");
889: *buf = xrealloc(*buf, (*len) + 1 + addlen);
890: memcpy((*buf) + *len, add, addlen);
891: (*len) += addlen;
892: }
893:
894: static void
895: yylex_append1(char **buf, size_t *len, char add)
896: {
897: yylex_append(buf, len, &add, 1);
898: }
899:
900: static int
1.5 nicm 901: yylex_getc1(void)
902: {
903: struct cmd_parse_state *ps = &parse_state;
904: int ch;
905:
906: if (ps->f != NULL)
907: ch = getc(ps->f);
908: else {
909: if (ps->off == ps->len)
910: ch = EOF;
911: else
912: ch = ps->buf[ps->off++];
913: }
914: return (ch);
915: }
916:
917: static void
918: yylex_ungetc(int ch)
919: {
920: struct cmd_parse_state *ps = &parse_state;
921:
922: if (ps->f != NULL)
923: ungetc(ch, ps->f);
924: else if (ps->off > 0 && ch != EOF)
925: ps->off--;
926: }
927:
928: static int
1.1 nicm 929: yylex_getc(void)
930: {
931: struct cmd_parse_state *ps = &parse_state;
932: int ch;
933:
934: if (ps->escapes != 0) {
935: ps->escapes--;
936: return ('\\');
937: }
938: for (;;) {
1.5 nicm 939: ch = yylex_getc1();
1.1 nicm 940: if (ch == '\\') {
941: ps->escapes++;
942: continue;
943: }
944: if (ch == '\n' && (ps->escapes % 2) == 1) {
945: ps->input->line++;
946: ps->escapes--;
947: continue;
948: }
949:
950: if (ps->escapes != 0) {
1.5 nicm 951: yylex_ungetc(ch);
1.1 nicm 952: ps->escapes--;
953: return ('\\');
954: }
955: return (ch);
956: }
957: }
958:
959: static char *
960: yylex_get_word(int ch)
961: {
1.5 nicm 962: char *buf;
963: size_t len;
1.1 nicm 964:
965: len = 0;
966: buf = xmalloc(1);
967:
968: do
969: yylex_append1(&buf, &len, ch);
970: while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL);
1.5 nicm 971: yylex_ungetc(ch);
1.1 nicm 972:
973: buf[len] = '\0';
974: log_debug("%s: %s", __func__, buf);
975: return (buf);
976: }
977:
978: static int
979: yylex(void)
980: {
981: struct cmd_parse_state *ps = &parse_state;
982: char *token, *cp;
1.15 nicm 983: int ch, next, condition;
1.1 nicm 984:
1.8 nicm 985: if (ps->eol)
986: ps->input->line++;
987: ps->eol = 0;
988:
1.15 nicm 989: condition = ps->condition;
990: ps->condition = 0;
991:
1.1 nicm 992: for (;;) {
993: ch = yylex_getc();
994:
995: if (ch == EOF) {
996: /*
997: * Ensure every file or string is terminated by a
998: * newline. This keeps the parser simpler and avoids
999: * having to add a newline to each string.
1000: */
1001: if (ps->eof)
1002: break;
1003: ps->eof = 1;
1004: return ('\n');
1005: }
1006:
1007: if (ch == ' ' || ch == '\t') {
1008: /*
1009: * Ignore whitespace.
1010: */
1011: continue;
1012: }
1013:
1014: if (ch == '\n') {
1015: /*
1016: * End of line. Update the line number.
1017: */
1.8 nicm 1018: ps->eol = 1;
1.1 nicm 1019: return ('\n');
1020: }
1021:
1022: if (ch == ';') {
1023: /*
1024: * A semicolon is itself.
1025: */
1026: return (';');
1027: }
1028:
1029: if (ch == '#') {
1030: /*
1.15 nicm 1031: * #{ after a condition opens a format; anything else
1032: * is a comment, ignore up to the end of the line.
1.1 nicm 1033: */
1034: next = yylex_getc();
1.15 nicm 1035: if (condition && next == '{') {
1.1 nicm 1036: yylval.token = yylex_format();
1037: if (yylval.token == NULL)
1038: return (ERROR);
1039: return (FORMAT);
1040: }
1041: while (next != '\n' && next != EOF)
1042: next = yylex_getc();
1043: if (next == '\n') {
1044: ps->input->line++;
1045: return ('\n');
1046: }
1047: continue;
1048: }
1049:
1050: if (ch == '%') {
1051: /*
1.11 nicm 1052: * % is a condition unless it is all % or all numbers,
1053: * then it is a token.
1.1 nicm 1054: */
1055: yylval.token = yylex_get_word('%');
1.11 nicm 1056: for (cp = yylval.token; *cp != '\0'; cp++) {
1057: if (*cp != '%' && !isdigit((u_char)*cp))
1058: break;
1059: }
1060: if (*cp == '\0')
1.1 nicm 1061: return (TOKEN);
1.15 nicm 1062: ps->condition = 1;
1.1 nicm 1063: if (strcmp(yylval.token, "%if") == 0) {
1064: free(yylval.token);
1065: return (IF);
1066: }
1067: if (strcmp(yylval.token, "%else") == 0) {
1068: free(yylval.token);
1069: return (ELSE);
1070: }
1071: if (strcmp(yylval.token, "%elif") == 0) {
1072: free(yylval.token);
1073: return (ELIF);
1074: }
1075: if (strcmp(yylval.token, "%endif") == 0) {
1076: free(yylval.token);
1077: return (ENDIF);
1078: }
1079: free(yylval.token);
1080: return (ERROR);
1081: }
1082:
1083: /*
1084: * Otherwise this is a token.
1085: */
1086: token = yylex_token(ch);
1087: if (token == NULL)
1088: return (ERROR);
1089: yylval.token = token;
1090:
1091: if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) {
1092: for (cp = token + 1; *cp != '='; cp++) {
1093: if (!yylex_is_var(*cp, 0))
1094: break;
1095: }
1096: if (*cp == '=')
1097: return (EQUALS);
1098: }
1099: return (TOKEN);
1100: }
1101: return (0);
1102: }
1103:
1104: static char *
1105: yylex_format(void)
1106: {
1107: char *buf;
1108: size_t len;
1109: int ch, brackets = 1;
1110:
1111: len = 0;
1112: buf = xmalloc(1);
1113:
1114: yylex_append(&buf, &len, "#{", 2);
1115: for (;;) {
1116: if ((ch = yylex_getc()) == EOF || ch == '\n')
1117: goto error;
1118: if (ch == '#') {
1119: if ((ch = yylex_getc()) == EOF || ch == '\n')
1120: goto error;
1121: if (ch == '{')
1122: brackets++;
1123: yylex_append1(&buf, &len, '#');
1124: } else if (ch == '}') {
1125: if (brackets != 0 && --brackets == 0) {
1126: yylex_append1(&buf, &len, ch);
1127: break;
1128: }
1129: }
1130: yylex_append1(&buf, &len, ch);
1131: }
1132: if (brackets != 0)
1133: goto error;
1134:
1135: buf[len] = '\0';
1136: log_debug("%s: %s", __func__, buf);
1137: return (buf);
1138:
1139: error:
1140: free(buf);
1141: return (NULL);
1142: }
1143:
1144: static int
1145: yylex_token_escape(char **buf, size_t *len)
1146: {
1.7 nicm 1147: int ch, type, o2, o3;
1.1 nicm 1148: u_int size, i, tmp;
1149: char s[9];
1150: struct utf8_data ud;
1151:
1.7 nicm 1152: ch = yylex_getc();
1153:
1154: if (ch >= '4' && ch <= '7') {
1155: yyerror("invalid octal escape");
1156: return (0);
1157: }
1158: if (ch >= '0' && ch <= '3') {
1159: o2 = yylex_getc();
1160: if (o2 >= '0' && o2 <= '7') {
1161: o3 = yylex_getc();
1162: if (o3 >= '0' && o3 <= '7') {
1163: ch = 64 * (ch - '0') +
1164: 8 * (o2 - '0') +
1165: (o3 - '0');
1166: yylex_append1(buf, len, ch);
1167: return (1);
1168: }
1169: }
1170: yyerror("invalid octal escape");
1171: return (0);
1172: }
1173:
1174: switch (ch) {
1.1 nicm 1175: case EOF:
1176: return (0);
1.9 nicm 1177: case 'a':
1178: ch = '\a';
1179: break;
1180: case 'b':
1181: ch = '\b';
1182: break;
1.1 nicm 1183: case 'e':
1184: ch = '\033';
1.9 nicm 1185: break;
1186: case 'f':
1187: ch = '\f';
1188: break;
1189: case 's':
1190: ch = ' ';
1191: break;
1192: case 'v':
1193: ch = '\v';
1.1 nicm 1194: break;
1195: case 'r':
1196: ch = '\r';
1197: break;
1198: case 'n':
1199: ch = '\n';
1200: break;
1201: case 't':
1202: ch = '\t';
1203: break;
1204: case 'u':
1205: type = 'u';
1206: size = 4;
1207: goto unicode;
1208: case 'U':
1209: type = 'U';
1210: size = 8;
1211: goto unicode;
1212: }
1213:
1214: yylex_append1(buf, len, ch);
1215: return (1);
1216:
1217: unicode:
1218: for (i = 0; i < size; i++) {
1219: ch = yylex_getc();
1220: if (ch == EOF || ch == '\n')
1221: return (0);
1222: if (!isxdigit((u_char)ch)) {
1223: yyerror("invalid \\%c argument", type);
1224: return (0);
1225: }
1226: s[i] = ch;
1227: }
1228: s[i] = '\0';
1229:
1230: if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) ||
1231: (size == 8 && sscanf(s, "%8x", &tmp) != 1)) {
1232: yyerror("invalid \\%c argument", type);
1233: return (0);
1234: }
1235: if (utf8_split(tmp, &ud) != UTF8_DONE) {
1236: yyerror("invalid \\%c argument", type);
1237: return (0);
1238: }
1239: yylex_append(buf, len, ud.data, ud.size);
1240: return (1);
1241: }
1242:
1243: static int
1244: yylex_token_variable(char **buf, size_t *len)
1245: {
1246: struct environ_entry *envent;
1247: int ch, brackets = 0;
1248: char name[BUFSIZ];
1249: size_t namelen = 0;
1250: const char *value;
1251:
1252: ch = yylex_getc();
1253: if (ch == EOF)
1254: return (0);
1255: if (ch == '{')
1256: brackets = 1;
1257: else {
1258: if (!yylex_is_var(ch, 1)) {
1259: yylex_append1(buf, len, '$');
1.5 nicm 1260: yylex_ungetc(ch);
1.1 nicm 1261: return (1);
1262: }
1263: name[namelen++] = ch;
1264: }
1265:
1266: for (;;) {
1267: ch = yylex_getc();
1268: if (brackets && ch == '}')
1269: break;
1270: if (ch == EOF || !yylex_is_var(ch, 0)) {
1271: if (!brackets) {
1.5 nicm 1272: yylex_ungetc(ch);
1.1 nicm 1273: break;
1274: }
1275: yyerror("invalid environment variable");
1276: return (0);
1277: }
1278: if (namelen == (sizeof name) - 2) {
1279: yyerror("environment variable is too long");
1280: return (0);
1281: }
1282: name[namelen++] = ch;
1283: }
1284: name[namelen] = '\0';
1285:
1286: envent = environ_find(global_environ, name);
1287: if (envent != NULL) {
1288: value = envent->value;
1289: log_debug("%s: %s -> %s", __func__, name, value);
1290: yylex_append(buf, len, value, strlen(value));
1291: }
1292: return (1);
1293: }
1294:
1295: static int
1296: yylex_token_tilde(char **buf, size_t *len)
1297: {
1298: struct environ_entry *envent;
1299: int ch;
1300: char name[BUFSIZ];
1301: size_t namelen = 0;
1302: struct passwd *pw;
1303: const char *home = NULL;
1304:
1305: for (;;) {
1306: ch = yylex_getc();
1307: if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) {
1.5 nicm 1308: yylex_ungetc(ch);
1.1 nicm 1309: break;
1310: }
1311: if (namelen == (sizeof name) - 2) {
1312: yyerror("user name is too long");
1313: return (0);
1314: }
1315: name[namelen++] = ch;
1316: }
1317: name[namelen] = '\0';
1318:
1319: if (*name == '\0') {
1320: envent = environ_find(global_environ, "HOME");
1321: if (envent != NULL && *envent->value != '\0')
1322: home = envent->value;
1323: else if ((pw = getpwuid(getuid())) != NULL)
1324: home = pw->pw_dir;
1325: } else {
1326: if ((pw = getpwnam(name)) != NULL)
1327: home = pw->pw_dir;
1328: }
1329: if (home == NULL)
1330: return (0);
1331:
1332: log_debug("%s: ~%s -> %s", __func__, name, home);
1333: yylex_append(buf, len, home, strlen(home));
1334: return (1);
1335: }
1336:
1.6 nicm 1337: static int
1338: yylex_token_brace(char **buf, size_t *len)
1339: {
1340: struct cmd_parse_state *ps = &parse_state;
1.17 nicm 1341: int ch, lines = 0, nesting = 1, escape = 0;
1342: int quote = '\0', token = 0;
1.6 nicm 1343:
1344: /*
1345: * Extract a string up to the matching unquoted '}', including newlines
1346: * and handling nested braces.
1347: *
1348: * To detect the final and intermediate braces which affect the nesting
1349: * depth, we scan the input as if it was a tmux config file, and ignore
1350: * braces which would be considered quoted, escaped, or in a comment.
1351: *
1.17 nicm 1352: * We update the token state after every character because '#' begins a
1353: * comment only when it begins a token. For simplicity, we treat an
1354: * unquoted directive format as comment.
1355: *
1.6 nicm 1356: * The result is verbatim copy of the input excluding the final brace.
1357: */
1358:
1359: for (ch = yylex_getc1(); ch != EOF; ch = yylex_getc1()) {
1360: yylex_append1(buf, len, ch);
1361: if (ch == '\n')
1362: lines++;
1363:
1364: /*
1365: * If the previous character was a backslash (escape is set),
1366: * escape anything if unquoted or in double quotes, otherwise
1367: * escape only '\n' and '\\'.
1368: */
1369: if (escape &&
1370: (quote == '\0' ||
1371: quote == '"' ||
1372: ch == '\n' ||
1373: ch == '\\')) {
1374: escape = 0;
1.17 nicm 1375: if (ch != '\n')
1376: token = 1;
1.6 nicm 1377: continue;
1378: }
1379:
1380: /*
1381: * The character is not escaped. If it is a backslash, set the
1382: * escape flag.
1383: */
1384: if (ch == '\\') {
1385: escape = 1;
1386: continue;
1387: }
1388: escape = 0;
1389:
1390: /* A newline always resets to unquoted. */
1391: if (ch == '\n') {
1.17 nicm 1392: quote = token = 0;
1.6 nicm 1393: continue;
1394: }
1395:
1396: if (quote) {
1397: /*
1398: * Inside quotes or comment. Check if this is the
1399: * closing quote.
1400: */
1401: if (ch == quote && quote != '#')
1402: quote = 0;
1.17 nicm 1403: token = 1; /* token continues regardless */
1404: } else {
1.6 nicm 1405: /* Not inside quotes or comment. */
1406: switch (ch) {
1407: case '"':
1408: case '\'':
1409: case '#':
1.17 nicm 1410: /* Beginning of quote or maybe comment. */
1411: if (ch != '#' || !token)
1412: quote = ch;
1413: token = 1;
1414: break;
1415: case ' ':
1416: case '\t':
1417: case ';':
1418: /* Delimiter - token resets. */
1419: token = 0;
1.6 nicm 1420: break;
1421: case '{':
1422: nesting++;
1.17 nicm 1423: token = 0; /* new commands set - token resets */
1.6 nicm 1424: break;
1425: case '}':
1426: nesting--;
1.17 nicm 1427: token = 1; /* same as after quotes */
1.6 nicm 1428: if (nesting == 0) {
1429: (*len)--; /* remove closing } */
1430: ps->input->line += lines;
1431: return (1);
1432: }
1433: break;
1.17 nicm 1434: default:
1435: token = 1;
1436: break;
1.6 nicm 1437: }
1438: }
1439: }
1440:
1441: /*
1.17 nicm 1442: * Update line count after error as reporting the opening line is more
1443: * useful than EOF.
1.6 nicm 1444: */
1445: yyerror("unterminated brace string");
1446: ps->input->line += lines;
1447: return (0);
1448: }
1449:
1.1 nicm 1450: static char *
1451: yylex_token(int ch)
1452: {
1453: char *buf;
1454: size_t len;
1455: enum { START,
1456: NONE,
1457: DOUBLE_QUOTES,
1458: SINGLE_QUOTES } state = NONE, last = START;
1459:
1460: len = 0;
1461: buf = xmalloc(1);
1462:
1463: for (;;) {
1464: /*
1465: * EOF or \n are always the end of the token. If inside quotes
1466: * they are an error.
1467: */
1468: if (ch == EOF || ch == '\n') {
1469: if (state != NONE)
1470: goto error;
1471: break;
1472: }
1473:
1474: /* Whitespace or ; ends a token unless inside quotes. */
1475: if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE)
1476: break;
1477:
1478: /*
1479: * \ ~ and $ are expanded except in single quotes.
1480: */
1481: if (ch == '\\' && state != SINGLE_QUOTES) {
1482: if (!yylex_token_escape(&buf, &len))
1483: goto error;
1484: goto skip;
1485: }
1486: if (ch == '~' && last != state && state != SINGLE_QUOTES) {
1487: if (!yylex_token_tilde(&buf, &len))
1488: goto error;
1489: goto skip;
1490: }
1491: if (ch == '$' && state != SINGLE_QUOTES) {
1492: if (!yylex_token_variable(&buf, &len))
1493: goto error;
1494: goto skip;
1495: }
1.6 nicm 1496: if (ch == '{' && state == NONE) {
1497: if (!yylex_token_brace(&buf, &len))
1498: goto error;
1499: goto skip;
1500: }
1501: if (ch == '}' && state == NONE)
1502: goto error; /* unmatched (matched ones were handled) */
1.1 nicm 1503:
1504: /*
1505: * ' and " starts or end quotes (and is consumed).
1506: */
1507: if (ch == '\'') {
1508: if (state == NONE) {
1509: state = SINGLE_QUOTES;
1510: goto next;
1511: }
1512: if (state == SINGLE_QUOTES) {
1513: state = NONE;
1514: goto next;
1515: }
1516: }
1517: if (ch == '"') {
1518: if (state == NONE) {
1519: state = DOUBLE_QUOTES;
1520: goto next;
1521: }
1522: if (state == DOUBLE_QUOTES) {
1523: state = NONE;
1524: goto next;
1525: }
1526: }
1527:
1528: /*
1529: * Otherwise add the character to the buffer.
1530: */
1531: yylex_append1(&buf, &len, ch);
1532:
1533: skip:
1534: last = state;
1535:
1536: next:
1537: ch = yylex_getc();
1538: }
1.5 nicm 1539: yylex_ungetc(ch);
1.1 nicm 1540:
1541: buf[len] = '\0';
1542: log_debug("%s: %s", __func__, buf);
1543: return (buf);
1544:
1545: error:
1546: free(buf);
1547: return (NULL);
1548: }