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