mirror of
https://github.com/tmux/tmux.git
synced 2024-11-01 07:08:49 +00:00
c9616700ca
GitHub issue 3973.
901 lines
20 KiB
C
901 lines
20 KiB
C
/* $OpenBSD$ */
|
|
|
|
/*
|
|
* Copyright (c) 2013 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 <pwd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <vis.h>
|
|
|
|
#include "tmux.h"
|
|
|
|
/* Command queue flags. */
|
|
#define CMDQ_FIRED 0x1
|
|
#define CMDQ_WAITING 0x2
|
|
|
|
/* Command queue item type. */
|
|
enum cmdq_type {
|
|
CMDQ_COMMAND,
|
|
CMDQ_CALLBACK,
|
|
};
|
|
|
|
/* Command queue item. */
|
|
struct cmdq_item {
|
|
char *name;
|
|
struct cmdq_list *queue;
|
|
struct cmdq_item *next;
|
|
|
|
struct client *client;
|
|
struct client *target_client;
|
|
|
|
enum cmdq_type type;
|
|
u_int group;
|
|
|
|
u_int number;
|
|
time_t time;
|
|
|
|
int flags;
|
|
|
|
struct cmdq_state *state;
|
|
struct cmd_find_state source;
|
|
struct cmd_find_state target;
|
|
|
|
struct cmd_list *cmdlist;
|
|
struct cmd *cmd;
|
|
|
|
cmdq_cb cb;
|
|
void *data;
|
|
|
|
TAILQ_ENTRY(cmdq_item) entry;
|
|
};
|
|
TAILQ_HEAD(cmdq_item_list, cmdq_item);
|
|
|
|
/*
|
|
* Command queue state. This is the context for commands on the command queue.
|
|
* It holds information about how the commands were fired (the key and flags),
|
|
* any additional formats for the commands, and the current default target.
|
|
* Multiple commands can share the same state and a command may update the
|
|
* default target.
|
|
*/
|
|
struct cmdq_state {
|
|
int references;
|
|
int flags;
|
|
|
|
struct format_tree *formats;
|
|
|
|
struct key_event event;
|
|
struct cmd_find_state current;
|
|
};
|
|
|
|
/* Command queue. */
|
|
struct cmdq_list {
|
|
struct cmdq_item *item;
|
|
struct cmdq_item_list list;
|
|
};
|
|
|
|
/* Get command queue name. */
|
|
static const char *
|
|
cmdq_name(struct client *c)
|
|
{
|
|
static char s[256];
|
|
|
|
if (c == NULL)
|
|
return ("<global>");
|
|
if (c->name != NULL)
|
|
xsnprintf(s, sizeof s, "<%s>", c->name);
|
|
else
|
|
xsnprintf(s, sizeof s, "<%p>", c);
|
|
return (s);
|
|
}
|
|
|
|
/* Get command queue from client. */
|
|
static struct cmdq_list *
|
|
cmdq_get(struct client *c)
|
|
{
|
|
static struct cmdq_list *global_queue;
|
|
|
|
if (c == NULL) {
|
|
if (global_queue == NULL)
|
|
global_queue = cmdq_new();
|
|
return (global_queue);
|
|
}
|
|
return (c->queue);
|
|
}
|
|
|
|
/* Create a queue. */
|
|
struct cmdq_list *
|
|
cmdq_new(void)
|
|
{
|
|
struct cmdq_list *queue;
|
|
|
|
queue = xcalloc(1, sizeof *queue);
|
|
TAILQ_INIT (&queue->list);
|
|
return (queue);
|
|
}
|
|
|
|
/* Free a queue. */
|
|
void
|
|
cmdq_free(struct cmdq_list *queue)
|
|
{
|
|
if (!TAILQ_EMPTY(&queue->list))
|
|
fatalx("queue not empty");
|
|
free(queue);
|
|
}
|
|
|
|
/* Get item name. */
|
|
const char *
|
|
cmdq_get_name(struct cmdq_item *item)
|
|
{
|
|
return (item->name);
|
|
}
|
|
|
|
/* Get item client. */
|
|
struct client *
|
|
cmdq_get_client(struct cmdq_item *item)
|
|
{
|
|
return (item->client);
|
|
}
|
|
|
|
/* Get item target client. */
|
|
struct client *
|
|
cmdq_get_target_client(struct cmdq_item *item)
|
|
{
|
|
return (item->target_client);
|
|
}
|
|
|
|
/* Get item state. */
|
|
struct cmdq_state *
|
|
cmdq_get_state(struct cmdq_item *item)
|
|
{
|
|
return (item->state);
|
|
}
|
|
|
|
/* Get item target. */
|
|
struct cmd_find_state *
|
|
cmdq_get_target(struct cmdq_item *item)
|
|
{
|
|
return (&item->target);
|
|
}
|
|
|
|
/* Get item source. */
|
|
struct cmd_find_state *
|
|
cmdq_get_source(struct cmdq_item *item)
|
|
{
|
|
return (&item->source);
|
|
}
|
|
|
|
/* Get state event. */
|
|
struct key_event *
|
|
cmdq_get_event(struct cmdq_item *item)
|
|
{
|
|
return (&item->state->event);
|
|
}
|
|
|
|
/* Get state current target. */
|
|
struct cmd_find_state *
|
|
cmdq_get_current(struct cmdq_item *item)
|
|
{
|
|
return (&item->state->current);
|
|
}
|
|
|
|
/* Get state flags. */
|
|
int
|
|
cmdq_get_flags(struct cmdq_item *item)
|
|
{
|
|
return (item->state->flags);
|
|
}
|
|
|
|
/* Create a new state. */
|
|
struct cmdq_state *
|
|
cmdq_new_state(struct cmd_find_state *current, struct key_event *event,
|
|
int flags)
|
|
{
|
|
struct cmdq_state *state;
|
|
|
|
state = xcalloc(1, sizeof *state);
|
|
state->references = 1;
|
|
state->flags = flags;
|
|
|
|
if (event != NULL)
|
|
memcpy(&state->event, event, sizeof state->event);
|
|
else
|
|
state->event.key = KEYC_NONE;
|
|
if (current != NULL && cmd_find_valid_state(current))
|
|
cmd_find_copy_state(&state->current, current);
|
|
else
|
|
cmd_find_clear_state(&state->current, 0);
|
|
|
|
return (state);
|
|
}
|
|
|
|
/* Add a reference to a state. */
|
|
struct cmdq_state *
|
|
cmdq_link_state(struct cmdq_state *state)
|
|
{
|
|
state->references++;
|
|
return (state);
|
|
}
|
|
|
|
/* Make a copy of a state. */
|
|
struct cmdq_state *
|
|
cmdq_copy_state(struct cmdq_state *state, struct cmd_find_state *current)
|
|
{
|
|
if (current != NULL)
|
|
return (cmdq_new_state(current, &state->event, state->flags));
|
|
return (cmdq_new_state(&state->current, &state->event, state->flags));
|
|
}
|
|
|
|
/* Free a state. */
|
|
void
|
|
cmdq_free_state(struct cmdq_state *state)
|
|
{
|
|
if (--state->references != 0)
|
|
return;
|
|
|
|
if (state->formats != NULL)
|
|
format_free(state->formats);
|
|
free(state);
|
|
}
|
|
|
|
/* Add a format to command queue. */
|
|
void
|
|
cmdq_add_format(struct cmdq_state *state, const char *key, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char *value;
|
|
|
|
va_start(ap, fmt);
|
|
xvasprintf(&value, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (state->formats == NULL)
|
|
state->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
|
|
format_add(state->formats, key, "%s", value);
|
|
|
|
free(value);
|
|
}
|
|
|
|
/* Add formats to command queue. */
|
|
void
|
|
cmdq_add_formats(struct cmdq_state *state, struct format_tree *ft)
|
|
{
|
|
if (state->formats == NULL)
|
|
state->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
|
|
format_merge(state->formats, ft);
|
|
}
|
|
|
|
/* Merge formats from item. */
|
|
void
|
|
cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft)
|
|
{
|
|
const struct cmd_entry *entry;
|
|
|
|
if (item->cmd != NULL) {
|
|
entry = cmd_get_entry(item->cmd);
|
|
format_add(ft, "command", "%s", entry->name);
|
|
}
|
|
if (item->state->formats != NULL)
|
|
format_merge(ft, item->state->formats);
|
|
}
|
|
|
|
/* Append an item. */
|
|
struct cmdq_item *
|
|
cmdq_append(struct client *c, struct cmdq_item *item)
|
|
{
|
|
struct cmdq_list *queue = cmdq_get(c);
|
|
struct cmdq_item *next;
|
|
|
|
do {
|
|
next = item->next;
|
|
item->next = NULL;
|
|
|
|
if (c != NULL)
|
|
c->references++;
|
|
item->client = c;
|
|
|
|
item->queue = queue;
|
|
TAILQ_INSERT_TAIL(&queue->list, item, entry);
|
|
log_debug("%s %s: %s", __func__, cmdq_name(c), item->name);
|
|
|
|
item = next;
|
|
} while (item != NULL);
|
|
return (TAILQ_LAST(&queue->list, cmdq_item_list));
|
|
}
|
|
|
|
/* Insert an item. */
|
|
struct cmdq_item *
|
|
cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item)
|
|
{
|
|
struct client *c = after->client;
|
|
struct cmdq_list *queue = after->queue;
|
|
struct cmdq_item *next;
|
|
|
|
do {
|
|
next = item->next;
|
|
item->next = after->next;
|
|
after->next = item;
|
|
|
|
if (c != NULL)
|
|
c->references++;
|
|
item->client = c;
|
|
|
|
item->queue = queue;
|
|
TAILQ_INSERT_AFTER(&queue->list, after, item, entry);
|
|
log_debug("%s %s: %s after %s", __func__, cmdq_name(c),
|
|
item->name, after->name);
|
|
|
|
after = item;
|
|
item = next;
|
|
} while (item != NULL);
|
|
return (after);
|
|
}
|
|
|
|
/* Insert a hook. */
|
|
void
|
|
cmdq_insert_hook(struct session *s, struct cmdq_item *item,
|
|
struct cmd_find_state *current, const char *fmt, ...)
|
|
{
|
|
struct cmdq_state *state = item->state;
|
|
struct cmd *cmd = item->cmd;
|
|
struct args *args = cmd_get_args(cmd);
|
|
struct args_entry *ae;
|
|
struct args_value *av;
|
|
struct options *oo;
|
|
va_list ap;
|
|
char *name, tmp[32], flag, *arguments;
|
|
u_int i;
|
|
const char *value;
|
|
struct cmdq_item *new_item;
|
|
struct cmdq_state *new_state;
|
|
struct options_entry *o;
|
|
struct options_array_item *a;
|
|
struct cmd_list *cmdlist;
|
|
|
|
if (item->state->flags & CMDQ_STATE_NOHOOKS)
|
|
return;
|
|
if (s == NULL)
|
|
oo = global_s_options;
|
|
else
|
|
oo = s->options;
|
|
|
|
va_start(ap, fmt);
|
|
xvasprintf(&name, fmt, ap);
|
|
va_end(ap);
|
|
|
|
o = options_get(oo, name);
|
|
if (o == NULL) {
|
|
free(name);
|
|
return;
|
|
}
|
|
log_debug("running hook %s (parent %p)", name, item);
|
|
|
|
/*
|
|
* The hooks get a new state because they should not update the current
|
|
* target or formats for any subsequent commands.
|
|
*/
|
|
new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS);
|
|
cmdq_add_format(new_state, "hook", "%s", name);
|
|
|
|
arguments = args_print(args);
|
|
cmdq_add_format(new_state, "hook_arguments", "%s", arguments);
|
|
free(arguments);
|
|
|
|
for (i = 0; i < args_count(args); i++) {
|
|
xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i);
|
|
cmdq_add_format(new_state, tmp, "%s", args_string(args, i));
|
|
}
|
|
flag = args_first(args, &ae);
|
|
while (flag != 0) {
|
|
value = args_get(args, flag);
|
|
if (value == NULL) {
|
|
xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag);
|
|
cmdq_add_format(new_state, tmp, "1");
|
|
} else {
|
|
xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag);
|
|
cmdq_add_format(new_state, tmp, "%s", value);
|
|
}
|
|
|
|
i = 0;
|
|
av = args_first_value(args, flag);
|
|
while (av != NULL) {
|
|
xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i);
|
|
cmdq_add_format(new_state, tmp, "%s", av->string);
|
|
i++;
|
|
av = args_next_value(av);
|
|
}
|
|
|
|
flag = args_next(&ae);
|
|
}
|
|
|
|
a = options_array_first(o);
|
|
while (a != NULL) {
|
|
cmdlist = options_array_item_value(a)->cmdlist;
|
|
if (cmdlist != NULL) {
|
|
new_item = cmdq_get_command(cmdlist, new_state);
|
|
if (item != NULL)
|
|
item = cmdq_insert_after(item, new_item);
|
|
else
|
|
item = cmdq_append(NULL, new_item);
|
|
}
|
|
a = options_array_next(a);
|
|
}
|
|
|
|
cmdq_free_state(new_state);
|
|
free(name);
|
|
}
|
|
|
|
/* Continue processing command queue. */
|
|
void
|
|
cmdq_continue(struct cmdq_item *item)
|
|
{
|
|
item->flags &= ~CMDQ_WAITING;
|
|
}
|
|
|
|
/* Remove an item. */
|
|
static void
|
|
cmdq_remove(struct cmdq_item *item)
|
|
{
|
|
if (item->client != NULL)
|
|
server_client_unref(item->client);
|
|
if (item->cmdlist != NULL)
|
|
cmd_list_free(item->cmdlist);
|
|
cmdq_free_state(item->state);
|
|
|
|
TAILQ_REMOVE(&item->queue->list, item, entry);
|
|
|
|
free(item->name);
|
|
free(item);
|
|
}
|
|
|
|
/* Remove all subsequent items that match this item's group. */
|
|
static void
|
|
cmdq_remove_group(struct cmdq_item *item)
|
|
{
|
|
struct cmdq_item *this, *next;
|
|
|
|
if (item->group == 0)
|
|
return;
|
|
this = TAILQ_NEXT(item, entry);
|
|
while (this != NULL) {
|
|
next = TAILQ_NEXT(this, entry);
|
|
if (this->group == item->group)
|
|
cmdq_remove(this);
|
|
this = next;
|
|
}
|
|
}
|
|
|
|
/* Empty command callback. */
|
|
static enum cmd_retval
|
|
cmdq_empty_command(__unused struct cmdq_item *item, __unused void *data)
|
|
{
|
|
return (CMD_RETURN_NORMAL);
|
|
}
|
|
|
|
/* Get a command for the command queue. */
|
|
struct cmdq_item *
|
|
cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state)
|
|
{
|
|
struct cmdq_item *item, *first = NULL, *last = NULL;
|
|
struct cmd *cmd;
|
|
const struct cmd_entry *entry;
|
|
int created = 0;
|
|
|
|
if ((cmd = cmd_list_first(cmdlist)) == NULL)
|
|
return (cmdq_get_callback(cmdq_empty_command, NULL));
|
|
|
|
if (state == NULL) {
|
|
state = cmdq_new_state(NULL, NULL, 0);
|
|
created = 1;
|
|
}
|
|
|
|
while (cmd != NULL) {
|
|
entry = cmd_get_entry(cmd);
|
|
|
|
item = xcalloc(1, sizeof *item);
|
|
xasprintf(&item->name, "[%s/%p]", entry->name, item);
|
|
item->type = CMDQ_COMMAND;
|
|
|
|
item->group = cmd_get_group(cmd);
|
|
item->state = cmdq_link_state(state);
|
|
|
|
item->cmdlist = cmdlist;
|
|
item->cmd = cmd;
|
|
|
|
cmdlist->references++;
|
|
log_debug("%s: %s group %u", __func__, item->name, item->group);
|
|
|
|
if (first == NULL)
|
|
first = item;
|
|
if (last != NULL)
|
|
last->next = item;
|
|
last = item;
|
|
|
|
cmd = cmd_list_next(cmd);
|
|
}
|
|
|
|
if (created)
|
|
cmdq_free_state(state);
|
|
return (first);
|
|
}
|
|
|
|
/* Fill in flag for a command. */
|
|
static enum cmd_retval
|
|
cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs,
|
|
const struct cmd_entry_flag *flag)
|
|
{
|
|
const char *value;
|
|
|
|
if (flag->flag == 0) {
|
|
cmd_find_from_client(fs, item->target_client, 0);
|
|
return (CMD_RETURN_NORMAL);
|
|
}
|
|
|
|
value = args_get(cmd_get_args(item->cmd), flag->flag);
|
|
if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) {
|
|
cmd_find_clear_state(fs, 0);
|
|
return (CMD_RETURN_ERROR);
|
|
}
|
|
return (CMD_RETURN_NORMAL);
|
|
}
|
|
|
|
/* Add message with command. */
|
|
static void
|
|
cmdq_add_message(struct cmdq_item *item)
|
|
{
|
|
struct client *c = item->client;
|
|
struct cmdq_state *state = item->state;
|
|
const char *key;
|
|
char *tmp;
|
|
uid_t uid;
|
|
struct passwd *pw;
|
|
char *user = NULL;
|
|
|
|
tmp = cmd_print(item->cmd);
|
|
if (c != NULL) {
|
|
uid = proc_get_peer_uid(c->peer);
|
|
if (uid != (uid_t)-1 && uid != getuid()) {
|
|
if ((pw = getpwuid(uid)) != NULL)
|
|
xasprintf(&user, "[%s]", pw->pw_name);
|
|
else
|
|
user = xstrdup("[unknown]");
|
|
} else
|
|
user = xstrdup("");
|
|
if (c->session != NULL && state->event.key != KEYC_NONE) {
|
|
key = key_string_lookup_key(state->event.key, 0);
|
|
server_add_message("%s%s key %s: %s", c->name, user,
|
|
key, tmp);
|
|
} else {
|
|
server_add_message("%s%s command: %s", c->name, user,
|
|
tmp);
|
|
}
|
|
free(user);
|
|
} else
|
|
server_add_message("command: %s", tmp);
|
|
free(tmp);
|
|
}
|
|
|
|
/* Fire command on command queue. */
|
|
static enum cmd_retval
|
|
cmdq_fire_command(struct cmdq_item *item)
|
|
{
|
|
const char *name = cmdq_name(item->client);
|
|
struct cmdq_state *state = item->state;
|
|
struct cmd *cmd = item->cmd;
|
|
struct args *args = cmd_get_args(cmd);
|
|
const struct cmd_entry *entry = cmd_get_entry(cmd);
|
|
struct client *tc, *saved = item->client;
|
|
enum cmd_retval retval;
|
|
struct cmd_find_state *fsp, fs;
|
|
int flags, quiet = 0;
|
|
char *tmp;
|
|
|
|
if (cfg_finished)
|
|
cmdq_add_message(item);
|
|
if (log_get_level() > 1) {
|
|
tmp = cmd_print(cmd);
|
|
log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp);
|
|
free(tmp);
|
|
}
|
|
|
|
flags = !!(state->flags & CMDQ_STATE_CONTROL);
|
|
cmdq_guard(item, "begin", flags);
|
|
|
|
if (item->client == NULL)
|
|
item->client = cmd_find_client(item, NULL, 1);
|
|
|
|
if (entry->flags & CMD_CLIENT_CANFAIL)
|
|
quiet = 1;
|
|
if (entry->flags & CMD_CLIENT_CFLAG) {
|
|
tc = cmd_find_client(item, args_get(args, 'c'), quiet);
|
|
if (tc == NULL && !quiet) {
|
|
retval = CMD_RETURN_ERROR;
|
|
goto out;
|
|
}
|
|
} else if (entry->flags & CMD_CLIENT_TFLAG) {
|
|
tc = cmd_find_client(item, args_get(args, 't'), quiet);
|
|
if (tc == NULL && !quiet) {
|
|
retval = CMD_RETURN_ERROR;
|
|
goto out;
|
|
}
|
|
} else
|
|
tc = cmd_find_client(item, NULL, 1);
|
|
item->target_client = tc;
|
|
|
|
retval = cmdq_find_flag(item, &item->source, &entry->source);
|
|
if (retval == CMD_RETURN_ERROR)
|
|
goto out;
|
|
retval = cmdq_find_flag(item, &item->target, &entry->target);
|
|
if (retval == CMD_RETURN_ERROR)
|
|
goto out;
|
|
|
|
retval = entry->exec(cmd, item);
|
|
if (retval == CMD_RETURN_ERROR)
|
|
goto out;
|
|
|
|
if (entry->flags & CMD_AFTERHOOK) {
|
|
if (cmd_find_valid_state(&item->target))
|
|
fsp = &item->target;
|
|
else if (cmd_find_valid_state(&item->state->current))
|
|
fsp = &item->state->current;
|
|
else if (cmd_find_from_client(&fs, item->client, 0) == 0)
|
|
fsp = &fs;
|
|
else
|
|
goto out;
|
|
cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name);
|
|
}
|
|
|
|
out:
|
|
item->client = saved;
|
|
if (retval == CMD_RETURN_ERROR) {
|
|
fsp = NULL;
|
|
if (cmd_find_valid_state(&item->target))
|
|
fsp = &item->target;
|
|
else if (cmd_find_valid_state(&item->state->current))
|
|
fsp = &item->state->current;
|
|
else if (cmd_find_from_client(&fs, item->client, 0) == 0)
|
|
fsp = &fs;
|
|
cmdq_insert_hook(fsp != NULL ? fsp->s : NULL, item, fsp,
|
|
"command-error");
|
|
cmdq_guard(item, "error", flags);
|
|
} else
|
|
cmdq_guard(item, "end", flags);
|
|
return (retval);
|
|
}
|
|
|
|
/* Get a callback for the command queue. */
|
|
struct cmdq_item *
|
|
cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
|
|
{
|
|
struct cmdq_item *item;
|
|
|
|
item = xcalloc(1, sizeof *item);
|
|
xasprintf(&item->name, "[%s/%p]", name, item);
|
|
item->type = CMDQ_CALLBACK;
|
|
|
|
item->group = 0;
|
|
item->state = cmdq_new_state(NULL, NULL, 0);
|
|
|
|
item->cb = cb;
|
|
item->data = data;
|
|
|
|
return (item);
|
|
}
|
|
|
|
/* Generic error callback. */
|
|
static enum cmd_retval
|
|
cmdq_error_callback(struct cmdq_item *item, void *data)
|
|
{
|
|
char *error = data;
|
|
|
|
cmdq_error(item, "%s", error);
|
|
free(error);
|
|
|
|
return (CMD_RETURN_NORMAL);
|
|
}
|
|
|
|
/* Get an error callback for the command queue. */
|
|
struct cmdq_item *
|
|
cmdq_get_error(const char *error)
|
|
{
|
|
return (cmdq_get_callback(cmdq_error_callback, xstrdup(error)));
|
|
}
|
|
|
|
/* Fire callback on callback queue. */
|
|
static enum cmd_retval
|
|
cmdq_fire_callback(struct cmdq_item *item)
|
|
{
|
|
return (item->cb(item, item->data));
|
|
}
|
|
|
|
/* Process next item on command queue. */
|
|
u_int
|
|
cmdq_next(struct client *c)
|
|
{
|
|
struct cmdq_list *queue = cmdq_get(c);
|
|
const char *name = cmdq_name(c);
|
|
struct cmdq_item *item;
|
|
enum cmd_retval retval;
|
|
u_int items = 0;
|
|
static u_int number;
|
|
|
|
if (TAILQ_EMPTY(&queue->list)) {
|
|
log_debug("%s %s: empty", __func__, name);
|
|
return (0);
|
|
}
|
|
if (TAILQ_FIRST(&queue->list)->flags & CMDQ_WAITING) {
|
|
log_debug("%s %s: waiting", __func__, name);
|
|
return (0);
|
|
}
|
|
|
|
log_debug("%s %s: enter", __func__, name);
|
|
for (;;) {
|
|
item = queue->item = TAILQ_FIRST(&queue->list);
|
|
if (item == NULL)
|
|
break;
|
|
log_debug("%s %s: %s (%d), flags %x", __func__, name,
|
|
item->name, item->type, item->flags);
|
|
|
|
/*
|
|
* Any item with the waiting flag set waits until an external
|
|
* event clears the flag (for example, a job - look at
|
|
* run-shell).
|
|
*/
|
|
if (item->flags & CMDQ_WAITING)
|
|
goto waiting;
|
|
|
|
/*
|
|
* Items are only fired once, once the fired flag is set, a
|
|
* waiting flag can only be cleared by an external event.
|
|
*/
|
|
if (~item->flags & CMDQ_FIRED) {
|
|
item->time = time(NULL);
|
|
item->number = ++number;
|
|
|
|
switch (item->type) {
|
|
case CMDQ_COMMAND:
|
|
retval = cmdq_fire_command(item);
|
|
|
|
/*
|
|
* If a command returns an error, remove any
|
|
* subsequent commands in the same group.
|
|
*/
|
|
if (retval == CMD_RETURN_ERROR)
|
|
cmdq_remove_group(item);
|
|
break;
|
|
case CMDQ_CALLBACK:
|
|
retval = cmdq_fire_callback(item);
|
|
break;
|
|
default:
|
|
retval = CMD_RETURN_ERROR;
|
|
break;
|
|
}
|
|
item->flags |= CMDQ_FIRED;
|
|
|
|
if (retval == CMD_RETURN_WAIT) {
|
|
item->flags |= CMDQ_WAITING;
|
|
goto waiting;
|
|
}
|
|
items++;
|
|
}
|
|
cmdq_remove(item);
|
|
}
|
|
queue->item = NULL;
|
|
|
|
log_debug("%s %s: exit (empty)", __func__, name);
|
|
return (items);
|
|
|
|
waiting:
|
|
log_debug("%s %s: exit (wait)", __func__, name);
|
|
return (items);
|
|
}
|
|
|
|
/* Get running item if any. */
|
|
struct cmdq_item *
|
|
cmdq_running(struct client *c)
|
|
{
|
|
struct cmdq_list *queue = cmdq_get(c);
|
|
|
|
if (queue->item == NULL)
|
|
return (NULL);
|
|
if (queue->item->flags & CMDQ_WAITING)
|
|
return (NULL);
|
|
return (queue->item);
|
|
}
|
|
|
|
/* Print a guard line. */
|
|
void
|
|
cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
|
|
{
|
|
struct client *c = item->client;
|
|
long t = item->time;
|
|
u_int number = item->number;
|
|
|
|
if (c != NULL && (c->flags & CLIENT_CONTROL))
|
|
control_write(c, "%%%s %ld %u %d", guard, t, number, flags);
|
|
}
|
|
|
|
/* Show message from command. */
|
|
void
|
|
cmdq_print_data(struct cmdq_item *item, int parse, struct evbuffer *evb)
|
|
{
|
|
server_client_print(item->client, parse, evb);
|
|
}
|
|
|
|
/* Show message from command. */
|
|
void
|
|
cmdq_print(struct cmdq_item *item, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
struct evbuffer *evb;
|
|
|
|
evb = evbuffer_new();
|
|
if (evb == NULL)
|
|
fatalx("out of memory");
|
|
|
|
va_start(ap, fmt);
|
|
evbuffer_add_vprintf(evb, fmt, ap);
|
|
va_end(ap);
|
|
|
|
cmdq_print_data(item, 0, evb);
|
|
evbuffer_free(evb);
|
|
}
|
|
|
|
/* Show error from command. */
|
|
void
|
|
cmdq_error(struct cmdq_item *item, const char *fmt, ...)
|
|
{
|
|
struct client *c = item->client;
|
|
struct cmd *cmd = item->cmd;
|
|
va_list ap;
|
|
char *msg, *tmp;
|
|
const char *file;
|
|
u_int line;
|
|
|
|
va_start(ap, fmt);
|
|
xvasprintf(&msg, fmt, ap);
|
|
va_end(ap);
|
|
|
|
log_debug("%s: %s", __func__, msg);
|
|
|
|
if (c == NULL) {
|
|
cmd_get_source(cmd, &file, &line);
|
|
cfg_add_cause("%s:%u: %s", file, line, msg);
|
|
} else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
|
|
server_add_message("%s message: %s", c->name, msg);
|
|
if (~c->flags & CLIENT_UTF8) {
|
|
tmp = msg;
|
|
msg = utf8_sanitize(tmp);
|
|
free(tmp);
|
|
}
|
|
if (c->flags & CLIENT_CONTROL)
|
|
control_write(c, "%s", msg);
|
|
else
|
|
file_error(c, "%s\n", msg);
|
|
c->retval = 1;
|
|
} else {
|
|
*msg = toupper((u_char) *msg);
|
|
status_message_set(c, -1, 1, 0, "%s", msg);
|
|
}
|
|
|
|
free(msg);
|
|
}
|