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