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