mirror of
https://github.com/tmux/tmux.git
synced 2026-10-07 02:04:22 +00:00
Add invoke API.
This commit is contained in:
488
cmd-invoke.c
Normal file
488
cmd-invoke.c
Normal file
@@ -0,0 +1,488 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicm@users.sourceforge.net>
|
||||
*
|
||||
* 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 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 <pwd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
#include "tmux-parser.h"
|
||||
|
||||
/* One frame in the parse tree stack. */
|
||||
struct cmd_invoke_frame {
|
||||
struct cmd_parse_node *node;
|
||||
struct cmd_parse_node *next;
|
||||
struct cmd_parse_node *end;
|
||||
};
|
||||
|
||||
/* Shared state for a parsed command tree invocation. */
|
||||
struct cmd_invoke_state {
|
||||
u_int references;
|
||||
struct cmd_parse_tree *tree;
|
||||
|
||||
struct cmd_invoke_frame *stack;
|
||||
u_int nstack;
|
||||
|
||||
enum cmd_retval last;
|
||||
int have_last;
|
||||
|
||||
int argc;
|
||||
char **argv;
|
||||
int parse_flags;
|
||||
char *file;
|
||||
};
|
||||
|
||||
static void cmd_invoke_push(struct cmd_invoke_state *,
|
||||
struct cmd_parse_node *, struct cmd_parse_node *,
|
||||
struct cmd_parse_node *);
|
||||
static struct cmd_parse_node *cmd_invoke_next(struct cmd_invoke_state *);
|
||||
static void cmd_invoke_skip_sequence(struct cmd_invoke_state *);
|
||||
static int cmd_invoke_expand_string(struct cmdq_item *,
|
||||
struct cmd_invoke_state *, struct cmd_parse_node *,
|
||||
char **);
|
||||
static int cmd_invoke_assignment(struct cmdq_item *,
|
||||
struct cmd_invoke_state *, struct cmd_parse_node *);
|
||||
static int cmd_invoke_if(struct cmdq_item *, struct cmd_invoke_state *,
|
||||
struct cmd_parse_node *);
|
||||
static struct cmd *cmd_invoke_build_command(struct cmdq_item *,
|
||||
struct cmd_invoke_state *, struct cmd_parse_node *);
|
||||
|
||||
/* Push a node's child range onto the traversal stack. */
|
||||
static void
|
||||
cmd_invoke_push(struct cmd_invoke_state *is, struct cmd_parse_node *node,
|
||||
struct cmd_parse_node *first, struct cmd_parse_node *end)
|
||||
{
|
||||
u_int n = is->nstack + 1;
|
||||
|
||||
is->stack = xreallocarray(is->stack, n, sizeof *is->stack);
|
||||
is->stack[is->nstack].node = node;
|
||||
is->stack[is->nstack].next = first;
|
||||
is->stack[is->nstack].end = end;
|
||||
is->nstack = n;
|
||||
}
|
||||
|
||||
/* Return the next node to execute from the traversal stack. */
|
||||
static struct cmd_parse_node *
|
||||
cmd_invoke_next(struct cmd_invoke_state *is)
|
||||
{
|
||||
struct cmd_invoke_frame *frame;
|
||||
struct cmd_parse_node *node;
|
||||
|
||||
for (;;) {
|
||||
if (is->nstack == 0)
|
||||
return (NULL);
|
||||
frame = &is->stack[is->nstack - 1];
|
||||
if (frame->next != NULL && frame->next != frame->end)
|
||||
break;
|
||||
is->nstack--;
|
||||
}
|
||||
|
||||
node = frame->next;
|
||||
frame->next = cmd_parse_node_next(node);
|
||||
return (node);
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip the rest of the active sequence after a command failure. Failure scope
|
||||
* is the active CMD_PARSE_SEQUENCE. Discard nested frames and advance only as
|
||||
* far as the end of that sequence.
|
||||
*/
|
||||
static void
|
||||
cmd_invoke_skip_sequence(struct cmd_invoke_state *is)
|
||||
{
|
||||
enum cmd_parse_node_type type;
|
||||
u_int i;
|
||||
|
||||
for (i = is->nstack; i > 0; i--) {
|
||||
type = cmd_parse_node_type(is->stack[i - 1].node);
|
||||
if (type == CMD_PARSE_SEQUENCE) {
|
||||
is->stack[i - 1].next = is->stack[i - 1].end;
|
||||
is->nstack = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
is->have_last = 0;
|
||||
}
|
||||
|
||||
/* Append a string to a dynamically allocated buffer. */
|
||||
static void
|
||||
cmd_invoke_append(char **buf, size_t *len, const char *s)
|
||||
{
|
||||
size_t slen;
|
||||
|
||||
if (s != NULL) {
|
||||
slen = strlen(s);
|
||||
*buf = xrealloc(*buf, *len + slen + 1);
|
||||
memcpy(*buf + *len, s, slen + 1);
|
||||
*len += slen;
|
||||
}
|
||||
}
|
||||
|
||||
/* Look up an environment variable. */
|
||||
static const char *
|
||||
cmd_invoke_getenv(struct cmdq_item *item, const char *name)
|
||||
{
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct environ_entry *envent;
|
||||
|
||||
if (c != NULL && c->environ != NULL) {
|
||||
envent = environ_find(c->environ, name);
|
||||
if (envent != NULL && envent->value != NULL)
|
||||
return (envent->value);
|
||||
}
|
||||
envent = environ_find(global_environ, name);
|
||||
if (envent != NULL && envent->value != NULL)
|
||||
return (envent->value);
|
||||
return ("");
|
||||
}
|
||||
|
||||
/* Resolve a tilde expansion to a home directory. */
|
||||
static const char *
|
||||
cmd_invoke_tilde(const char *name)
|
||||
{
|
||||
struct passwd *pw;
|
||||
|
||||
if (name == NULL || *name == '\0')
|
||||
return (find_home());
|
||||
if ((pw = getpwnam(name)) == NULL)
|
||||
return ("");
|
||||
return (pw->pw_dir);
|
||||
}
|
||||
|
||||
/* Expand a parsed string node into an argv string. */
|
||||
static int
|
||||
cmd_invoke_expand_string(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||
struct cmd_parse_node *node, char **out)
|
||||
{
|
||||
struct cmd_parse_node *child;
|
||||
const char *s, *value;
|
||||
char *buf = NULL, *new;
|
||||
size_t len = 0;
|
||||
int i;
|
||||
|
||||
child = cmd_parse_node_first_child(node);
|
||||
while (child != NULL) {
|
||||
value = cmd_parse_node_value(child);
|
||||
switch (cmd_parse_node_type(child)) {
|
||||
case CMD_PARSE_TEXT:
|
||||
s = value;
|
||||
break;
|
||||
case CMD_PARSE_ENVIRONMENT:
|
||||
s = cmd_invoke_getenv(item, value);
|
||||
break;
|
||||
case CMD_PARSE_TILDE:
|
||||
s = cmd_invoke_tilde(value);
|
||||
break;
|
||||
default:
|
||||
fatalx("unexpected node type in string");
|
||||
}
|
||||
cmd_invoke_append(&buf, &len, s);
|
||||
child = cmd_parse_node_next(child);
|
||||
}
|
||||
if (buf == NULL)
|
||||
buf = xstrdup("");
|
||||
for (i = 0; i < is->argc; i++) {
|
||||
new = cmd_template_replace(buf, is->argv[i], i + 1);
|
||||
free(buf);
|
||||
buf = new;
|
||||
}
|
||||
*out = buf;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Execute an assignment node by updating the environment. */
|
||||
static int
|
||||
cmd_invoke_assignment(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||
struct cmd_parse_node *node)
|
||||
{
|
||||
struct cmd_parse_node *value;
|
||||
const char *name;
|
||||
char *expanded;
|
||||
int flags = 0;
|
||||
|
||||
value = cmd_parse_node_first_child(node);
|
||||
if (value == NULL)
|
||||
return (-1);
|
||||
if (cmd_invoke_expand_string(item, is, value, &expanded) != 0)
|
||||
return (-1);
|
||||
|
||||
name = cmd_parse_node_value(node);
|
||||
if (cmd_parse_node_type(node) == CMD_PARSE_HIDDEN_ASSIGN)
|
||||
flags |= ENVIRON_HIDDEN;
|
||||
environ_set(global_environ, name, flags, "%s", expanded);
|
||||
free(expanded);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Expand and evaluate a conditional expression. */
|
||||
static int
|
||||
cmd_invoke_is_true(struct cmdq_item *item,
|
||||
struct cmd_invoke_state *is, struct cmd_parse_node *node, int *result)
|
||||
{
|
||||
struct format_tree *ft;
|
||||
char *s, *expanded;
|
||||
|
||||
if (cmd_invoke_expand_string(item, is, node, &s) != 0)
|
||||
return (-1);
|
||||
ft = format_create_from_target(item);
|
||||
|
||||
expanded = format_expand(ft, s);
|
||||
*result = format_true(expanded);
|
||||
free(expanded);
|
||||
|
||||
format_free(ft);
|
||||
free(s);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Push the selected conditional branch onto the traversal stack. */
|
||||
static void
|
||||
cmd_invoke_push_branch(struct cmd_invoke_state *is,
|
||||
struct cmd_parse_node *node, struct cmd_parse_node *first)
|
||||
{
|
||||
cmd_invoke_push(is, node, first, NULL);
|
||||
}
|
||||
|
||||
/* Select and queue the branch for a conditional node. */
|
||||
static int
|
||||
cmd_invoke_if(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||
struct cmd_parse_node *node)
|
||||
{
|
||||
struct cmd_parse_node *child, *first, *next;
|
||||
int r;
|
||||
|
||||
next = cmd_parse_node_first_child(node);
|
||||
if (next == NULL)
|
||||
return (0);
|
||||
first = cmd_parse_node_next(next);
|
||||
if (cmd_invoke_is_true(item, is, next, &r) != 0)
|
||||
return (-1);
|
||||
if (r) {
|
||||
cmd_invoke_push_branch(is, node, first);
|
||||
return (0);
|
||||
}
|
||||
|
||||
for (child = first; child != NULL; child = cmd_parse_node_next(child)) {
|
||||
switch (cmd_parse_node_type(child)) {
|
||||
case CMD_PARSE_ELIF:
|
||||
next = cmd_parse_node_first_child(child);
|
||||
if (next == NULL)
|
||||
break;
|
||||
if (cmd_invoke_is_true(item, is, next, &r) != 0)
|
||||
return (-1);
|
||||
if (r) {
|
||||
cmd_invoke_push_branch(is, child, next);
|
||||
return (0);
|
||||
}
|
||||
break;
|
||||
case CMD_PARSE_ELSE:
|
||||
next = cmd_parse_node_first_child(child);
|
||||
cmd_invoke_push_branch(is, child, next);
|
||||
return (0);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Build one command from a parsed command node. */
|
||||
static struct cmd *
|
||||
cmd_invoke_build_command(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||
struct cmd_parse_node *node)
|
||||
{
|
||||
struct cmd_parse_node *child;
|
||||
struct args_value *values = NULL;
|
||||
struct cmd *cmd;
|
||||
char *cause = NULL;
|
||||
u_int count = 0, i;
|
||||
|
||||
child = cmd_parse_node_first_child(node);
|
||||
while (child != NULL) {
|
||||
values = xreallocarray(values, count + 1, sizeof *values);
|
||||
memset(&values[count], 0, sizeof values[count]);
|
||||
switch (cmd_parse_node_type(child)) {
|
||||
case CMD_PARSE_STRING:
|
||||
values[count].type = ARGS_STRING;
|
||||
if (cmd_invoke_expand_string(item, is, child,
|
||||
&values[count].string) != 0)
|
||||
goto fail;
|
||||
break;
|
||||
case CMD_PARSE_COMMANDS:
|
||||
values[count].type = ARGS_COMMANDS;
|
||||
values[count].cmdparse = child;
|
||||
break;
|
||||
default:
|
||||
fatalx("unexpected node type in command");
|
||||
}
|
||||
count++;
|
||||
child = cmd_parse_node_next(child);
|
||||
}
|
||||
|
||||
cmd = cmd_parse(values, count, is->file, cmd_parse_node_line(node),
|
||||
is->parse_flags, &cause);
|
||||
if (cmd == NULL) {
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
goto fail;
|
||||
}
|
||||
for (i = 0; i < count; i++) {
|
||||
if (values[i].type == ARGS_STRING)
|
||||
free(values[i].string);
|
||||
}
|
||||
free(values);
|
||||
return (cmd);
|
||||
|
||||
fail:
|
||||
for (i = 0; i < count; i++) {
|
||||
if (values[i].type == ARGS_STRING)
|
||||
free(values[i].string);
|
||||
}
|
||||
free(values);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Create the first invoke queue item for a parsed tree. */
|
||||
struct cmdq_item *
|
||||
cmd_invoke_get(struct cmd_parse_tree *tree, struct cmdq_state *state, int argc,
|
||||
char **argv)
|
||||
{
|
||||
struct cmd_invoke_state *is;
|
||||
struct cmd_parse_node *root = cmd_parse_root(tree), *first;
|
||||
struct cmdq_item *item;
|
||||
int i;
|
||||
|
||||
is = xcalloc(1, sizeof *is);
|
||||
is->references = 1;
|
||||
is->tree = cmd_parse_add_ref(tree);
|
||||
|
||||
is->argc = argc;
|
||||
if (argc != 0) {
|
||||
is->argv = xreallocarray(NULL, argc, sizeof *is->argv);
|
||||
for (i = 0; i < argc; i++)
|
||||
is->argv[i] = xstrdup(argv[i]);
|
||||
}
|
||||
|
||||
first = cmd_parse_node_first_child(root);
|
||||
cmd_invoke_push(is, root, first, NULL);
|
||||
|
||||
item = cmdq_get_invoke(is, state);
|
||||
cmd_invoke_state_free(is);
|
||||
return (item);
|
||||
}
|
||||
|
||||
/* Add a reference to shared invoke state. */
|
||||
struct cmd_invoke_state *
|
||||
cmd_invoke_state_add_ref(struct cmd_invoke_state *is)
|
||||
{
|
||||
is->references++;
|
||||
return (is);
|
||||
}
|
||||
|
||||
/* Release a reference to shared invoke state. */
|
||||
void
|
||||
cmd_invoke_state_free(struct cmd_invoke_state *is)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (is == NULL)
|
||||
return;
|
||||
if (--is->references != 0)
|
||||
return;
|
||||
|
||||
for (i = 0; i < is->argc; i++)
|
||||
free(is->argv[i]);
|
||||
free(is->argv);
|
||||
|
||||
cmd_parse_free(is->tree);
|
||||
free(is->file);
|
||||
free(is->stack);
|
||||
free(is);
|
||||
}
|
||||
|
||||
/* Record the result from the last command item. */
|
||||
void
|
||||
cmd_invoke_result(struct cmd_invoke_state *is, enum cmd_retval retval)
|
||||
{
|
||||
is->last = retval;
|
||||
is->have_last = 1;
|
||||
}
|
||||
|
||||
/* Fire an invoke item and queue the next command plus continuation. */
|
||||
enum cmd_retval
|
||||
cmd_invoke_fire(struct cmdq_item *item, struct cmd_invoke_state *is)
|
||||
{
|
||||
struct cmd_parse_node *node;
|
||||
struct cmdq_item *new_item, *next;
|
||||
struct cmdq_state *state;
|
||||
struct cmd *cmd;
|
||||
|
||||
if (is->have_last && is->last == CMD_RETURN_ERROR)
|
||||
cmd_invoke_skip_sequence(is);
|
||||
else
|
||||
is->have_last = 0;
|
||||
|
||||
for (;;) {
|
||||
node = cmd_invoke_next(is);
|
||||
if (node == NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
switch (cmd_parse_node_type(node)) {
|
||||
case CMD_PARSE_ROOT:
|
||||
case CMD_PARSE_SEQUENCE:
|
||||
cmd_invoke_push(is, node,
|
||||
cmd_parse_node_first_child(node), NULL);
|
||||
break;
|
||||
case CMD_PARSE_ASSIGN:
|
||||
case CMD_PARSE_HIDDEN_ASSIGN:
|
||||
if (cmd_invoke_assignment(item, is, node) != 0)
|
||||
return (CMD_RETURN_ERROR);
|
||||
break;
|
||||
case CMD_PARSE_IF:
|
||||
if (cmd_invoke_if(item, is, node) != 0)
|
||||
return (CMD_RETURN_ERROR);
|
||||
break;
|
||||
case CMD_PARSE_ELIF:
|
||||
case CMD_PARSE_ELSE:
|
||||
break;
|
||||
case CMD_PARSE_COMMAND:
|
||||
cmd = cmd_invoke_build_command(item, is, node);
|
||||
if (cmd == NULL)
|
||||
return (CMD_RETURN_ERROR);
|
||||
|
||||
/*
|
||||
* Queue one command followed by this walker. WAIT and
|
||||
* command-inserted items therefore run before resume.
|
||||
*/
|
||||
state = cmdq_get_state(item);
|
||||
new_item = cmdq_get_one_command(cmd, state, is);
|
||||
next = cmdq_get_invoke(is, state);
|
||||
cmdq_insert_after(item, next);
|
||||
cmdq_insert_after(item, new_item);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
case CMD_PARSE_STRING:
|
||||
case CMD_PARSE_COMMANDS:
|
||||
case CMD_PARSE_TEXT:
|
||||
case CMD_PARSE_ENVIRONMENT:
|
||||
case CMD_PARSE_TILDE:
|
||||
fatalx("unexpected node type");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user