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