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