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