Annotation of src/usr.bin/tmux/cmd-parse.y, Revision 1.4
1.4 ! nicm 1: /* $OpenBSD: cmd-parse.y,v 1.3 2019/05/23 18:39:00 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:
1.4 ! nicm 544: static struct cmd_parse_result *
! 545: cmd_parse_build_commands(struct cmd_parse_commands *cmds,
! 546: struct cmd_parse_input *pi)
1.1 nicm 547: {
548: static struct cmd_parse_result pr;
1.4 ! nicm 549: struct cmd_parse_commands *cmds2;
1.1 nicm 550: struct cmd_parse_command *cmd, *cmd2, *next, *next2, *after;
1.4 ! nicm 551: FILE *f;
1.1 nicm 552: u_int line = UINT_MAX;
553: int i;
554: struct cmd_list *cmdlist = NULL, *result;
555: struct cmd *add;
556: char *alias, *cause, *s;
557:
1.4 ! nicm 558: /* Check for an empty list. */
1.1 nicm 559: if (TAILQ_EMPTY(cmds)) {
560: free(cmds);
561: pr.status = CMD_PARSE_EMPTY;
562: return (&pr);
563: }
564:
565: /*
566: * Walk the commands and expand any aliases. Each alias is parsed
567: * individually to a new command list, any trailing arguments appended
568: * to the last command, and all commands inserted into the original
569: * command list.
570: */
571: TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) {
572: alias = cmd_get_alias(cmd->name);
573: if (alias == NULL)
574: continue;
575:
576: line = cmd->line;
577: log_debug("%s: %u %s = %s", __func__, line, cmd->name, alias);
578:
579: f = fmemopen(alias, strlen(alias), "r");
580: if (f == NULL) {
581: free(alias);
582: pr.status = CMD_PARSE_ERROR;
583: pr.error = cmd_parse_get_strerror(pi->file, line);
584: goto out;
585: }
586: pi->line = line;
587: cmds2 = cmd_parse_run_parser(f, pi, &cause);
588: fclose(f);
589: free(alias);
590: if (cmds2 == NULL) {
591: pr.status = CMD_PARSE_ERROR;
592: pr.error = cause;
593: goto out;
594: }
595:
596: cmd2 = TAILQ_LAST(cmds2, cmd_parse_commands);
597: if (cmd2 == NULL) {
598: TAILQ_REMOVE(cmds, cmd, entry);
599: cmd_parse_free_command(cmd);
600: continue;
601: }
602: for (i = 0; i < cmd->argc; i++)
603: cmd_append_argv(&cmd2->argc, &cmd2->argv, cmd->argv[i]);
604:
605: after = cmd;
606: TAILQ_FOREACH_SAFE(cmd2, cmds2, entry, next2) {
607: cmd2->line = line;
608: TAILQ_REMOVE(cmds2, cmd2, entry);
609: TAILQ_INSERT_AFTER(cmds, after, cmd2, entry);
610: after = cmd2;
611: }
612: cmd_parse_free_commands(cmds2);
613:
614: TAILQ_REMOVE(cmds, cmd, entry);
615: cmd_parse_free_command(cmd);
616: }
617:
618: /*
619: * Parse each command into a command list. Create a new command list
620: * for each line so they get a new group (so the queue knows which ones
621: * to remove if a command fails when executed).
622: */
623: result = cmd_list_new();
624: TAILQ_FOREACH(cmd, cmds, entry) {
625: log_debug("%s: %u %s", __func__, cmd->line, cmd->name);
626: cmd_log_argv(cmd->argc, cmd->argv, __func__);
627:
628: if (cmdlist == NULL || cmd->line != line) {
629: if (cmdlist != NULL) {
630: cmd_list_move(result, cmdlist);
631: cmd_list_free(cmdlist);
632: }
633: cmdlist = cmd_list_new();
634: }
635: line = cmd->line;
636:
637: cmd_prepend_argv(&cmd->argc, &cmd->argv, cmd->name);
638: add = cmd_parse(cmd->argc, cmd->argv, pi->file, line, &cause);
639: if (add == NULL) {
640: cmd_list_free(result);
641: pr.status = CMD_PARSE_ERROR;
642: pr.error = cmd_parse_get_error(pi->file, line, cause);
643: free(cause);
644: goto out;
645: }
646: cmd_list_append(cmdlist, add);
647: }
648: if (cmdlist != NULL) {
649: cmd_list_move(result, cmdlist);
650: cmd_list_free(cmdlist);
651: }
652:
1.2 nicm 653: s = cmd_list_print(result, 0);
1.1 nicm 654: log_debug("%s: %s", __func__, s);
655: free(s);
656:
657: pr.status = CMD_PARSE_SUCCESS;
658: pr.cmdlist = result;
659:
660: out:
661: cmd_parse_free_commands(cmds);
662: free(cmds);
663:
664: return (&pr);
665: }
666:
667: struct cmd_parse_result *
1.4 ! nicm 668: cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi)
! 669: {
! 670: static struct cmd_parse_result pr;
! 671: struct cmd_parse_input input;
! 672: struct cmd_parse_commands *cmds;
! 673: char *cause;
! 674:
! 675: if (pi == NULL) {
! 676: memset(&input, 0, sizeof input);
! 677: pi = &input;
! 678: }
! 679: memset(&pr, 0, sizeof pr);
! 680:
! 681: /*
! 682: * Parse the file into a list of commands.
! 683: */
! 684: cmds = cmd_parse_run_parser(f, pi, &cause);
! 685: if (cmds == NULL) {
! 686: pr.status = CMD_PARSE_ERROR;
! 687: pr.error = cause;
! 688: return (&pr);
! 689: }
! 690: return (cmd_parse_build_commands(cmds, pi));
! 691: }
! 692:
! 693: struct cmd_parse_result *
1.1 nicm 694: cmd_parse_from_string(const char *s, struct cmd_parse_input *pi)
695: {
696: static struct cmd_parse_result pr;
697: struct cmd_parse_result *prp;
1.4 ! nicm 698: struct cmd_parse_input input;
1.1 nicm 699: FILE *f;
700:
1.4 ! nicm 701: if (pi == NULL) {
! 702: memset(&input, 0, sizeof input);
! 703: pi = &input;
! 704: }
! 705: memset(&pr, 0, sizeof pr);
! 706:
1.1 nicm 707: if (*s == '\0') {
708: pr.status = CMD_PARSE_EMPTY;
709: pr.cmdlist = NULL;
710: pr.error = NULL;
711: return (&pr);
712: }
713:
714: f = fmemopen((void *)s, strlen(s), "r");
715: if (f == NULL) {
716: pr.status = CMD_PARSE_ERROR;
717: pr.cmdlist = NULL;
718: pr.error = cmd_parse_get_strerror(pi->file, pi->line);
719: return (NULL);
720: }
721: prp = cmd_parse_from_file(f, pi);
722: fclose(f);
723: return (prp);
1.4 ! nicm 724: }
! 725:
! 726: struct cmd_parse_result *
! 727: cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi)
! 728: {
! 729: struct cmd_parse_input input;
! 730: struct cmd_parse_commands *cmds;
! 731: struct cmd_parse_command *cmd;
! 732: char **copy, **new_argv;
! 733: size_t size;
! 734: int i, last, new_argc;
! 735:
! 736: /*
! 737: * The commands are already split up into arguments, so just separate
! 738: * into a set of commands by ';'.
! 739: */
! 740:
! 741: if (pi == NULL) {
! 742: memset(&input, 0, sizeof input);
! 743: pi = &input;
! 744: }
! 745: cmd_log_argv(argc, argv, "%s", __func__);
! 746:
! 747: cmds = xmalloc(sizeof *cmds);
! 748: TAILQ_INIT(cmds);
! 749: copy = cmd_copy_argv(argc, argv);
! 750:
! 751: last = 0;
! 752: for (i = 0; i < argc; i++) {
! 753: size = strlen(copy[i]);
! 754: if (size == 0 || copy[i][size - 1] != ';')
! 755: continue;
! 756: copy[i][--size] = '\0';
! 757: if (size > 0 && copy[i][size - 1] == '\\') {
! 758: copy[i][size - 1] = ';';
! 759: continue;
! 760: }
! 761:
! 762: new_argc = i - last;
! 763: new_argv = copy + last;
! 764: if (size != 0)
! 765: new_argc++;
! 766:
! 767: if (new_argc != 0) {
! 768: cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__,
! 769: i);
! 770:
! 771: cmd = xcalloc(1, sizeof *cmd);
! 772: cmd->name = xstrdup(new_argv[0]);
! 773: cmd->line = pi->line;
! 774:
! 775: cmd->argc = new_argc - 1;
! 776: cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
! 777:
! 778: TAILQ_INSERT_TAIL(cmds, cmd, entry);
! 779: }
! 780:
! 781: last = i + 1;
! 782: }
! 783: if (last != argc) {
! 784: new_argv = copy + last;
! 785: new_argc = argc - last;
! 786:
! 787: if (new_argc != 0) {
! 788: cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__,
! 789: last);
! 790:
! 791: cmd = xcalloc(1, sizeof *cmd);
! 792: cmd->name = xstrdup(new_argv[0]);
! 793: cmd->line = pi->line;
! 794:
! 795: cmd->argc = new_argc - 1;
! 796: cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
! 797:
! 798: TAILQ_INSERT_TAIL(cmds, cmd, entry);
! 799: }
! 800: }
! 801: return (cmd_parse_build_commands(cmds, pi));
1.1 nicm 802: }
803:
804: static int printflike(1, 2)
805: yyerror(const char *fmt, ...)
806: {
807: struct cmd_parse_state *ps = &parse_state;
808: struct cmd_parse_input *pi = ps->input;
809: va_list ap;
810: char *error;
811:
812: if (ps->error != NULL)
813: return (0);
814:
815: va_start(ap, fmt);
816: xvasprintf(&error, fmt, ap);
817: va_end(ap);
818:
819: ps->error = cmd_parse_get_error(pi->file, pi->line, error);
820: free(error);
821: return (0);
822: }
823:
824: static int
825: yylex_is_var(char ch, int first)
826: {
827: if (ch == '=')
828: return (0);
829: if (first && isdigit((u_char)ch))
830: return (0);
831: return (isalnum((u_char)ch) || ch == '_');
832: }
833:
834: static void
835: yylex_append(char **buf, size_t *len, const char *add, size_t addlen)
836: {
837: if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen)
838: fatalx("buffer is too big");
839: *buf = xrealloc(*buf, (*len) + 1 + addlen);
840: memcpy((*buf) + *len, add, addlen);
841: (*len) += addlen;
842: }
843:
844: static void
845: yylex_append1(char **buf, size_t *len, char add)
846: {
847: yylex_append(buf, len, &add, 1);
848: }
849:
850: static int
851: yylex_getc(void)
852: {
853: struct cmd_parse_state *ps = &parse_state;
854: int ch;
855:
856: if (ps->escapes != 0) {
857: ps->escapes--;
858: return ('\\');
859: }
860: for (;;) {
861: ch = getc(ps->f);
862: if (ch == '\\') {
863: ps->escapes++;
864: continue;
865: }
866: if (ch == '\n' && (ps->escapes % 2) == 1) {
867: ps->input->line++;
868: ps->escapes--;
869: continue;
870: }
871:
872: if (ps->escapes != 0) {
873: ungetc(ch, ps->f);
874: ps->escapes--;
875: return ('\\');
876: }
877: return (ch);
878: }
879: }
880:
881: static char *
882: yylex_get_word(int ch)
883: {
884: struct cmd_parse_state *ps = &parse_state;
885: char *buf;
886: size_t len;
887:
888: len = 0;
889: buf = xmalloc(1);
890:
891: do
892: yylex_append1(&buf, &len, ch);
893: while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL);
894: ungetc(ch, ps->f);
895:
896: buf[len] = '\0';
897: log_debug("%s: %s", __func__, buf);
898: return (buf);
899: }
900:
901: static int
902: yylex(void)
903: {
904: struct cmd_parse_state *ps = &parse_state;
905: char *token, *cp;
906: int ch, next;
907:
908: for (;;) {
909: ch = yylex_getc();
910:
911: if (ch == EOF) {
912: /*
913: * Ensure every file or string is terminated by a
914: * newline. This keeps the parser simpler and avoids
915: * having to add a newline to each string.
916: */
917: if (ps->eof)
918: break;
919: ps->eof = 1;
920: return ('\n');
921: }
922:
923: if (ch == ' ' || ch == '\t') {
924: /*
925: * Ignore whitespace.
926: */
927: continue;
928: }
929:
930: if (ch == '\n') {
931: /*
932: * End of line. Update the line number.
933: */
934: ps->input->line++;
935: return ('\n');
936: }
937:
938: if (ch == ';') {
939: /*
940: * A semicolon is itself.
941: */
942: return (';');
943: }
944:
945: if (ch == '#') {
946: /*
947: * #{ opens a format; anything else is a comment,
948: * ignore up to the end of the line.
949: */
950: next = yylex_getc();
951: if (next == '{') {
952: yylval.token = yylex_format();
953: if (yylval.token == NULL)
954: return (ERROR);
955: return (FORMAT);
956: }
957: while (next != '\n' && next != EOF)
958: next = yylex_getc();
959: if (next == '\n') {
960: ps->input->line++;
961: return ('\n');
962: }
963: continue;
964: }
965:
966: if (ch == '%') {
967: /*
968: * % is a condition unless it is alone, then it is a
969: * token.
970: */
971: yylval.token = yylex_get_word('%');
972: if (strcmp(yylval.token, "%") == 0)
973: return (TOKEN);
974: if (strcmp(yylval.token, "%if") == 0) {
975: free(yylval.token);
976: return (IF);
977: }
978: if (strcmp(yylval.token, "%else") == 0) {
979: free(yylval.token);
980: return (ELSE);
981: }
982: if (strcmp(yylval.token, "%elif") == 0) {
983: free(yylval.token);
984: return (ELIF);
985: }
986: if (strcmp(yylval.token, "%endif") == 0) {
987: free(yylval.token);
988: return (ENDIF);
989: }
990: free(yylval.token);
991: return (ERROR);
992: }
993:
994: /*
995: * Otherwise this is a token.
996: */
997: token = yylex_token(ch);
998: if (token == NULL)
999: return (ERROR);
1000: yylval.token = token;
1001:
1002: if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) {
1003: for (cp = token + 1; *cp != '='; cp++) {
1004: if (!yylex_is_var(*cp, 0))
1005: break;
1006: }
1007: if (*cp == '=')
1008: return (EQUALS);
1009: }
1010: return (TOKEN);
1011: }
1012: return (0);
1013: }
1014:
1015: static char *
1016: yylex_format(void)
1017: {
1018: char *buf;
1019: size_t len;
1020: int ch, brackets = 1;
1021:
1022: len = 0;
1023: buf = xmalloc(1);
1024:
1025: yylex_append(&buf, &len, "#{", 2);
1026: for (;;) {
1027: if ((ch = yylex_getc()) == EOF || ch == '\n')
1028: goto error;
1029: if (ch == '#') {
1030: if ((ch = yylex_getc()) == EOF || ch == '\n')
1031: goto error;
1032: if (ch == '{')
1033: brackets++;
1034: yylex_append1(&buf, &len, '#');
1035: } else if (ch == '}') {
1036: if (brackets != 0 && --brackets == 0) {
1037: yylex_append1(&buf, &len, ch);
1038: break;
1039: }
1040: }
1041: yylex_append1(&buf, &len, ch);
1042: }
1043: if (brackets != 0)
1044: goto error;
1045:
1046: buf[len] = '\0';
1047: log_debug("%s: %s", __func__, buf);
1048: return (buf);
1049:
1050: error:
1051: free(buf);
1052: return (NULL);
1053: }
1054:
1055: static int
1056: yylex_token_escape(char **buf, size_t *len)
1057: {
1058: int ch, type;
1059: u_int size, i, tmp;
1060: char s[9];
1061: struct utf8_data ud;
1062:
1063: switch (ch = yylex_getc()) {
1064: case EOF:
1065: return (0);
1066: case 'e':
1067: ch = '\033';
1068: break;
1069: case 'r':
1070: ch = '\r';
1071: break;
1072: case 'n':
1073: ch = '\n';
1074: break;
1075: case 't':
1076: ch = '\t';
1077: break;
1078: case 'u':
1079: type = 'u';
1080: size = 4;
1081: goto unicode;
1082: case 'U':
1083: type = 'U';
1084: size = 8;
1085: goto unicode;
1086: }
1087:
1088: yylex_append1(buf, len, ch);
1089: return (1);
1090:
1091: unicode:
1092: for (i = 0; i < size; i++) {
1093: ch = yylex_getc();
1094: if (ch == EOF || ch == '\n')
1095: return (0);
1096: if (!isxdigit((u_char)ch)) {
1097: yyerror("invalid \\%c argument", type);
1098: return (0);
1099: }
1100: s[i] = ch;
1101: }
1102: s[i] = '\0';
1103:
1104: if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) ||
1105: (size == 8 && sscanf(s, "%8x", &tmp) != 1)) {
1106: yyerror("invalid \\%c argument", type);
1107: return (0);
1108: }
1109: if (utf8_split(tmp, &ud) != UTF8_DONE) {
1110: yyerror("invalid \\%c argument", type);
1111: return (0);
1112: }
1113: yylex_append(buf, len, ud.data, ud.size);
1114: return (1);
1115: }
1116:
1117: static int
1118: yylex_token_variable(char **buf, size_t *len)
1119: {
1120: struct cmd_parse_state *ps = &parse_state;
1121: struct environ_entry *envent;
1122: int ch, brackets = 0;
1123: char name[BUFSIZ];
1124: size_t namelen = 0;
1125: const char *value;
1126:
1127: ch = yylex_getc();
1128: if (ch == EOF)
1129: return (0);
1130: if (ch == '{')
1131: brackets = 1;
1132: else {
1133: if (!yylex_is_var(ch, 1)) {
1134: yylex_append1(buf, len, '$');
1135: ungetc(ch, ps->f);
1136: return (1);
1137: }
1138: name[namelen++] = ch;
1139: }
1140:
1141: for (;;) {
1142: ch = yylex_getc();
1143: if (brackets && ch == '}')
1144: break;
1145: if (ch == EOF || !yylex_is_var(ch, 0)) {
1146: if (!brackets) {
1147: ungetc(ch, ps->f);
1148: break;
1149: }
1150: yyerror("invalid environment variable");
1151: return (0);
1152: }
1153: if (namelen == (sizeof name) - 2) {
1154: yyerror("environment variable is too long");
1155: return (0);
1156: }
1157: name[namelen++] = ch;
1158: }
1159: name[namelen] = '\0';
1160:
1161: envent = environ_find(global_environ, name);
1162: if (envent != NULL) {
1163: value = envent->value;
1164: log_debug("%s: %s -> %s", __func__, name, value);
1165: yylex_append(buf, len, value, strlen(value));
1166: }
1167: return (1);
1168: }
1169:
1170: static int
1171: yylex_token_tilde(char **buf, size_t *len)
1172: {
1173: struct cmd_parse_state *ps = &parse_state;
1174: struct environ_entry *envent;
1175: int ch;
1176: char name[BUFSIZ];
1177: size_t namelen = 0;
1178: struct passwd *pw;
1179: const char *home = NULL;
1180:
1181: for (;;) {
1182: ch = yylex_getc();
1183: if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) {
1184: ungetc(ch, ps->f);
1185: break;
1186: }
1187: if (namelen == (sizeof name) - 2) {
1188: yyerror("user name is too long");
1189: return (0);
1190: }
1191: name[namelen++] = ch;
1192: }
1193: name[namelen] = '\0';
1194:
1195: if (*name == '\0') {
1196: envent = environ_find(global_environ, "HOME");
1197: if (envent != NULL && *envent->value != '\0')
1198: home = envent->value;
1199: else if ((pw = getpwuid(getuid())) != NULL)
1200: home = pw->pw_dir;
1201: } else {
1202: if ((pw = getpwnam(name)) != NULL)
1203: home = pw->pw_dir;
1204: }
1205: if (home == NULL)
1206: return (0);
1207:
1208: log_debug("%s: ~%s -> %s", __func__, name, home);
1209: yylex_append(buf, len, home, strlen(home));
1210: return (1);
1211: }
1212:
1213: static char *
1214: yylex_token(int ch)
1215: {
1216: struct cmd_parse_state *ps = &parse_state;
1217: char *buf;
1218: size_t len;
1219: enum { START,
1220: NONE,
1221: DOUBLE_QUOTES,
1222: SINGLE_QUOTES } state = NONE, last = START;
1223:
1224: len = 0;
1225: buf = xmalloc(1);
1226:
1227: for (;;) {
1228: /*
1229: * EOF or \n are always the end of the token. If inside quotes
1230: * they are an error.
1231: */
1232: if (ch == EOF || ch == '\n') {
1233: if (state != NONE)
1234: goto error;
1235: break;
1236: }
1237:
1238: /* Whitespace or ; ends a token unless inside quotes. */
1239: if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE)
1240: break;
1241:
1242: /*
1243: * \ ~ and $ are expanded except in single quotes.
1244: */
1245: if (ch == '\\' && state != SINGLE_QUOTES) {
1246: if (!yylex_token_escape(&buf, &len))
1247: goto error;
1248: goto skip;
1249: }
1250: if (ch == '~' && last != state && state != SINGLE_QUOTES) {
1251: if (!yylex_token_tilde(&buf, &len))
1252: goto error;
1253: goto skip;
1254: }
1255: if (ch == '$' && state != SINGLE_QUOTES) {
1256: if (!yylex_token_variable(&buf, &len))
1257: goto error;
1258: goto skip;
1259: }
1260:
1261: /*
1262: * ' and " starts or end quotes (and is consumed).
1263: */
1264: if (ch == '\'') {
1265: if (state == NONE) {
1266: state = SINGLE_QUOTES;
1267: goto next;
1268: }
1269: if (state == SINGLE_QUOTES) {
1270: state = NONE;
1271: goto next;
1272: }
1273: }
1274: if (ch == '"') {
1275: if (state == NONE) {
1276: state = DOUBLE_QUOTES;
1277: goto next;
1278: }
1279: if (state == DOUBLE_QUOTES) {
1280: state = NONE;
1281: goto next;
1282: }
1283: }
1284:
1285: /*
1286: * Otherwise add the character to the buffer.
1287: */
1288: yylex_append1(&buf, &len, ch);
1289:
1290: skip:
1291: last = state;
1292:
1293: next:
1294: ch = yylex_getc();
1295: }
1296: ungetc(ch, ps->f);
1297:
1298: buf[len] = '\0';
1299: log_debug("%s: %s", __func__, buf);
1300: return (buf);
1301:
1302: error:
1303: free(buf);
1304: return (NULL);
1305: }