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