mirror of
https://github.com/tmux/tmux.git
synced 2026-07-22 16:52:42 +00:00
additional payload (to reduce problems with lifetime of objects) and are delivered to one or more event sinks. This is more powerful and reduces the complex dependencies between control mode and hooks. Events are now used for hooks, control mode notifications and for monitors (set-hook -B). wait-for can now wait for an event to fire (-E flag, with -F to for filter), with -v to print the payload, as well as listing (-l) waiting clients on an event and forcing one to wake (-w). A few additional hooks are also now available (pane-created, pane-resized, etc) and some of the existing ones have additional format variables available.
1761 lines
36 KiB
Plaintext
1761 lines
36 KiB
Plaintext
/* $OpenBSD: cmd-parse.y,v 1.58 2026/04/27 12:31:11 nicm Exp $ */
|
|
|
|
/*
|
|
* 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 void printflike(1,2) yyerror(const char *, ...);
|
|
|
|
static char *yylex_token(int);
|
|
static char *yylex_format(void);
|
|
|
|
#define CMD_PARSE_MAX_ENVIRON_LEN 16384
|
|
|
|
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;
|
|
int flag = 1;
|
|
struct cmd_parse_scope *scope;
|
|
|
|
if (ps->scope != NULL) {
|
|
flag = ps->scope->flag;
|
|
TAILQ_FOREACH(scope, &ps->stack, entry)
|
|
flag = flag && scope->flag;
|
|
}
|
|
|
|
if (strlen($1) > CMD_PARSE_MAX_ENVIRON_LEN) {
|
|
yyerror("environment variable is too long");
|
|
YYABORT;
|
|
}
|
|
if ((~flags & CMD_PARSE_PARSEONLY) && 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;
|
|
int flag = 1;
|
|
struct cmd_parse_scope *scope;
|
|
|
|
if (ps->scope != NULL) {
|
|
flag = ps->scope->flag;
|
|
TAILQ_FOREACH(scope, &ps->stack, entry)
|
|
flag = flag && scope->flag;
|
|
}
|
|
|
|
if (strlen($2) > CMD_PARSE_MAX_ENVIRON_LEN) {
|
|
yyerror("environment variable is too long");
|
|
YYABORT;
|
|
}
|
|
if ((~flags & CMD_PARSE_PARSEONLY) && 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 *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_CONCAT(&last->arguments, &cmd->arguments, 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, pi->flags, &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
|
|
free(copy);
|
|
} 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 void 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;
|
|
|
|
va_start(ap, fmt);
|
|
xvasprintf(&error, fmt, ap);
|
|
va_end(ap);
|
|
|
|
ps->error = cmd_parse_get_error(pi->file, pi->line, error);
|
|
free(error);
|
|
}
|
|
|
|
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 == '\r') {
|
|
/*
|
|
* Treat \r\n as \n.
|
|
*/
|
|
ch = yylex_getc();
|
|
if (ch != '\n') {
|
|
yylex_ungetc(ch);
|
|
ch = '\r';
|
|
}
|
|
}
|
|
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 != 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)
|
|
{
|
|
struct cmd_parse_state *ps = &parse_state;
|
|
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) {
|
|
log_debug("%s: end at EOF", __func__);
|
|
break;
|
|
}
|
|
if (state == NONE && ch == '\r') {
|
|
ch = yylex_getc();
|
|
if (ch != '\n') {
|
|
yylex_ungetc(ch);
|
|
ch = '\r';
|
|
}
|
|
}
|
|
if (ch == '\n') {
|
|
if (state == NONE) {
|
|
log_debug("%s: end at EOL", __func__);
|
|
break;
|
|
}
|
|
ps->input->line++;
|
|
}
|
|
|
|
/* Whitespace or ; or } ends a token unless inside quotes. */
|
|
if (state == NONE && (ch == ' ' || ch == '\t')) {
|
|
log_debug("%s: end at WS", __func__);
|
|
break;
|
|
}
|
|
if (state == NONE && (ch == ';' || ch == '}')) {
|
|
log_debug("%s: end at %c", __func__, ch);
|
|
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);
|
|
}
|