|  | /* $OpenBSD$ */ | 
|  |  | 
|  | /* | 
|  | * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> | 
|  | * | 
|  | * Permission to use, copy, modify, and distribute this software for any | 
|  | * purpose with or without fee is hereby granted, provided that the above | 
|  | * copyright notice and this permission notice appear in all copies. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | 
|  | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | 
|  | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | 
|  | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
|  | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER | 
|  | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING | 
|  | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
|  | */ | 
|  |  | 
|  | %{ | 
|  |  | 
|  | #include <sys/types.h> | 
|  |  | 
|  | #include <ctype.h> | 
|  | #include <errno.h> | 
|  | #include <pwd.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <unistd.h> | 
|  | #include <wchar.h> | 
|  |  | 
|  | #include "tmux.h" | 
|  |  | 
|  | static int			 yylex(void); | 
|  | static int			 yyparse(void); | 
|  | static int printflike(1,2)	 yyerror(const char *, ...); | 
|  |  | 
|  | static char			*yylex_token(int); | 
|  | static char			*yylex_format(void); | 
|  |  | 
|  | struct cmd_parse_scope { | 
|  | int				 flag; | 
|  | TAILQ_ENTRY (cmd_parse_scope)	 entry; | 
|  | }; | 
|  |  | 
|  | enum cmd_parse_argument_type { | 
|  | CMD_PARSE_STRING, | 
|  | CMD_PARSE_COMMANDS, | 
|  | CMD_PARSE_PARSED_COMMANDS | 
|  | }; | 
|  |  | 
|  | struct cmd_parse_argument { | 
|  | enum cmd_parse_argument_type	 type; | 
|  | char				*string; | 
|  | struct cmd_parse_commands	*commands; | 
|  | struct cmd_list			*cmdlist; | 
|  |  | 
|  | TAILQ_ENTRY(cmd_parse_argument)	 entry; | 
|  | }; | 
|  | TAILQ_HEAD(cmd_parse_arguments, cmd_parse_argument); | 
|  |  | 
|  | struct cmd_parse_command { | 
|  | u_int				 line; | 
|  | struct cmd_parse_arguments	 arguments; | 
|  |  | 
|  | TAILQ_ENTRY(cmd_parse_command)	 entry; | 
|  | }; | 
|  | TAILQ_HEAD(cmd_parse_commands, cmd_parse_command); | 
|  |  | 
|  | struct cmd_parse_state { | 
|  | FILE				*f; | 
|  |  | 
|  | const char			*buf; | 
|  | size_t				 len; | 
|  | size_t				 off; | 
|  |  | 
|  | int				 condition; | 
|  | int				 eol; | 
|  | int				 eof; | 
|  | struct cmd_parse_input		*input; | 
|  | u_int				 escapes; | 
|  |  | 
|  | char				*error; | 
|  | struct cmd_parse_commands	*commands; | 
|  |  | 
|  | struct cmd_parse_scope		*scope; | 
|  | TAILQ_HEAD(, cmd_parse_scope)	 stack; | 
|  | }; | 
|  | static struct cmd_parse_state parse_state; | 
|  |  | 
|  | static char	*cmd_parse_get_error(const char *, u_int, const char *); | 
|  | static void	 cmd_parse_free_command(struct cmd_parse_command *); | 
|  | static struct cmd_parse_commands *cmd_parse_new_commands(void); | 
|  | static void	 cmd_parse_free_commands(struct cmd_parse_commands *); | 
|  | static void	 cmd_parse_build_commands(struct cmd_parse_commands *, | 
|  | struct cmd_parse_input *, struct cmd_parse_result *); | 
|  | static void	 cmd_parse_print_commands(struct cmd_parse_input *, | 
|  | struct cmd_list *); | 
|  |  | 
|  | %} | 
|  |  | 
|  | %union | 
|  | { | 
|  | char					 *token; | 
|  | struct cmd_parse_arguments		 *arguments; | 
|  | struct cmd_parse_argument		 *argument; | 
|  | int					  flag; | 
|  | struct { | 
|  | int				  flag; | 
|  | struct cmd_parse_commands	 *commands; | 
|  | } elif; | 
|  | struct cmd_parse_commands		 *commands; | 
|  | struct cmd_parse_command		 *command; | 
|  | } | 
|  |  | 
|  | %token ERROR | 
|  | %token HIDDEN | 
|  | %token IF | 
|  | %token ELSE | 
|  | %token ELIF | 
|  | %token ENDIF | 
|  | %token <token> FORMAT TOKEN EQUALS | 
|  |  | 
|  | %type <token> expanded format | 
|  | %type <arguments> arguments | 
|  | %type <argument> argument | 
|  | %type <flag> if_open if_elif | 
|  | %type <elif> elif elif1 | 
|  | %type <commands> argument_statements statements statement | 
|  | %type <commands> commands condition condition1 | 
|  | %type <command> command | 
|  |  | 
|  | %% | 
|  |  | 
|  | lines		: /* empty */ | 
|  | | statements | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  |  | 
|  | ps->commands = $1; | 
|  | } | 
|  |  | 
|  | statements	: statement '\n' | 
|  | { | 
|  | $$ = $1; | 
|  | } | 
|  | | statements statement '\n' | 
|  | { | 
|  | $$ = $1; | 
|  | TAILQ_CONCAT($$, $2, entry); | 
|  | free($2); | 
|  | } | 
|  |  | 
|  | statement	: /* empty */ | 
|  | { | 
|  | $$ = xmalloc (sizeof *$$); | 
|  | TAILQ_INIT($$); | 
|  | } | 
|  | | hidden_assignment | 
|  | { | 
|  | $$ = xmalloc (sizeof *$$); | 
|  | TAILQ_INIT($$); | 
|  | } | 
|  | | condition | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  |  | 
|  | if (ps->scope == NULL || ps->scope->flag) | 
|  | $$ = $1; | 
|  | else { | 
|  | $$ = cmd_parse_new_commands(); | 
|  | cmd_parse_free_commands($1); | 
|  | } | 
|  | } | 
|  | | commands | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  |  | 
|  | if (ps->scope == NULL || ps->scope->flag) | 
|  | $$ = $1; | 
|  | else { | 
|  | $$ = cmd_parse_new_commands(); | 
|  | cmd_parse_free_commands($1); | 
|  | } | 
|  | } | 
|  |  | 
|  | format		: FORMAT | 
|  | { | 
|  | $$ = $1; | 
|  | } | 
|  | | TOKEN | 
|  | { | 
|  | $$ = $1; | 
|  | } | 
|  |  | 
|  | expanded	: format | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  | struct cmd_parse_input	*pi = ps->input; | 
|  | struct format_tree	*ft; | 
|  | struct client		*c = pi->c; | 
|  | struct cmd_find_state	*fsp; | 
|  | struct cmd_find_state	 fs; | 
|  | int			 flags = FORMAT_NOJOBS; | 
|  |  | 
|  | if (cmd_find_valid_state(&pi->fs)) | 
|  | fsp = &pi->fs; | 
|  | else { | 
|  | cmd_find_from_client(&fs, c, 0); | 
|  | fsp = &fs; | 
|  | } | 
|  | ft = format_create(NULL, pi->item, FORMAT_NONE, flags); | 
|  | format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp); | 
|  |  | 
|  | $$ = format_expand(ft, $1); | 
|  | format_free(ft); | 
|  | free($1); | 
|  | } | 
|  |  | 
|  | optional_assignment	: /* empty */ | 
|  | | assignment | 
|  |  | 
|  | assignment	: EQUALS | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  | int			 flags = ps->input->flags; | 
|  |  | 
|  | if ((~flags & CMD_PARSE_PARSEONLY) && | 
|  | (ps->scope == NULL || ps->scope->flag)) | 
|  | environ_put(global_environ, $1, 0); | 
|  | free($1); | 
|  | } | 
|  |  | 
|  | hidden_assignment : HIDDEN EQUALS | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  | int			 flags = ps->input->flags; | 
|  |  | 
|  | if ((~flags & CMD_PARSE_PARSEONLY) && | 
|  | (ps->scope == NULL || ps->scope->flag)) | 
|  | environ_put(global_environ, $2, ENVIRON_HIDDEN); | 
|  | free($2); | 
|  | } | 
|  |  | 
|  | if_open		: IF expanded | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  | struct cmd_parse_scope	*scope; | 
|  |  | 
|  | scope = xmalloc(sizeof *scope); | 
|  | $$ = scope->flag = format_true($2); | 
|  | free($2); | 
|  |  | 
|  | if (ps->scope != NULL) | 
|  | TAILQ_INSERT_HEAD(&ps->stack, ps->scope, entry); | 
|  | ps->scope = scope; | 
|  | } | 
|  |  | 
|  | if_else		: ELSE | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  | struct cmd_parse_scope	*scope; | 
|  |  | 
|  | scope = xmalloc(sizeof *scope); | 
|  | scope->flag = !ps->scope->flag; | 
|  |  | 
|  | free(ps->scope); | 
|  | ps->scope = scope; | 
|  | } | 
|  |  | 
|  | if_elif		: ELIF expanded | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  | struct cmd_parse_scope	*scope; | 
|  |  | 
|  | scope = xmalloc(sizeof *scope); | 
|  | $$ = scope->flag = format_true($2); | 
|  | free($2); | 
|  |  | 
|  | free(ps->scope); | 
|  | ps->scope = scope; | 
|  | } | 
|  |  | 
|  | if_close	: ENDIF | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  |  | 
|  | free(ps->scope); | 
|  | ps->scope = TAILQ_FIRST(&ps->stack); | 
|  | if (ps->scope != NULL) | 
|  | TAILQ_REMOVE(&ps->stack, ps->scope, entry); | 
|  | } | 
|  |  | 
|  | condition	: if_open '\n' statements if_close | 
|  | { | 
|  | if ($1) | 
|  | $$ = $3; | 
|  | else { | 
|  | $$ = cmd_parse_new_commands(); | 
|  | cmd_parse_free_commands($3); | 
|  | } | 
|  | } | 
|  | | if_open '\n' statements if_else '\n' statements if_close | 
|  | { | 
|  | if ($1) { | 
|  | $$ = $3; | 
|  | cmd_parse_free_commands($6); | 
|  | } else { | 
|  | $$ = $6; | 
|  | cmd_parse_free_commands($3); | 
|  | } | 
|  | } | 
|  | | if_open '\n' statements elif if_close | 
|  | { | 
|  | if ($1) { | 
|  | $$ = $3; | 
|  | cmd_parse_free_commands($4.commands); | 
|  | } else if ($4.flag) { | 
|  | $$ = $4.commands; | 
|  | cmd_parse_free_commands($3); | 
|  | } else { | 
|  | $$ = cmd_parse_new_commands(); | 
|  | cmd_parse_free_commands($3); | 
|  | cmd_parse_free_commands($4.commands); | 
|  | } | 
|  | } | 
|  | | if_open '\n' statements elif if_else '\n' statements if_close | 
|  | { | 
|  | if ($1) { | 
|  | $$ = $3; | 
|  | cmd_parse_free_commands($4.commands); | 
|  | cmd_parse_free_commands($7); | 
|  | } else if ($4.flag) { | 
|  | $$ = $4.commands; | 
|  | cmd_parse_free_commands($3); | 
|  | cmd_parse_free_commands($7); | 
|  | } else { | 
|  | $$ = $7; | 
|  | cmd_parse_free_commands($3); | 
|  | cmd_parse_free_commands($4.commands); | 
|  | } | 
|  | } | 
|  |  | 
|  | elif		: if_elif '\n' statements | 
|  | { | 
|  | if ($1) { | 
|  | $$.flag = 1; | 
|  | $$.commands = $3; | 
|  | } else { | 
|  | $$.flag = 0; | 
|  | $$.commands = cmd_parse_new_commands(); | 
|  | cmd_parse_free_commands($3); | 
|  | } | 
|  | } | 
|  | | if_elif '\n' statements elif | 
|  | { | 
|  | if ($1) { | 
|  | $$.flag = 1; | 
|  | $$.commands = $3; | 
|  | cmd_parse_free_commands($4.commands); | 
|  | } else if ($4.flag) { | 
|  | $$.flag = 1; | 
|  | $$.commands = $4.commands; | 
|  | cmd_parse_free_commands($3); | 
|  | } else { | 
|  | $$.flag = 0; | 
|  | $$.commands = cmd_parse_new_commands(); | 
|  | cmd_parse_free_commands($3); | 
|  | cmd_parse_free_commands($4.commands); | 
|  | } | 
|  | } | 
|  |  | 
|  | commands	: command | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  |  | 
|  | $$ = cmd_parse_new_commands(); | 
|  | if (!TAILQ_EMPTY(&$1->arguments) && | 
|  | (ps->scope == NULL || ps->scope->flag)) | 
|  | TAILQ_INSERT_TAIL($$, $1, entry); | 
|  | else | 
|  | cmd_parse_free_command($1); | 
|  | } | 
|  | | commands ';' | 
|  | { | 
|  | $$ = $1; | 
|  | } | 
|  | | commands ';' condition1 | 
|  | { | 
|  | $$ = $1; | 
|  | TAILQ_CONCAT($$, $3, entry); | 
|  | free($3); | 
|  | } | 
|  | | commands ';' command | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  |  | 
|  | if (!TAILQ_EMPTY(&$3->arguments) && | 
|  | (ps->scope == NULL || ps->scope->flag)) { | 
|  | $$ = $1; | 
|  | TAILQ_INSERT_TAIL($$, $3, entry); | 
|  | } else { | 
|  | $$ = cmd_parse_new_commands(); | 
|  | cmd_parse_free_commands($1); | 
|  | cmd_parse_free_command($3); | 
|  | } | 
|  | } | 
|  | | condition1 | 
|  | { | 
|  | $$ = $1; | 
|  | } | 
|  |  | 
|  | command		: assignment | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  |  | 
|  | $$ = xcalloc(1, sizeof *$$); | 
|  | $$->line = ps->input->line; | 
|  | TAILQ_INIT(&$$->arguments); | 
|  | } | 
|  | | optional_assignment TOKEN | 
|  | { | 
|  | struct cmd_parse_state		*ps = &parse_state; | 
|  | struct cmd_parse_argument	*arg; | 
|  |  | 
|  | $$ = xcalloc(1, sizeof *$$); | 
|  | $$->line = ps->input->line; | 
|  | TAILQ_INIT(&$$->arguments); | 
|  |  | 
|  | arg = xcalloc(1, sizeof *arg); | 
|  | arg->type = CMD_PARSE_STRING; | 
|  | arg->string = $2; | 
|  | TAILQ_INSERT_HEAD(&$$->arguments, arg, entry); | 
|  | } | 
|  | | optional_assignment TOKEN arguments | 
|  | { | 
|  | struct cmd_parse_state		*ps = &parse_state; | 
|  | struct cmd_parse_argument	*arg; | 
|  |  | 
|  | $$ = xcalloc(1, sizeof *$$); | 
|  | $$->line = ps->input->line; | 
|  | TAILQ_INIT(&$$->arguments); | 
|  |  | 
|  | TAILQ_CONCAT(&$$->arguments, $3, entry); | 
|  | free($3); | 
|  |  | 
|  | arg = xcalloc(1, sizeof *arg); | 
|  | arg->type = CMD_PARSE_STRING; | 
|  | arg->string = $2; | 
|  | TAILQ_INSERT_HEAD(&$$->arguments, arg, entry); | 
|  | } | 
|  |  | 
|  | condition1	: if_open commands if_close | 
|  | { | 
|  | if ($1) | 
|  | $$ = $2; | 
|  | else { | 
|  | $$ = cmd_parse_new_commands(); | 
|  | cmd_parse_free_commands($2); | 
|  | } | 
|  | } | 
|  | | if_open commands if_else commands if_close | 
|  | { | 
|  | if ($1) { | 
|  | $$ = $2; | 
|  | cmd_parse_free_commands($4); | 
|  | } else { | 
|  | $$ = $4; | 
|  | cmd_parse_free_commands($2); | 
|  | } | 
|  | } | 
|  | | if_open commands elif1 if_close | 
|  | { | 
|  | if ($1) { | 
|  | $$ = $2; | 
|  | cmd_parse_free_commands($3.commands); | 
|  | } else if ($3.flag) { | 
|  | $$ = $3.commands; | 
|  | cmd_parse_free_commands($2); | 
|  | } else { | 
|  | $$ = cmd_parse_new_commands(); | 
|  | cmd_parse_free_commands($2); | 
|  | cmd_parse_free_commands($3.commands); | 
|  | } | 
|  | } | 
|  | | if_open commands elif1 if_else commands if_close | 
|  | { | 
|  | if ($1) { | 
|  | $$ = $2; | 
|  | cmd_parse_free_commands($3.commands); | 
|  | cmd_parse_free_commands($5); | 
|  | } else if ($3.flag) { | 
|  | $$ = $3.commands; | 
|  | cmd_parse_free_commands($2); | 
|  | cmd_parse_free_commands($5); | 
|  | } else { | 
|  | $$ = $5; | 
|  | cmd_parse_free_commands($2); | 
|  | cmd_parse_free_commands($3.commands); | 
|  | } | 
|  | } | 
|  |  | 
|  | elif1		: if_elif commands | 
|  | { | 
|  | if ($1) { | 
|  | $$.flag = 1; | 
|  | $$.commands = $2; | 
|  | } else { | 
|  | $$.flag = 0; | 
|  | $$.commands = cmd_parse_new_commands(); | 
|  | cmd_parse_free_commands($2); | 
|  | } | 
|  | } | 
|  | | if_elif commands elif1 | 
|  | { | 
|  | if ($1) { | 
|  | $$.flag = 1; | 
|  | $$.commands = $2; | 
|  | cmd_parse_free_commands($3.commands); | 
|  | } else if ($3.flag) { | 
|  | $$.flag = 1; | 
|  | $$.commands = $3.commands; | 
|  | cmd_parse_free_commands($2); | 
|  | } else { | 
|  | $$.flag = 0; | 
|  | $$.commands = cmd_parse_new_commands(); | 
|  | cmd_parse_free_commands($2); | 
|  | cmd_parse_free_commands($3.commands); | 
|  | } | 
|  | } | 
|  |  | 
|  | arguments	: argument | 
|  | { | 
|  | $$ = xcalloc(1, sizeof *$$); | 
|  | TAILQ_INIT($$); | 
|  |  | 
|  | TAILQ_INSERT_HEAD($$, $1, entry); | 
|  | } | 
|  | | argument arguments | 
|  | { | 
|  | TAILQ_INSERT_HEAD($2, $1, entry); | 
|  | $$ = $2; | 
|  | } | 
|  |  | 
|  | argument	: TOKEN | 
|  | { | 
|  | $$ = xcalloc(1, sizeof *$$); | 
|  | $$->type = CMD_PARSE_STRING; | 
|  | $$->string = $1; | 
|  | } | 
|  | | EQUALS | 
|  | { | 
|  | $$ = xcalloc(1, sizeof *$$); | 
|  | $$->type = CMD_PARSE_STRING; | 
|  | $$->string = $1; | 
|  | } | 
|  | | '{' argument_statements | 
|  | { | 
|  | $$ = xcalloc(1, sizeof *$$); | 
|  | $$->type = CMD_PARSE_COMMANDS; | 
|  | $$->commands = $2; | 
|  | } | 
|  |  | 
|  | argument_statements	: statement '}' | 
|  | { | 
|  | $$ = $1; | 
|  | } | 
|  | | statements statement '}' | 
|  | { | 
|  | $$ = $1; | 
|  | TAILQ_CONCAT($$, $2, entry); | 
|  | free($2); | 
|  | } | 
|  |  | 
|  | %% | 
|  |  | 
|  | static char * | 
|  | cmd_parse_get_error(const char *file, u_int line, const char *error) | 
|  | { | 
|  | char	*s; | 
|  |  | 
|  | if (file == NULL) | 
|  | s = xstrdup(error); | 
|  | else | 
|  | xasprintf(&s, "%s:%u: %s", file, line, error); | 
|  | return (s); | 
|  | } | 
|  |  | 
|  | static void | 
|  | cmd_parse_print_commands(struct cmd_parse_input *pi, struct cmd_list *cmdlist) | 
|  | { | 
|  | char	*s; | 
|  |  | 
|  | if (pi->item == NULL || (~pi->flags & CMD_PARSE_VERBOSE)) | 
|  | return; | 
|  | s = cmd_list_print(cmdlist, 0); | 
|  | if (pi->file != NULL) | 
|  | cmdq_print(pi->item, "%s:%u: %s", pi->file, pi->line, s); | 
|  | else | 
|  | cmdq_print(pi->item, "%u: %s", pi->line, s); | 
|  | free(s); | 
|  | } | 
|  |  | 
|  | static void | 
|  | cmd_parse_free_argument(struct cmd_parse_argument *arg) | 
|  | { | 
|  | switch (arg->type) { | 
|  | case CMD_PARSE_STRING: | 
|  | free(arg->string); | 
|  | break; | 
|  | case CMD_PARSE_COMMANDS: | 
|  | cmd_parse_free_commands(arg->commands); | 
|  | break; | 
|  | case CMD_PARSE_PARSED_COMMANDS: | 
|  | cmd_list_free(arg->cmdlist); | 
|  | break; | 
|  | } | 
|  | free(arg); | 
|  | } | 
|  |  | 
|  | static void | 
|  | cmd_parse_free_arguments(struct cmd_parse_arguments *args) | 
|  | { | 
|  | struct cmd_parse_argument	*arg, *arg1; | 
|  |  | 
|  | TAILQ_FOREACH_SAFE(arg, args, entry, arg1) { | 
|  | TAILQ_REMOVE(args, arg, entry); | 
|  | cmd_parse_free_argument(arg); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | cmd_parse_free_command(struct cmd_parse_command *cmd) | 
|  | { | 
|  | cmd_parse_free_arguments(&cmd->arguments); | 
|  | free(cmd); | 
|  | } | 
|  |  | 
|  | static struct cmd_parse_commands * | 
|  | cmd_parse_new_commands(void) | 
|  | { | 
|  | struct cmd_parse_commands	*cmds; | 
|  |  | 
|  | cmds = xmalloc(sizeof *cmds); | 
|  | TAILQ_INIT(cmds); | 
|  | return (cmds); | 
|  | } | 
|  |  | 
|  | static void | 
|  | cmd_parse_free_commands(struct cmd_parse_commands *cmds) | 
|  | { | 
|  | struct cmd_parse_command	*cmd, *cmd1; | 
|  |  | 
|  | TAILQ_FOREACH_SAFE(cmd, cmds, entry, cmd1) { | 
|  | TAILQ_REMOVE(cmds, cmd, entry); | 
|  | cmd_parse_free_command(cmd); | 
|  | } | 
|  | free(cmds); | 
|  | } | 
|  |  | 
|  | static struct cmd_parse_commands * | 
|  | cmd_parse_run_parser(char **cause) | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  | struct cmd_parse_scope	*scope, *scope1; | 
|  | int			 retval; | 
|  |  | 
|  | ps->commands = NULL; | 
|  | TAILQ_INIT(&ps->stack); | 
|  |  | 
|  | retval = yyparse(); | 
|  | TAILQ_FOREACH_SAFE(scope, &ps->stack, entry, scope1) { | 
|  | TAILQ_REMOVE(&ps->stack, scope, entry); | 
|  | free(scope); | 
|  | } | 
|  | if (retval != 0) { | 
|  | *cause = ps->error; | 
|  | return (NULL); | 
|  | } | 
|  |  | 
|  | if (ps->commands == NULL) | 
|  | return (cmd_parse_new_commands()); | 
|  | return (ps->commands); | 
|  | } | 
|  |  | 
|  | static struct cmd_parse_commands * | 
|  | cmd_parse_do_file(FILE *f, struct cmd_parse_input *pi, char **cause) | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  |  | 
|  | memset(ps, 0, sizeof *ps); | 
|  | ps->input = pi; | 
|  | ps->f = f; | 
|  | return (cmd_parse_run_parser(cause)); | 
|  | } | 
|  |  | 
|  | static struct cmd_parse_commands * | 
|  | cmd_parse_do_buffer(const char *buf, size_t len, struct cmd_parse_input *pi, | 
|  | char **cause) | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  |  | 
|  | memset(ps, 0, sizeof *ps); | 
|  | ps->input = pi; | 
|  | ps->buf = buf; | 
|  | ps->len = len; | 
|  | return (cmd_parse_run_parser(cause)); | 
|  | } | 
|  |  | 
|  | static void | 
|  | cmd_parse_log_commands(struct cmd_parse_commands *cmds, const char *prefix) | 
|  | { | 
|  | struct cmd_parse_command	*cmd; | 
|  | struct cmd_parse_argument	*arg; | 
|  | u_int				 i, j; | 
|  | char				*s; | 
|  |  | 
|  | i = 0; | 
|  | TAILQ_FOREACH(cmd, cmds, entry) { | 
|  | j = 0; | 
|  | TAILQ_FOREACH(arg, &cmd->arguments, entry) { | 
|  | switch (arg->type) { | 
|  | case CMD_PARSE_STRING: | 
|  | log_debug("%s %u:%u: %s", prefix, i, j, | 
|  | arg->string); | 
|  | break; | 
|  | case CMD_PARSE_COMMANDS: | 
|  | xasprintf(&s, "%s %u:%u", prefix, i, j); | 
|  | cmd_parse_log_commands(arg->commands, s); | 
|  | free(s); | 
|  | break; | 
|  | case CMD_PARSE_PARSED_COMMANDS: | 
|  | s = cmd_list_print(arg->cmdlist, 0); | 
|  | log_debug("%s %u:%u: %s", prefix, i, j, s); | 
|  | free(s); | 
|  | break; | 
|  | } | 
|  | j++; | 
|  | } | 
|  | i++; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int | 
|  | cmd_parse_expand_alias(struct cmd_parse_command *cmd, | 
|  | struct cmd_parse_input *pi, struct cmd_parse_result *pr) | 
|  | { | 
|  | struct cmd_parse_argument	*arg, *arg1, *first; | 
|  | struct cmd_parse_commands	*cmds; | 
|  | struct cmd_parse_command	*last; | 
|  | char				*alias, *name, *cause; | 
|  |  | 
|  | if (pi->flags & CMD_PARSE_NOALIAS) | 
|  | return (0); | 
|  | memset(pr, 0, sizeof *pr); | 
|  |  | 
|  | first = TAILQ_FIRST(&cmd->arguments); | 
|  | if (first == NULL || first->type != CMD_PARSE_STRING) { | 
|  | pr->status = CMD_PARSE_SUCCESS; | 
|  | pr->cmdlist = cmd_list_new(); | 
|  | return (1); | 
|  | } | 
|  | name = first->string; | 
|  |  | 
|  | alias = cmd_get_alias(name); | 
|  | if (alias == NULL) | 
|  | return (0); | 
|  | log_debug("%s: %u alias %s = %s", __func__, pi->line, name, alias); | 
|  |  | 
|  | cmds = cmd_parse_do_buffer(alias, strlen(alias), pi, &cause); | 
|  | free(alias); | 
|  | if (cmds == NULL) { | 
|  | pr->status = CMD_PARSE_ERROR; | 
|  | pr->error = cause; | 
|  | return (1); | 
|  | } | 
|  |  | 
|  | last = TAILQ_LAST(cmds, cmd_parse_commands); | 
|  | if (last == NULL) { | 
|  | pr->status = CMD_PARSE_SUCCESS; | 
|  | pr->cmdlist = cmd_list_new(); | 
|  | return (1); | 
|  | } | 
|  |  | 
|  | TAILQ_REMOVE(&cmd->arguments, first, entry); | 
|  | cmd_parse_free_argument(first); | 
|  |  | 
|  | TAILQ_FOREACH_SAFE(arg, &cmd->arguments, entry, arg1) { | 
|  | TAILQ_REMOVE(&cmd->arguments, arg, entry); | 
|  | TAILQ_INSERT_TAIL(&last->arguments, arg, entry); | 
|  | } | 
|  | cmd_parse_log_commands(cmds, __func__); | 
|  |  | 
|  | pi->flags |= CMD_PARSE_NOALIAS; | 
|  | cmd_parse_build_commands(cmds, pi, pr); | 
|  | pi->flags &= ~CMD_PARSE_NOALIAS; | 
|  | return (1); | 
|  | } | 
|  |  | 
|  | static void | 
|  | cmd_parse_build_command(struct cmd_parse_command *cmd, | 
|  | struct cmd_parse_input *pi, struct cmd_parse_result *pr) | 
|  | { | 
|  | struct cmd_parse_argument	*arg; | 
|  | struct cmd			*add; | 
|  | char				*cause; | 
|  | struct args_value		*values = NULL; | 
|  | u_int				 count = 0, idx; | 
|  |  | 
|  | memset(pr, 0, sizeof *pr); | 
|  |  | 
|  | if (cmd_parse_expand_alias(cmd, pi, pr)) | 
|  | return; | 
|  |  | 
|  | TAILQ_FOREACH(arg, &cmd->arguments, entry) { | 
|  | values = xrecallocarray(values, count, count + 1, | 
|  | sizeof *values); | 
|  | switch (arg->type) { | 
|  | case CMD_PARSE_STRING: | 
|  | values[count].type = ARGS_STRING; | 
|  | values[count].string = xstrdup(arg->string); | 
|  | break; | 
|  | case CMD_PARSE_COMMANDS: | 
|  | cmd_parse_build_commands(arg->commands, pi, pr); | 
|  | if (pr->status != CMD_PARSE_SUCCESS) | 
|  | goto out; | 
|  | values[count].type = ARGS_COMMANDS; | 
|  | values[count].cmdlist = pr->cmdlist; | 
|  | break; | 
|  | case CMD_PARSE_PARSED_COMMANDS: | 
|  | values[count].type = ARGS_COMMANDS; | 
|  | values[count].cmdlist = arg->cmdlist; | 
|  | values[count].cmdlist->references++; | 
|  | break; | 
|  | } | 
|  | count++; | 
|  | } | 
|  |  | 
|  | add = cmd_parse(values, count, pi->file, pi->line, &cause); | 
|  | if (add == NULL) { | 
|  | pr->status = CMD_PARSE_ERROR; | 
|  | pr->error = cmd_parse_get_error(pi->file, pi->line, cause); | 
|  | free(cause); | 
|  | goto out; | 
|  | } | 
|  | pr->status = CMD_PARSE_SUCCESS; | 
|  | pr->cmdlist = cmd_list_new(); | 
|  | cmd_list_append(pr->cmdlist, add); | 
|  |  | 
|  | out: | 
|  | for (idx = 0; idx < count; idx++) | 
|  | args_free_value(&values[idx]); | 
|  | free(values); | 
|  | } | 
|  |  | 
|  | static void | 
|  | cmd_parse_build_commands(struct cmd_parse_commands *cmds, | 
|  | struct cmd_parse_input *pi, struct cmd_parse_result *pr) | 
|  | { | 
|  | struct cmd_parse_command	*cmd; | 
|  | u_int				 line = UINT_MAX; | 
|  | struct cmd_list			*current = NULL, *result; | 
|  | char				*s; | 
|  |  | 
|  | memset(pr, 0, sizeof *pr); | 
|  |  | 
|  | /* Check for an empty list. */ | 
|  | if (TAILQ_EMPTY(cmds)) { | 
|  | pr->status = CMD_PARSE_SUCCESS; | 
|  | pr->cmdlist = cmd_list_new(); | 
|  | return; | 
|  | } | 
|  | cmd_parse_log_commands(cmds, __func__); | 
|  |  | 
|  | /* | 
|  | * Parse each command into a command list. Create a new command list | 
|  | * for each line (unless the flag is set) so they get a new group (so | 
|  | * the queue knows which ones to remove if a command fails when | 
|  | * executed). | 
|  | */ | 
|  | result = cmd_list_new(); | 
|  | TAILQ_FOREACH(cmd, cmds, entry) { | 
|  | if (((~pi->flags & CMD_PARSE_ONEGROUP) && cmd->line != line)) { | 
|  | if (current != NULL) { | 
|  | cmd_parse_print_commands(pi, current); | 
|  | cmd_list_move(result, current); | 
|  | cmd_list_free(current); | 
|  | } | 
|  | current = cmd_list_new(); | 
|  | } | 
|  | if (current == NULL) | 
|  | current = cmd_list_new(); | 
|  | line = pi->line = cmd->line; | 
|  |  | 
|  | cmd_parse_build_command(cmd, pi, pr); | 
|  | if (pr->status != CMD_PARSE_SUCCESS) { | 
|  | cmd_list_free(result); | 
|  | cmd_list_free(current); | 
|  | return; | 
|  | } | 
|  | cmd_list_append_all(current, pr->cmdlist); | 
|  | cmd_list_free(pr->cmdlist); | 
|  | } | 
|  | if (current != NULL) { | 
|  | cmd_parse_print_commands(pi, current); | 
|  | cmd_list_move(result, current); | 
|  | cmd_list_free(current); | 
|  | } | 
|  |  | 
|  | s = cmd_list_print(result, 0); | 
|  | log_debug("%s: %s", __func__, s); | 
|  | free(s); | 
|  |  | 
|  | pr->status = CMD_PARSE_SUCCESS; | 
|  | pr->cmdlist = result; | 
|  | } | 
|  |  | 
|  | struct cmd_parse_result * | 
|  | cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi) | 
|  | { | 
|  | static struct cmd_parse_result	 pr; | 
|  | struct cmd_parse_input		 input; | 
|  | struct cmd_parse_commands	*cmds; | 
|  | char				*cause; | 
|  |  | 
|  | if (pi == NULL) { | 
|  | memset(&input, 0, sizeof input); | 
|  | pi = &input; | 
|  | } | 
|  | memset(&pr, 0, sizeof pr); | 
|  |  | 
|  | cmds = cmd_parse_do_file(f, pi, &cause); | 
|  | if (cmds == NULL) { | 
|  | pr.status = CMD_PARSE_ERROR; | 
|  | pr.error = cause; | 
|  | return (&pr); | 
|  | } | 
|  | cmd_parse_build_commands(cmds, pi, &pr); | 
|  | cmd_parse_free_commands(cmds); | 
|  | return (&pr); | 
|  |  | 
|  | } | 
|  |  | 
|  | struct cmd_parse_result * | 
|  | cmd_parse_from_string(const char *s, struct cmd_parse_input *pi) | 
|  | { | 
|  | struct cmd_parse_input	input; | 
|  |  | 
|  | if (pi == NULL) { | 
|  | memset(&input, 0, sizeof input); | 
|  | pi = &input; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * When parsing a string, put commands in one group even if there are | 
|  | * multiple lines. This means { a \n b } is identical to "a ; b" when | 
|  | * given as an argument to another command. | 
|  | */ | 
|  | pi->flags |= CMD_PARSE_ONEGROUP; | 
|  | return (cmd_parse_from_buffer(s, strlen(s), pi)); | 
|  | } | 
|  |  | 
|  | enum cmd_parse_status | 
|  | cmd_parse_and_insert(const char *s, struct cmd_parse_input *pi, | 
|  | struct cmdq_item *after, struct cmdq_state *state, char **error) | 
|  | { | 
|  | struct cmd_parse_result	*pr; | 
|  | struct cmdq_item	*item; | 
|  |  | 
|  | pr = cmd_parse_from_string(s, pi); | 
|  | switch (pr->status) { | 
|  | case CMD_PARSE_ERROR: | 
|  | if (error != NULL) | 
|  | *error = pr->error; | 
|  | else | 
|  | free(pr->error); | 
|  | break; | 
|  | case CMD_PARSE_SUCCESS: | 
|  | item = cmdq_get_command(pr->cmdlist, state); | 
|  | cmdq_insert_after(after, item); | 
|  | cmd_list_free(pr->cmdlist); | 
|  | break; | 
|  | } | 
|  | return (pr->status); | 
|  | } | 
|  |  | 
|  | enum cmd_parse_status | 
|  | cmd_parse_and_append(const char *s, struct cmd_parse_input *pi, | 
|  | struct client *c, struct cmdq_state *state, char **error) | 
|  | { | 
|  | struct cmd_parse_result	*pr; | 
|  | struct cmdq_item	*item; | 
|  |  | 
|  | pr = cmd_parse_from_string(s, pi); | 
|  | switch (pr->status) { | 
|  | case CMD_PARSE_ERROR: | 
|  | if (error != NULL) | 
|  | *error = pr->error; | 
|  | else | 
|  | free(pr->error); | 
|  | break; | 
|  | case CMD_PARSE_SUCCESS: | 
|  | item = cmdq_get_command(pr->cmdlist, state); | 
|  | cmdq_append(c, item); | 
|  | cmd_list_free(pr->cmdlist); | 
|  | break; | 
|  | } | 
|  | return (pr->status); | 
|  | } | 
|  |  | 
|  | struct cmd_parse_result * | 
|  | cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi) | 
|  | { | 
|  | static struct cmd_parse_result	 pr; | 
|  | struct cmd_parse_input		 input; | 
|  | struct cmd_parse_commands	*cmds; | 
|  | char				*cause; | 
|  |  | 
|  | if (pi == NULL) { | 
|  | memset(&input, 0, sizeof input); | 
|  | pi = &input; | 
|  | } | 
|  | memset(&pr, 0, sizeof pr); | 
|  |  | 
|  | if (len == 0) { | 
|  | pr.status = CMD_PARSE_SUCCESS; | 
|  | pr.cmdlist = cmd_list_new(); | 
|  | return (&pr); | 
|  | } | 
|  |  | 
|  | cmds = cmd_parse_do_buffer(buf, len, pi, &cause); | 
|  | if (cmds == NULL) { | 
|  | pr.status = CMD_PARSE_ERROR; | 
|  | pr.error = cause; | 
|  | return (&pr); | 
|  | } | 
|  | cmd_parse_build_commands(cmds, pi, &pr); | 
|  | cmd_parse_free_commands(cmds); | 
|  | return (&pr); | 
|  | } | 
|  |  | 
|  | struct cmd_parse_result * | 
|  | cmd_parse_from_arguments(struct args_value *values, u_int count, | 
|  | struct cmd_parse_input *pi) | 
|  | { | 
|  | static struct cmd_parse_result	 pr; | 
|  | struct cmd_parse_input		 input; | 
|  | struct cmd_parse_commands	*cmds; | 
|  | struct cmd_parse_command	*cmd; | 
|  | struct cmd_parse_argument	*arg; | 
|  | u_int				 i; | 
|  | char				*copy; | 
|  | size_t				 size; | 
|  | int				 end; | 
|  |  | 
|  | /* | 
|  | * The commands are already split up into arguments, so just separate | 
|  | * into a set of commands by ';'. | 
|  | */ | 
|  |  | 
|  | if (pi == NULL) { | 
|  | memset(&input, 0, sizeof input); | 
|  | pi = &input; | 
|  | } | 
|  | memset(&pr, 0, sizeof pr); | 
|  |  | 
|  | cmds = cmd_parse_new_commands(); | 
|  |  | 
|  | cmd = xcalloc(1, sizeof *cmd); | 
|  | cmd->line = pi->line; | 
|  | TAILQ_INIT(&cmd->arguments); | 
|  |  | 
|  | for (i = 0; i < count; i++) { | 
|  | end = 0; | 
|  | if (values[i].type == ARGS_STRING) { | 
|  | copy = xstrdup(values[i].string); | 
|  | size = strlen(copy); | 
|  | if (size != 0 && copy[size - 1] == ';') { | 
|  | copy[--size] = '\0'; | 
|  | if (size > 0 && copy[size - 1] == '\\') | 
|  | copy[size - 1] = ';'; | 
|  | else | 
|  | end = 1; | 
|  | } | 
|  | if (!end || size != 0) { | 
|  | arg = xcalloc(1, sizeof *arg); | 
|  | arg->type = CMD_PARSE_STRING; | 
|  | arg->string = copy; | 
|  | TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry); | 
|  | } | 
|  | } else if (values[i].type == ARGS_COMMANDS) { | 
|  | arg = xcalloc(1, sizeof *arg); | 
|  | arg->type = CMD_PARSE_PARSED_COMMANDS; | 
|  | arg->cmdlist = values[i].cmdlist; | 
|  | arg->cmdlist->references++; | 
|  | TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry); | 
|  | } else | 
|  | fatalx("unknown argument type"); | 
|  | if (end) { | 
|  | TAILQ_INSERT_TAIL(cmds, cmd, entry); | 
|  | cmd = xcalloc(1, sizeof *cmd); | 
|  | cmd->line = pi->line; | 
|  | TAILQ_INIT(&cmd->arguments); | 
|  | } | 
|  | } | 
|  | if (!TAILQ_EMPTY(&cmd->arguments)) | 
|  | TAILQ_INSERT_TAIL(cmds, cmd, entry); | 
|  | else | 
|  | free(cmd); | 
|  |  | 
|  | cmd_parse_build_commands(cmds, pi, &pr); | 
|  | cmd_parse_free_commands(cmds); | 
|  | return (&pr); | 
|  | } | 
|  |  | 
|  | static int printflike(1, 2) | 
|  | yyerror(const char *fmt, ...) | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  | struct cmd_parse_input	*pi = ps->input; | 
|  | va_list			 ap; | 
|  | char			*error; | 
|  |  | 
|  | if (ps->error != NULL) | 
|  | return (0); | 
|  |  | 
|  | va_start(ap, fmt); | 
|  | xvasprintf(&error, fmt, ap); | 
|  | va_end(ap); | 
|  |  | 
|  | ps->error = cmd_parse_get_error(pi->file, pi->line, error); | 
|  | free(error); | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | static int | 
|  | yylex_is_var(char ch, int first) | 
|  | { | 
|  | if (ch == '=') | 
|  | return (0); | 
|  | if (first && isdigit((u_char)ch)) | 
|  | return (0); | 
|  | return (isalnum((u_char)ch) || ch == '_'); | 
|  | } | 
|  |  | 
|  | static void | 
|  | yylex_append(char **buf, size_t *len, const char *add, size_t addlen) | 
|  | { | 
|  | if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen) | 
|  | fatalx("buffer is too big"); | 
|  | *buf = xrealloc(*buf, (*len) + 1 + addlen); | 
|  | memcpy((*buf) + *len, add, addlen); | 
|  | (*len) += addlen; | 
|  | } | 
|  |  | 
|  | static void | 
|  | yylex_append1(char **buf, size_t *len, char add) | 
|  | { | 
|  | yylex_append(buf, len, &add, 1); | 
|  | } | 
|  |  | 
|  | static int | 
|  | yylex_getc1(void) | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  | int			 ch; | 
|  |  | 
|  | if (ps->f != NULL) | 
|  | ch = getc(ps->f); | 
|  | else { | 
|  | if (ps->off == ps->len) | 
|  | ch = EOF; | 
|  | else | 
|  | ch = ps->buf[ps->off++]; | 
|  | } | 
|  | return (ch); | 
|  | } | 
|  |  | 
|  | static void | 
|  | yylex_ungetc(int ch) | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  |  | 
|  | if (ps->f != NULL) | 
|  | ungetc(ch, ps->f); | 
|  | else if (ps->off > 0 && ch != EOF) | 
|  | ps->off--; | 
|  | } | 
|  |  | 
|  | static int | 
|  | yylex_getc(void) | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  | int			 ch; | 
|  |  | 
|  | if (ps->escapes != 0) { | 
|  | ps->escapes--; | 
|  | return ('\\'); | 
|  | } | 
|  | for (;;) { | 
|  | ch = yylex_getc1(); | 
|  | if (ch == '\\') { | 
|  | ps->escapes++; | 
|  | continue; | 
|  | } | 
|  | if (ch == '\n' && (ps->escapes % 2) == 1) { | 
|  | ps->input->line++; | 
|  | ps->escapes--; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (ps->escapes != 0) { | 
|  | yylex_ungetc(ch); | 
|  | ps->escapes--; | 
|  | return ('\\'); | 
|  | } | 
|  | return (ch); | 
|  | } | 
|  | } | 
|  |  | 
|  | static char * | 
|  | yylex_get_word(int ch) | 
|  | { | 
|  | char	*buf; | 
|  | size_t	 len; | 
|  |  | 
|  | len = 0; | 
|  | buf = xmalloc(1); | 
|  |  | 
|  | do | 
|  | yylex_append1(&buf, &len, ch); | 
|  | while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL); | 
|  | yylex_ungetc(ch); | 
|  |  | 
|  | buf[len] = '\0'; | 
|  | log_debug("%s: %s", __func__, buf); | 
|  | return (buf); | 
|  | } | 
|  |  | 
|  | static int | 
|  | yylex(void) | 
|  | { | 
|  | struct cmd_parse_state	*ps = &parse_state; | 
|  | char			*token, *cp; | 
|  | int			 ch, next, condition; | 
|  |  | 
|  | if (ps->eol) | 
|  | ps->input->line++; | 
|  | ps->eol = 0; | 
|  |  | 
|  | condition = ps->condition; | 
|  | ps->condition = 0; | 
|  |  | 
|  | for (;;) { | 
|  | ch = yylex_getc(); | 
|  |  | 
|  | if (ch == EOF) { | 
|  | /* | 
|  | * Ensure every file or string is terminated by a | 
|  | * newline. This keeps the parser simpler and avoids | 
|  | * having to add a newline to each string. | 
|  | */ | 
|  | if (ps->eof) | 
|  | break; | 
|  | ps->eof = 1; | 
|  | return ('\n'); | 
|  | } | 
|  |  | 
|  | if (ch == ' ' || ch == '\t') { | 
|  | /* | 
|  | * Ignore whitespace. | 
|  | */ | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (ch == '\n') { | 
|  | /* | 
|  | * End of line. Update the line number. | 
|  | */ | 
|  | ps->eol = 1; | 
|  | return ('\n'); | 
|  | } | 
|  |  | 
|  | if (ch == ';' || ch == '{' || ch == '}') { | 
|  | /* | 
|  | * A semicolon or { or } is itself. | 
|  | */ | 
|  | return (ch); | 
|  | } | 
|  |  | 
|  | if (ch == '#') { | 
|  | /* | 
|  | * #{ after a condition opens a format; anything else | 
|  | * is a comment, ignore up to the end of the line. | 
|  | */ | 
|  | next = yylex_getc(); | 
|  | if (condition && next == '{') { | 
|  | yylval.token = yylex_format(); | 
|  | if (yylval.token == NULL) | 
|  | return (ERROR); | 
|  | return (FORMAT); | 
|  | } | 
|  | while (next != '\n' && next != EOF) | 
|  | next = yylex_getc(); | 
|  | if (next == '\n') { | 
|  | ps->input->line++; | 
|  | return ('\n'); | 
|  | } | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (ch == '%') { | 
|  | /* | 
|  | * % is a condition unless it is all % or all numbers, | 
|  | * then it is a token. | 
|  | */ | 
|  | yylval.token = yylex_get_word('%'); | 
|  | for (cp = yylval.token; *cp != '\0'; cp++) { | 
|  | if (*cp != '%' && !isdigit((u_char)*cp)) | 
|  | break; | 
|  | } | 
|  | if (*cp == '\0') | 
|  | return (TOKEN); | 
|  | ps->condition = 1; | 
|  | if (strcmp(yylval.token, "%hidden") == 0) { | 
|  | free(yylval.token); | 
|  | return (HIDDEN); | 
|  | } | 
|  | if (strcmp(yylval.token, "%if") == 0) { | 
|  | free(yylval.token); | 
|  | return (IF); | 
|  | } | 
|  | if (strcmp(yylval.token, "%else") == 0) { | 
|  | free(yylval.token); | 
|  | return (ELSE); | 
|  | } | 
|  | if (strcmp(yylval.token, "%elif") == 0) { | 
|  | free(yylval.token); | 
|  | return (ELIF); | 
|  | } | 
|  | if (strcmp(yylval.token, "%endif") == 0) { | 
|  | free(yylval.token); | 
|  | return (ENDIF); | 
|  | } | 
|  | free(yylval.token); | 
|  | return (ERROR); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Otherwise this is a token. | 
|  | */ | 
|  | token = yylex_token(ch); | 
|  | if (token == NULL) | 
|  | return (ERROR); | 
|  | yylval.token = token; | 
|  |  | 
|  | if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) { | 
|  | for (cp = token + 1; *cp != '='; cp++) { | 
|  | if (!yylex_is_var(*cp, 0)) | 
|  | break; | 
|  | } | 
|  | if (*cp == '=') | 
|  | return (EQUALS); | 
|  | } | 
|  | return (TOKEN); | 
|  | } | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | static char * | 
|  | yylex_format(void) | 
|  | { | 
|  | char	*buf; | 
|  | size_t	 len; | 
|  | int	 ch, brackets = 1; | 
|  |  | 
|  | len = 0; | 
|  | buf = xmalloc(1); | 
|  |  | 
|  | yylex_append(&buf, &len, "#{", 2); | 
|  | for (;;) { | 
|  | if ((ch = yylex_getc()) == EOF || ch == '\n') | 
|  | goto error; | 
|  | if (ch == '#') { | 
|  | if ((ch = yylex_getc()) == EOF || ch == '\n') | 
|  | goto error; | 
|  | if (ch == '{') | 
|  | brackets++; | 
|  | yylex_append1(&buf, &len, '#'); | 
|  | } else if (ch == '}') { | 
|  | if (brackets != 0 && --brackets == 0) { | 
|  | yylex_append1(&buf, &len, ch); | 
|  | break; | 
|  | } | 
|  | } | 
|  | yylex_append1(&buf, &len, ch); | 
|  | } | 
|  | if (brackets != 0) | 
|  | goto error; | 
|  |  | 
|  | buf[len] = '\0'; | 
|  | log_debug("%s: %s", __func__, buf); | 
|  | return (buf); | 
|  |  | 
|  | error: | 
|  | free(buf); | 
|  | return (NULL); | 
|  | } | 
|  |  | 
|  | static int | 
|  | yylex_token_escape(char **buf, size_t *len) | 
|  | { | 
|  | int	 ch, type, o2, o3, mlen; | 
|  | u_int	 size, i, tmp; | 
|  | char	 s[9], m[MB_LEN_MAX]; | 
|  |  | 
|  | ch = yylex_getc(); | 
|  |  | 
|  | if (ch >= '4' && ch <= '7') { | 
|  | yyerror("invalid octal escape"); | 
|  | return (0); | 
|  | } | 
|  | if (ch >= '0' && ch <= '3') { | 
|  | o2 = yylex_getc(); | 
|  | if (o2 >= '0' && o2 <= '7') { | 
|  | o3 = yylex_getc(); | 
|  | if (o3 >= '0' && o3 <= '7') { | 
|  | ch = 64 * (ch - '0') + | 
|  | 8 * (o2 - '0') + | 
|  | (o3 - '0'); | 
|  | yylex_append1(buf, len, ch); | 
|  | return (1); | 
|  | } | 
|  | } | 
|  | yyerror("invalid octal escape"); | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | switch (ch) { | 
|  | case EOF: | 
|  | return (0); | 
|  | case 'a': | 
|  | ch = '\a'; | 
|  | break; | 
|  | case 'b': | 
|  | ch = '\b'; | 
|  | break; | 
|  | case 'e': | 
|  | ch = '\033'; | 
|  | break; | 
|  | case 'f': | 
|  | ch = '\f'; | 
|  | break; | 
|  | case 's': | 
|  | ch = ' '; | 
|  | break; | 
|  | case 'v': | 
|  | ch = '\v'; | 
|  | break; | 
|  | case 'r': | 
|  | ch = '\r'; | 
|  | break; | 
|  | case 'n': | 
|  | ch = '\n'; | 
|  | break; | 
|  | case 't': | 
|  | ch = '\t'; | 
|  | break; | 
|  | case 'u': | 
|  | type = 'u'; | 
|  | size = 4; | 
|  | goto unicode; | 
|  | case 'U': | 
|  | type = 'U'; | 
|  | size = 8; | 
|  | goto unicode; | 
|  | } | 
|  |  | 
|  | yylex_append1(buf, len, ch); | 
|  | return (1); | 
|  |  | 
|  | unicode: | 
|  | for (i = 0; i < size; i++) { | 
|  | ch = yylex_getc(); | 
|  | if (ch == EOF || ch == '\n') | 
|  | return (0); | 
|  | if (!isxdigit((u_char)ch)) { | 
|  | yyerror("invalid \\%c argument", type); | 
|  | return (0); | 
|  | } | 
|  | s[i] = ch; | 
|  | } | 
|  | s[i] = '\0'; | 
|  |  | 
|  | if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) || | 
|  | (size == 8 && sscanf(s, "%8x", &tmp) != 1)) { | 
|  | yyerror("invalid \\%c argument", type); | 
|  | return (0); | 
|  | } | 
|  | mlen = wctomb(m, tmp); | 
|  | if (mlen <= 0 || mlen > (int)sizeof m) { | 
|  | yyerror("invalid \\%c argument", type); | 
|  | return (0); | 
|  | } | 
|  | yylex_append(buf, len, m, mlen); | 
|  | return (1); | 
|  | } | 
|  |  | 
|  | static int | 
|  | yylex_token_variable(char **buf, size_t *len) | 
|  | { | 
|  | struct environ_entry	*envent; | 
|  | int			 ch, brackets = 0; | 
|  | char			 name[1024]; | 
|  | size_t			 namelen = 0; | 
|  | const char		*value; | 
|  |  | 
|  | ch = yylex_getc(); | 
|  | if (ch == EOF) | 
|  | return (0); | 
|  | if (ch == '{') | 
|  | brackets = 1; | 
|  | else { | 
|  | if (!yylex_is_var(ch, 1)) { | 
|  | yylex_append1(buf, len, '$'); | 
|  | yylex_ungetc(ch); | 
|  | return (1); | 
|  | } | 
|  | name[namelen++] = ch; | 
|  | } | 
|  |  | 
|  | for (;;) { | 
|  | ch = yylex_getc(); | 
|  | if (brackets && ch == '}') | 
|  | break; | 
|  | if (ch == EOF || !yylex_is_var(ch, 0)) { | 
|  | if (!brackets) { | 
|  | yylex_ungetc(ch); | 
|  | break; | 
|  | } | 
|  | yyerror("invalid environment variable"); | 
|  | return (0); | 
|  | } | 
|  | if (namelen == (sizeof name) - 2) { | 
|  | yyerror("environment variable is too long"); | 
|  | return (0); | 
|  | } | 
|  | name[namelen++] = ch; | 
|  | } | 
|  | name[namelen] = '\0'; | 
|  |  | 
|  | envent = environ_find(global_environ, name); | 
|  | if (envent != NULL && envent->value != NULL) { | 
|  | value = envent->value; | 
|  | log_debug("%s: %s -> %s", __func__, name, value); | 
|  | yylex_append(buf, len, value, strlen(value)); | 
|  | } | 
|  | return (1); | 
|  | } | 
|  |  | 
|  | static int | 
|  | yylex_token_tilde(char **buf, size_t *len) | 
|  | { | 
|  | struct environ_entry	*envent; | 
|  | int			 ch; | 
|  | char			 name[1024]; | 
|  | size_t			 namelen = 0; | 
|  | struct passwd		*pw; | 
|  | const char		*home = NULL; | 
|  |  | 
|  | for (;;) { | 
|  | ch = yylex_getc(); | 
|  | if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) { | 
|  | yylex_ungetc(ch); | 
|  | break; | 
|  | } | 
|  | if (namelen == (sizeof name) - 2) { | 
|  | yyerror("user name is too long"); | 
|  | return (0); | 
|  | } | 
|  | name[namelen++] = ch; | 
|  | } | 
|  | name[namelen] = '\0'; | 
|  |  | 
|  | if (*name == '\0') { | 
|  | envent = environ_find(global_environ, "HOME"); | 
|  | if (envent != NULL && *envent->value != '\0') | 
|  | home = envent->value; | 
|  | else if ((pw = getpwuid(getuid())) != NULL) | 
|  | home = pw->pw_dir; | 
|  | } else { | 
|  | if ((pw = getpwnam(name)) != NULL) | 
|  | home = pw->pw_dir; | 
|  | } | 
|  | if (home == NULL) | 
|  | return (0); | 
|  |  | 
|  | log_debug("%s: ~%s -> %s", __func__, name, home); | 
|  | yylex_append(buf, len, home, strlen(home)); | 
|  | return (1); | 
|  | } | 
|  |  | 
|  | static char * | 
|  | yylex_token(int ch) | 
|  | { | 
|  | char			*buf; | 
|  | size_t			 len; | 
|  | enum { START, | 
|  | NONE, | 
|  | DOUBLE_QUOTES, | 
|  | SINGLE_QUOTES }	 state = NONE, last = START; | 
|  |  | 
|  | len = 0; | 
|  | buf = xmalloc(1); | 
|  |  | 
|  | for (;;) { | 
|  | /* EOF or \n are always the end of the token. */ | 
|  | if (ch == EOF || (state == NONE && ch == '\n')) | 
|  | break; | 
|  |  | 
|  | /* Whitespace or ; or } ends a token unless inside quotes. */ | 
|  | if ((ch == ' ' || ch == '\t' || ch == ';' || ch == '}') && | 
|  | state == NONE) | 
|  | break; | 
|  |  | 
|  | /* | 
|  | * Spaces and comments inside quotes after \n are removed but | 
|  | * the \n is left. | 
|  | */ | 
|  | if (ch == '\n' && state != NONE) { | 
|  | yylex_append1(&buf, &len, '\n'); | 
|  | while ((ch = yylex_getc()) == ' ' || ch == '\t') | 
|  | /* nothing */; | 
|  | if (ch != '#') | 
|  | continue; | 
|  | ch = yylex_getc(); | 
|  | if (strchr(",#{}:", ch) != NULL) { | 
|  | yylex_ungetc(ch); | 
|  | ch = '#'; | 
|  | } else { | 
|  | while ((ch = yylex_getc()) != '\n' && ch != EOF) | 
|  | /* nothing */; | 
|  | } | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* \ ~ and $ are expanded except in single quotes. */ | 
|  | if (ch == '\\' && state != SINGLE_QUOTES) { | 
|  | if (!yylex_token_escape(&buf, &len)) | 
|  | goto error; | 
|  | goto skip; | 
|  | } | 
|  | if (ch == '~' && last != state && state != SINGLE_QUOTES) { | 
|  | if (!yylex_token_tilde(&buf, &len)) | 
|  | goto error; | 
|  | goto skip; | 
|  | } | 
|  | if (ch == '$' && state != SINGLE_QUOTES) { | 
|  | if (!yylex_token_variable(&buf, &len)) | 
|  | goto error; | 
|  | goto skip; | 
|  | } | 
|  | if (ch == '}' && state == NONE) | 
|  | goto error;  /* unmatched (matched ones were handled) */ | 
|  |  | 
|  | /* ' and " starts or end quotes (and is consumed). */ | 
|  | if (ch == '\'') { | 
|  | if (state == NONE) { | 
|  | state = SINGLE_QUOTES; | 
|  | goto next; | 
|  | } | 
|  | if (state == SINGLE_QUOTES) { | 
|  | state = NONE; | 
|  | goto next; | 
|  | } | 
|  | } | 
|  | if (ch == '"') { | 
|  | if (state == NONE) { | 
|  | state = DOUBLE_QUOTES; | 
|  | goto next; | 
|  | } | 
|  | if (state == DOUBLE_QUOTES) { | 
|  | state = NONE; | 
|  | goto next; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Otherwise add the character to the buffer. */ | 
|  | yylex_append1(&buf, &len, ch); | 
|  |  | 
|  | skip: | 
|  | last = state; | 
|  |  | 
|  | next: | 
|  | ch = yylex_getc(); | 
|  | } | 
|  | yylex_ungetc(ch); | 
|  |  | 
|  | buf[len] = '\0'; | 
|  | log_debug("%s: %s", __func__, buf); | 
|  | return (buf); | 
|  |  | 
|  | error: | 
|  | free(buf); | 
|  | return (NULL); | 
|  | } |