Annotation of src/usr.bin/tmux/cmd-parse.y, Revision 1.21
1.21 ! nicm 1: /* $OpenBSD: cmd-parse.y,v 1.20 2019/10/14 08:38:07 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;
1.18 nicm 179: struct cmd_find_state *fsp;
180: struct cmd_find_state fs;
1.1 nicm 181: int flags = FORMAT_NOJOBS;
182:
183: if (cmd_find_valid_state(&pi->fs))
1.18 nicm 184: fsp = &pi->fs;
185: else {
186: cmd_find_from_client(&fs, c, 0);
187: fsp = &fs;
188: }
1.1 nicm 189: ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
1.18 nicm 190: format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp);
1.1 nicm 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);
1.16 nicm 520: if (pi->file != NULL)
521: cmdq_print(pi->item, "%s:%u: %s", pi->file, line, s);
522: else
523: cmdq_print(pi->item, "%u: %s", line, s);
1.14 nicm 524: free(s);
525: }
526: }
527:
528: static void
1.1 nicm 529: cmd_parse_free_command(struct cmd_parse_command *cmd)
530: {
531: free(cmd->name);
532: cmd_free_argv(cmd->argc, cmd->argv);
533: free(cmd);
534: }
535:
1.13 nicm 536: static struct cmd_parse_commands *
537: cmd_parse_new_commands(void)
538: {
539: struct cmd_parse_commands *cmds;
540:
541: cmds = xmalloc(sizeof *cmds);
542: TAILQ_INIT (cmds);
543: return (cmds);
544: }
545:
1.1 nicm 546: static void
547: cmd_parse_free_commands(struct cmd_parse_commands *cmds)
548: {
549: struct cmd_parse_command *cmd, *cmd1;
550:
551: TAILQ_FOREACH_SAFE(cmd, cmds, entry, cmd1) {
552: TAILQ_REMOVE(cmds, cmd, entry);
553: cmd_parse_free_command(cmd);
554: }
1.13 nicm 555: free(cmds);
1.1 nicm 556: }
557:
558: static struct cmd_parse_commands *
1.5 nicm 559: cmd_parse_run_parser(char **cause)
1.1 nicm 560: {
1.13 nicm 561: struct cmd_parse_state *ps = &parse_state;
562: struct cmd_parse_scope *scope, *scope1;
563: int retval;
1.1 nicm 564:
1.13 nicm 565: ps->commands = NULL;
1.1 nicm 566: TAILQ_INIT(&ps->stack);
567:
568: retval = yyparse();
569: TAILQ_FOREACH_SAFE(scope, &ps->stack, entry, scope1) {
570: TAILQ_REMOVE(&ps->stack, scope, entry);
571: free(scope);
572: }
573: if (retval != 0) {
574: *cause = ps->error;
575: return (NULL);
576: }
577:
1.13 nicm 578: if (ps->commands == NULL)
579: return (cmd_parse_new_commands());
580: return (ps->commands);
1.1 nicm 581: }
582:
1.5 nicm 583: static struct cmd_parse_commands *
584: cmd_parse_do_file(FILE *f, struct cmd_parse_input *pi, char **cause)
585: {
586: struct cmd_parse_state *ps = &parse_state;
587:
588: memset(ps, 0, sizeof *ps);
589: ps->input = pi;
590: ps->f = f;
591: return (cmd_parse_run_parser(cause));
592: }
593:
594: static struct cmd_parse_commands *
595: cmd_parse_do_buffer(const char *buf, size_t len, struct cmd_parse_input *pi,
596: char **cause)
597: {
598: struct cmd_parse_state *ps = &parse_state;
599:
600: memset(ps, 0, sizeof *ps);
601: ps->input = pi;
602: ps->buf = buf;
603: ps->len = len;
604: return (cmd_parse_run_parser(cause));
605: }
606:
1.4 nicm 607: static struct cmd_parse_result *
608: cmd_parse_build_commands(struct cmd_parse_commands *cmds,
609: struct cmd_parse_input *pi)
1.1 nicm 610: {
611: static struct cmd_parse_result pr;
1.4 nicm 612: struct cmd_parse_commands *cmds2;
1.1 nicm 613: struct cmd_parse_command *cmd, *cmd2, *next, *next2, *after;
614: u_int line = UINT_MAX;
615: int i;
616: struct cmd_list *cmdlist = NULL, *result;
617: struct cmd *add;
618: char *alias, *cause, *s;
619:
1.4 nicm 620: /* Check for an empty list. */
1.1 nicm 621: if (TAILQ_EMPTY(cmds)) {
1.13 nicm 622: cmd_parse_free_commands(cmds);
1.1 nicm 623: pr.status = CMD_PARSE_EMPTY;
624: return (&pr);
625: }
626:
627: /*
628: * Walk the commands and expand any aliases. Each alias is parsed
629: * individually to a new command list, any trailing arguments appended
630: * to the last command, and all commands inserted into the original
631: * command list.
632: */
633: TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) {
634: alias = cmd_get_alias(cmd->name);
635: if (alias == NULL)
636: continue;
637:
638: line = cmd->line;
639: log_debug("%s: %u %s = %s", __func__, line, cmd->name, alias);
640:
641: pi->line = line;
1.5 nicm 642: cmds2 = cmd_parse_do_buffer(alias, strlen(alias), pi, &cause);
1.1 nicm 643: free(alias);
644: if (cmds2 == NULL) {
645: pr.status = CMD_PARSE_ERROR;
646: pr.error = cause;
647: goto out;
648: }
649:
650: cmd2 = TAILQ_LAST(cmds2, cmd_parse_commands);
651: if (cmd2 == NULL) {
652: TAILQ_REMOVE(cmds, cmd, entry);
653: cmd_parse_free_command(cmd);
654: continue;
655: }
656: for (i = 0; i < cmd->argc; i++)
657: cmd_append_argv(&cmd2->argc, &cmd2->argv, cmd->argv[i]);
658:
659: after = cmd;
660: TAILQ_FOREACH_SAFE(cmd2, cmds2, entry, next2) {
661: cmd2->line = line;
662: TAILQ_REMOVE(cmds2, cmd2, entry);
663: TAILQ_INSERT_AFTER(cmds, after, cmd2, entry);
664: after = cmd2;
665: }
666: cmd_parse_free_commands(cmds2);
667:
668: TAILQ_REMOVE(cmds, cmd, entry);
669: cmd_parse_free_command(cmd);
670: }
671:
672: /*
673: * Parse each command into a command list. Create a new command list
674: * for each line so they get a new group (so the queue knows which ones
675: * to remove if a command fails when executed).
676: */
677: result = cmd_list_new();
678: TAILQ_FOREACH(cmd, cmds, entry) {
679: log_debug("%s: %u %s", __func__, cmd->line, cmd->name);
680: cmd_log_argv(cmd->argc, cmd->argv, __func__);
681:
682: if (cmdlist == NULL || cmd->line != line) {
683: if (cmdlist != NULL) {
1.14 nicm 684: cmd_parse_print_commands(pi, line, cmdlist);
1.1 nicm 685: cmd_list_move(result, cmdlist);
686: cmd_list_free(cmdlist);
687: }
688: cmdlist = cmd_list_new();
689: }
690: line = cmd->line;
691:
692: cmd_prepend_argv(&cmd->argc, &cmd->argv, cmd->name);
693: add = cmd_parse(cmd->argc, cmd->argv, pi->file, line, &cause);
694: if (add == NULL) {
695: cmd_list_free(result);
696: pr.status = CMD_PARSE_ERROR;
697: pr.error = cmd_parse_get_error(pi->file, line, cause);
698: free(cause);
1.20 nicm 699: cmd_list_free(cmdlist);
1.1 nicm 700: goto out;
701: }
702: cmd_list_append(cmdlist, add);
703: }
704: if (cmdlist != NULL) {
1.14 nicm 705: cmd_parse_print_commands(pi, line, cmdlist);
1.1 nicm 706: cmd_list_move(result, cmdlist);
707: cmd_list_free(cmdlist);
708: }
709:
1.2 nicm 710: s = cmd_list_print(result, 0);
1.1 nicm 711: log_debug("%s: %s", __func__, s);
712: free(s);
713:
714: pr.status = CMD_PARSE_SUCCESS;
715: pr.cmdlist = result;
716:
717: out:
718: cmd_parse_free_commands(cmds);
719:
720: return (&pr);
721: }
722:
723: struct cmd_parse_result *
1.4 nicm 724: cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi)
725: {
726: static struct cmd_parse_result pr;
727: struct cmd_parse_input input;
728: struct cmd_parse_commands *cmds;
729: char *cause;
730:
731: if (pi == NULL) {
732: memset(&input, 0, sizeof input);
733: pi = &input;
734: }
735: memset(&pr, 0, sizeof pr);
736:
1.5 nicm 737: cmds = cmd_parse_do_file(f, pi, &cause);
1.4 nicm 738: if (cmds == NULL) {
739: pr.status = CMD_PARSE_ERROR;
740: pr.error = cause;
741: return (&pr);
742: }
743: return (cmd_parse_build_commands(cmds, pi));
744: }
745:
746: struct cmd_parse_result *
1.1 nicm 747: cmd_parse_from_string(const char *s, struct cmd_parse_input *pi)
748: {
1.21 ! nicm 749: return (cmd_parse_from_buffer(s, strlen(s), pi));
! 750: }
! 751:
! 752: struct cmd_parse_result *
! 753: cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi)
! 754: {
1.1 nicm 755: static struct cmd_parse_result pr;
1.4 nicm 756: struct cmd_parse_input input;
1.5 nicm 757: struct cmd_parse_commands *cmds;
758: char *cause;
1.1 nicm 759:
1.4 nicm 760: if (pi == NULL) {
761: memset(&input, 0, sizeof input);
762: pi = &input;
763: }
764: memset(&pr, 0, sizeof pr);
765:
1.21 ! nicm 766: if (len == 0) {
1.1 nicm 767: pr.status = CMD_PARSE_EMPTY;
768: pr.cmdlist = NULL;
769: pr.error = NULL;
770: return (&pr);
771: }
772:
1.21 ! nicm 773: cmds = cmd_parse_do_buffer(buf, len, pi, &cause);
1.5 nicm 774: if (cmds == NULL) {
1.1 nicm 775: pr.status = CMD_PARSE_ERROR;
1.5 nicm 776: pr.error = cause;
777: return (&pr);
1.1 nicm 778: }
1.5 nicm 779: return (cmd_parse_build_commands(cmds, pi));
1.4 nicm 780: }
781:
782: struct cmd_parse_result *
783: cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi)
784: {
785: struct cmd_parse_input input;
786: struct cmd_parse_commands *cmds;
787: struct cmd_parse_command *cmd;
788: char **copy, **new_argv;
789: size_t size;
790: int i, last, new_argc;
791:
792: /*
793: * The commands are already split up into arguments, so just separate
794: * into a set of commands by ';'.
795: */
796:
797: if (pi == NULL) {
798: memset(&input, 0, sizeof input);
799: pi = &input;
800: }
801: cmd_log_argv(argc, argv, "%s", __func__);
802:
1.13 nicm 803: cmds = cmd_parse_new_commands();
1.4 nicm 804: copy = cmd_copy_argv(argc, argv);
805:
806: last = 0;
807: for (i = 0; i < argc; i++) {
808: size = strlen(copy[i]);
809: if (size == 0 || copy[i][size - 1] != ';')
810: continue;
811: copy[i][--size] = '\0';
812: if (size > 0 && copy[i][size - 1] == '\\') {
813: copy[i][size - 1] = ';';
814: continue;
815: }
816:
817: new_argc = i - last;
818: new_argv = copy + last;
819: if (size != 0)
820: new_argc++;
821:
822: if (new_argc != 0) {
823: cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__,
824: i);
825:
826: cmd = xcalloc(1, sizeof *cmd);
827: cmd->name = xstrdup(new_argv[0]);
828: cmd->line = pi->line;
829:
830: cmd->argc = new_argc - 1;
831: cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
832:
833: TAILQ_INSERT_TAIL(cmds, cmd, entry);
834: }
835:
836: last = i + 1;
837: }
838: if (last != argc) {
839: new_argv = copy + last;
840: new_argc = argc - last;
841:
842: if (new_argc != 0) {
843: cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__,
844: last);
845:
846: cmd = xcalloc(1, sizeof *cmd);
847: cmd->name = xstrdup(new_argv[0]);
848: cmd->line = pi->line;
849:
850: cmd->argc = new_argc - 1;
851: cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
852:
853: TAILQ_INSERT_TAIL(cmds, cmd, entry);
854: }
855: }
1.13 nicm 856:
857: cmd_free_argv(argc, copy);
1.4 nicm 858: return (cmd_parse_build_commands(cmds, pi));
1.1 nicm 859: }
860:
861: static int printflike(1, 2)
862: yyerror(const char *fmt, ...)
863: {
864: struct cmd_parse_state *ps = &parse_state;
865: struct cmd_parse_input *pi = ps->input;
866: va_list ap;
867: char *error;
868:
869: if (ps->error != NULL)
870: return (0);
871:
872: va_start(ap, fmt);
873: xvasprintf(&error, fmt, ap);
874: va_end(ap);
875:
876: ps->error = cmd_parse_get_error(pi->file, pi->line, error);
877: free(error);
878: return (0);
879: }
880:
881: static int
882: yylex_is_var(char ch, int first)
883: {
884: if (ch == '=')
885: return (0);
886: if (first && isdigit((u_char)ch))
887: return (0);
888: return (isalnum((u_char)ch) || ch == '_');
889: }
890:
891: static void
892: yylex_append(char **buf, size_t *len, const char *add, size_t addlen)
893: {
894: if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen)
895: fatalx("buffer is too big");
896: *buf = xrealloc(*buf, (*len) + 1 + addlen);
897: memcpy((*buf) + *len, add, addlen);
898: (*len) += addlen;
899: }
900:
901: static void
902: yylex_append1(char **buf, size_t *len, char add)
903: {
904: yylex_append(buf, len, &add, 1);
905: }
906:
907: static int
1.5 nicm 908: yylex_getc1(void)
909: {
910: struct cmd_parse_state *ps = &parse_state;
911: int ch;
912:
913: if (ps->f != NULL)
914: ch = getc(ps->f);
915: else {
916: if (ps->off == ps->len)
917: ch = EOF;
918: else
919: ch = ps->buf[ps->off++];
920: }
921: return (ch);
922: }
923:
924: static void
925: yylex_ungetc(int ch)
926: {
927: struct cmd_parse_state *ps = &parse_state;
928:
929: if (ps->f != NULL)
930: ungetc(ch, ps->f);
931: else if (ps->off > 0 && ch != EOF)
932: ps->off--;
933: }
934:
935: static int
1.1 nicm 936: yylex_getc(void)
937: {
938: struct cmd_parse_state *ps = &parse_state;
939: int ch;
940:
941: if (ps->escapes != 0) {
942: ps->escapes--;
943: return ('\\');
944: }
945: for (;;) {
1.5 nicm 946: ch = yylex_getc1();
1.1 nicm 947: if (ch == '\\') {
948: ps->escapes++;
949: continue;
950: }
951: if (ch == '\n' && (ps->escapes % 2) == 1) {
952: ps->input->line++;
953: ps->escapes--;
954: continue;
955: }
956:
957: if (ps->escapes != 0) {
1.5 nicm 958: yylex_ungetc(ch);
1.1 nicm 959: ps->escapes--;
960: return ('\\');
961: }
962: return (ch);
963: }
964: }
965:
966: static char *
967: yylex_get_word(int ch)
968: {
1.5 nicm 969: char *buf;
970: size_t len;
1.1 nicm 971:
972: len = 0;
973: buf = xmalloc(1);
974:
975: do
976: yylex_append1(&buf, &len, ch);
977: while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL);
1.5 nicm 978: yylex_ungetc(ch);
1.1 nicm 979:
980: buf[len] = '\0';
981: log_debug("%s: %s", __func__, buf);
982: return (buf);
983: }
984:
985: static int
986: yylex(void)
987: {
988: struct cmd_parse_state *ps = &parse_state;
989: char *token, *cp;
1.15 nicm 990: int ch, next, condition;
1.1 nicm 991:
1.8 nicm 992: if (ps->eol)
993: ps->input->line++;
994: ps->eol = 0;
995:
1.15 nicm 996: condition = ps->condition;
997: ps->condition = 0;
998:
1.1 nicm 999: for (;;) {
1000: ch = yylex_getc();
1001:
1002: if (ch == EOF) {
1003: /*
1004: * Ensure every file or string is terminated by a
1005: * newline. This keeps the parser simpler and avoids
1006: * having to add a newline to each string.
1007: */
1008: if (ps->eof)
1009: break;
1010: ps->eof = 1;
1011: return ('\n');
1012: }
1013:
1014: if (ch == ' ' || ch == '\t') {
1015: /*
1016: * Ignore whitespace.
1017: */
1018: continue;
1019: }
1020:
1021: if (ch == '\n') {
1022: /*
1023: * End of line. Update the line number.
1024: */
1.8 nicm 1025: ps->eol = 1;
1.1 nicm 1026: return ('\n');
1027: }
1028:
1029: if (ch == ';') {
1030: /*
1031: * A semicolon is itself.
1032: */
1033: return (';');
1034: }
1035:
1036: if (ch == '#') {
1037: /*
1.15 nicm 1038: * #{ after a condition opens a format; anything else
1039: * is a comment, ignore up to the end of the line.
1.1 nicm 1040: */
1041: next = yylex_getc();
1.15 nicm 1042: if (condition && next == '{') {
1.1 nicm 1043: yylval.token = yylex_format();
1044: if (yylval.token == NULL)
1045: return (ERROR);
1046: return (FORMAT);
1047: }
1048: while (next != '\n' && next != EOF)
1049: next = yylex_getc();
1050: if (next == '\n') {
1051: ps->input->line++;
1052: return ('\n');
1053: }
1054: continue;
1055: }
1056:
1057: if (ch == '%') {
1058: /*
1.11 nicm 1059: * % is a condition unless it is all % or all numbers,
1060: * then it is a token.
1.1 nicm 1061: */
1062: yylval.token = yylex_get_word('%');
1.11 nicm 1063: for (cp = yylval.token; *cp != '\0'; cp++) {
1064: if (*cp != '%' && !isdigit((u_char)*cp))
1065: break;
1066: }
1067: if (*cp == '\0')
1.1 nicm 1068: return (TOKEN);
1.15 nicm 1069: ps->condition = 1;
1.1 nicm 1070: if (strcmp(yylval.token, "%if") == 0) {
1071: free(yylval.token);
1072: return (IF);
1073: }
1074: if (strcmp(yylval.token, "%else") == 0) {
1075: free(yylval.token);
1076: return (ELSE);
1077: }
1078: if (strcmp(yylval.token, "%elif") == 0) {
1079: free(yylval.token);
1080: return (ELIF);
1081: }
1082: if (strcmp(yylval.token, "%endif") == 0) {
1083: free(yylval.token);
1084: return (ENDIF);
1085: }
1086: free(yylval.token);
1087: return (ERROR);
1088: }
1089:
1090: /*
1091: * Otherwise this is a token.
1092: */
1093: token = yylex_token(ch);
1094: if (token == NULL)
1095: return (ERROR);
1096: yylval.token = token;
1097:
1098: if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) {
1099: for (cp = token + 1; *cp != '='; cp++) {
1100: if (!yylex_is_var(*cp, 0))
1101: break;
1102: }
1103: if (*cp == '=')
1104: return (EQUALS);
1105: }
1106: return (TOKEN);
1107: }
1108: return (0);
1109: }
1110:
1111: static char *
1112: yylex_format(void)
1113: {
1114: char *buf;
1115: size_t len;
1116: int ch, brackets = 1;
1117:
1118: len = 0;
1119: buf = xmalloc(1);
1120:
1121: yylex_append(&buf, &len, "#{", 2);
1122: for (;;) {
1123: if ((ch = yylex_getc()) == EOF || ch == '\n')
1124: goto error;
1125: if (ch == '#') {
1126: if ((ch = yylex_getc()) == EOF || ch == '\n')
1127: goto error;
1128: if (ch == '{')
1129: brackets++;
1130: yylex_append1(&buf, &len, '#');
1131: } else if (ch == '}') {
1132: if (brackets != 0 && --brackets == 0) {
1133: yylex_append1(&buf, &len, ch);
1134: break;
1135: }
1136: }
1137: yylex_append1(&buf, &len, ch);
1138: }
1139: if (brackets != 0)
1140: goto error;
1141:
1142: buf[len] = '\0';
1143: log_debug("%s: %s", __func__, buf);
1144: return (buf);
1145:
1146: error:
1147: free(buf);
1148: return (NULL);
1149: }
1150:
1151: static int
1152: yylex_token_escape(char **buf, size_t *len)
1153: {
1.7 nicm 1154: int ch, type, o2, o3;
1.1 nicm 1155: u_int size, i, tmp;
1156: char s[9];
1157: struct utf8_data ud;
1158:
1.7 nicm 1159: ch = yylex_getc();
1160:
1161: if (ch >= '4' && ch <= '7') {
1162: yyerror("invalid octal escape");
1163: return (0);
1164: }
1165: if (ch >= '0' && ch <= '3') {
1166: o2 = yylex_getc();
1167: if (o2 >= '0' && o2 <= '7') {
1168: o3 = yylex_getc();
1169: if (o3 >= '0' && o3 <= '7') {
1170: ch = 64 * (ch - '0') +
1171: 8 * (o2 - '0') +
1172: (o3 - '0');
1173: yylex_append1(buf, len, ch);
1174: return (1);
1175: }
1176: }
1177: yyerror("invalid octal escape");
1178: return (0);
1179: }
1180:
1181: switch (ch) {
1.1 nicm 1182: case EOF:
1183: return (0);
1.9 nicm 1184: case 'a':
1185: ch = '\a';
1186: break;
1187: case 'b':
1188: ch = '\b';
1189: break;
1.1 nicm 1190: case 'e':
1191: ch = '\033';
1.9 nicm 1192: break;
1193: case 'f':
1194: ch = '\f';
1195: break;
1196: case 's':
1197: ch = ' ';
1198: break;
1199: case 'v':
1200: ch = '\v';
1.1 nicm 1201: break;
1202: case 'r':
1203: ch = '\r';
1204: break;
1205: case 'n':
1206: ch = '\n';
1207: break;
1208: case 't':
1209: ch = '\t';
1210: break;
1211: case 'u':
1212: type = 'u';
1213: size = 4;
1214: goto unicode;
1215: case 'U':
1216: type = 'U';
1217: size = 8;
1218: goto unicode;
1219: }
1220:
1221: yylex_append1(buf, len, ch);
1222: return (1);
1223:
1224: unicode:
1225: for (i = 0; i < size; i++) {
1226: ch = yylex_getc();
1227: if (ch == EOF || ch == '\n')
1228: return (0);
1229: if (!isxdigit((u_char)ch)) {
1230: yyerror("invalid \\%c argument", type);
1231: return (0);
1232: }
1233: s[i] = ch;
1234: }
1235: s[i] = '\0';
1236:
1237: if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) ||
1238: (size == 8 && sscanf(s, "%8x", &tmp) != 1)) {
1239: yyerror("invalid \\%c argument", type);
1240: return (0);
1241: }
1242: if (utf8_split(tmp, &ud) != UTF8_DONE) {
1243: yyerror("invalid \\%c argument", type);
1244: return (0);
1245: }
1246: yylex_append(buf, len, ud.data, ud.size);
1247: return (1);
1248: }
1249:
1250: static int
1251: yylex_token_variable(char **buf, size_t *len)
1252: {
1253: struct environ_entry *envent;
1254: int ch, brackets = 0;
1.19 nicm 1255: char name[1024];
1.1 nicm 1256: size_t namelen = 0;
1257: const char *value;
1258:
1259: ch = yylex_getc();
1260: if (ch == EOF)
1261: return (0);
1262: if (ch == '{')
1263: brackets = 1;
1264: else {
1265: if (!yylex_is_var(ch, 1)) {
1266: yylex_append1(buf, len, '$');
1.5 nicm 1267: yylex_ungetc(ch);
1.1 nicm 1268: return (1);
1269: }
1270: name[namelen++] = ch;
1271: }
1272:
1273: for (;;) {
1274: ch = yylex_getc();
1275: if (brackets && ch == '}')
1276: break;
1277: if (ch == EOF || !yylex_is_var(ch, 0)) {
1278: if (!brackets) {
1.5 nicm 1279: yylex_ungetc(ch);
1.1 nicm 1280: break;
1281: }
1282: yyerror("invalid environment variable");
1283: return (0);
1284: }
1285: if (namelen == (sizeof name) - 2) {
1286: yyerror("environment variable is too long");
1287: return (0);
1288: }
1289: name[namelen++] = ch;
1290: }
1291: name[namelen] = '\0';
1292:
1293: envent = environ_find(global_environ, name);
1294: if (envent != NULL) {
1295: value = envent->value;
1296: log_debug("%s: %s -> %s", __func__, name, value);
1297: yylex_append(buf, len, value, strlen(value));
1298: }
1299: return (1);
1300: }
1301:
1302: static int
1303: yylex_token_tilde(char **buf, size_t *len)
1304: {
1305: struct environ_entry *envent;
1306: int ch;
1.19 nicm 1307: char name[1024];
1.1 nicm 1308: size_t namelen = 0;
1309: struct passwd *pw;
1310: const char *home = NULL;
1311:
1312: for (;;) {
1313: ch = yylex_getc();
1314: if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) {
1.5 nicm 1315: yylex_ungetc(ch);
1.1 nicm 1316: break;
1317: }
1318: if (namelen == (sizeof name) - 2) {
1319: yyerror("user name is too long");
1320: return (0);
1321: }
1322: name[namelen++] = ch;
1323: }
1324: name[namelen] = '\0';
1325:
1326: if (*name == '\0') {
1327: envent = environ_find(global_environ, "HOME");
1328: if (envent != NULL && *envent->value != '\0')
1329: home = envent->value;
1330: else if ((pw = getpwuid(getuid())) != NULL)
1331: home = pw->pw_dir;
1332: } else {
1333: if ((pw = getpwnam(name)) != NULL)
1334: home = pw->pw_dir;
1335: }
1336: if (home == NULL)
1337: return (0);
1338:
1339: log_debug("%s: ~%s -> %s", __func__, name, home);
1340: yylex_append(buf, len, home, strlen(home));
1341: return (1);
1342: }
1343:
1.6 nicm 1344: static int
1345: yylex_token_brace(char **buf, size_t *len)
1346: {
1347: struct cmd_parse_state *ps = &parse_state;
1.17 nicm 1348: int ch, lines = 0, nesting = 1, escape = 0;
1349: int quote = '\0', token = 0;
1.6 nicm 1350:
1351: /*
1352: * Extract a string up to the matching unquoted '}', including newlines
1353: * and handling nested braces.
1354: *
1355: * To detect the final and intermediate braces which affect the nesting
1356: * depth, we scan the input as if it was a tmux config file, and ignore
1357: * braces which would be considered quoted, escaped, or in a comment.
1358: *
1.17 nicm 1359: * We update the token state after every character because '#' begins a
1360: * comment only when it begins a token. For simplicity, we treat an
1361: * unquoted directive format as comment.
1362: *
1.6 nicm 1363: * The result is verbatim copy of the input excluding the final brace.
1364: */
1365:
1366: for (ch = yylex_getc1(); ch != EOF; ch = yylex_getc1()) {
1367: yylex_append1(buf, len, ch);
1368: if (ch == '\n')
1369: lines++;
1370:
1371: /*
1372: * If the previous character was a backslash (escape is set),
1373: * escape anything if unquoted or in double quotes, otherwise
1374: * escape only '\n' and '\\'.
1375: */
1376: if (escape &&
1377: (quote == '\0' ||
1378: quote == '"' ||
1379: ch == '\n' ||
1380: ch == '\\')) {
1381: escape = 0;
1.17 nicm 1382: if (ch != '\n')
1383: token = 1;
1.6 nicm 1384: continue;
1385: }
1386:
1387: /*
1388: * The character is not escaped. If it is a backslash, set the
1389: * escape flag.
1390: */
1391: if (ch == '\\') {
1392: escape = 1;
1393: continue;
1394: }
1395: escape = 0;
1396:
1397: /* A newline always resets to unquoted. */
1398: if (ch == '\n') {
1.17 nicm 1399: quote = token = 0;
1.6 nicm 1400: continue;
1401: }
1402:
1403: if (quote) {
1404: /*
1405: * Inside quotes or comment. Check if this is the
1406: * closing quote.
1407: */
1408: if (ch == quote && quote != '#')
1409: quote = 0;
1.17 nicm 1410: token = 1; /* token continues regardless */
1411: } else {
1.6 nicm 1412: /* Not inside quotes or comment. */
1413: switch (ch) {
1414: case '"':
1415: case '\'':
1416: case '#':
1.17 nicm 1417: /* Beginning of quote or maybe comment. */
1418: if (ch != '#' || !token)
1419: quote = ch;
1420: token = 1;
1421: break;
1422: case ' ':
1423: case '\t':
1424: case ';':
1425: /* Delimiter - token resets. */
1426: token = 0;
1.6 nicm 1427: break;
1428: case '{':
1429: nesting++;
1.17 nicm 1430: token = 0; /* new commands set - token resets */
1.6 nicm 1431: break;
1432: case '}':
1433: nesting--;
1.17 nicm 1434: token = 1; /* same as after quotes */
1.6 nicm 1435: if (nesting == 0) {
1436: (*len)--; /* remove closing } */
1437: ps->input->line += lines;
1438: return (1);
1439: }
1440: break;
1.17 nicm 1441: default:
1442: token = 1;
1443: break;
1.6 nicm 1444: }
1445: }
1446: }
1447:
1448: /*
1.17 nicm 1449: * Update line count after error as reporting the opening line is more
1450: * useful than EOF.
1.6 nicm 1451: */
1452: yyerror("unterminated brace string");
1453: ps->input->line += lines;
1454: return (0);
1455: }
1456:
1.1 nicm 1457: static char *
1458: yylex_token(int ch)
1459: {
1460: char *buf;
1461: size_t len;
1462: enum { START,
1463: NONE,
1464: DOUBLE_QUOTES,
1465: SINGLE_QUOTES } state = NONE, last = START;
1466:
1467: len = 0;
1468: buf = xmalloc(1);
1469:
1470: for (;;) {
1471: /*
1472: * EOF or \n are always the end of the token. If inside quotes
1473: * they are an error.
1474: */
1475: if (ch == EOF || ch == '\n') {
1476: if (state != NONE)
1477: goto error;
1478: break;
1479: }
1480:
1481: /* Whitespace or ; ends a token unless inside quotes. */
1482: if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE)
1483: break;
1484:
1485: /*
1486: * \ ~ and $ are expanded except in single quotes.
1487: */
1488: if (ch == '\\' && state != SINGLE_QUOTES) {
1489: if (!yylex_token_escape(&buf, &len))
1490: goto error;
1491: goto skip;
1492: }
1493: if (ch == '~' && last != state && state != SINGLE_QUOTES) {
1494: if (!yylex_token_tilde(&buf, &len))
1495: goto error;
1496: goto skip;
1497: }
1498: if (ch == '$' && state != SINGLE_QUOTES) {
1499: if (!yylex_token_variable(&buf, &len))
1500: goto error;
1501: goto skip;
1502: }
1.6 nicm 1503: if (ch == '{' && state == NONE) {
1504: if (!yylex_token_brace(&buf, &len))
1505: goto error;
1506: goto skip;
1507: }
1508: if (ch == '}' && state == NONE)
1509: goto error; /* unmatched (matched ones were handled) */
1.1 nicm 1510:
1511: /*
1512: * ' and " starts or end quotes (and is consumed).
1513: */
1514: if (ch == '\'') {
1515: if (state == NONE) {
1516: state = SINGLE_QUOTES;
1517: goto next;
1518: }
1519: if (state == SINGLE_QUOTES) {
1520: state = NONE;
1521: goto next;
1522: }
1523: }
1524: if (ch == '"') {
1525: if (state == NONE) {
1526: state = DOUBLE_QUOTES;
1527: goto next;
1528: }
1529: if (state == DOUBLE_QUOTES) {
1530: state = NONE;
1531: goto next;
1532: }
1533: }
1534:
1535: /*
1536: * Otherwise add the character to the buffer.
1537: */
1538: yylex_append1(&buf, &len, ch);
1539:
1540: skip:
1541: last = state;
1542:
1543: next:
1544: ch = yylex_getc();
1545: }
1.5 nicm 1546: yylex_ungetc(ch);
1.1 nicm 1547:
1548: buf[len] = '\0';
1549: log_debug("%s: %s", __func__, buf);
1550: return (buf);
1551:
1552: error:
1553: free(buf);
1554: return (NULL);
1555: }