1*3a775927Snicm /* $OpenBSD: cmd-queue.c,v 1.118 2024/11/22 12:58:05 nicm Exp $ */ 2175d36ccSnicm 3175d36ccSnicm /* 498ca8272Snicm * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> 5175d36ccSnicm * 6175d36ccSnicm * Permission to use, copy, modify, and distribute this software for any 7175d36ccSnicm * purpose with or without fee is hereby granted, provided that the above 8175d36ccSnicm * copyright notice and this permission notice appear in all copies. 9175d36ccSnicm * 10175d36ccSnicm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11175d36ccSnicm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12175d36ccSnicm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13175d36ccSnicm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14175d36ccSnicm * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15175d36ccSnicm * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16175d36ccSnicm * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17175d36ccSnicm */ 18175d36ccSnicm 19175d36ccSnicm #include <sys/types.h> 20175d36ccSnicm 21175d36ccSnicm #include <ctype.h> 228ab000fcSnicm #include <pwd.h> 23175d36ccSnicm #include <stdlib.h> 24e048bb79Snicm #include <string.h> 256ba5a684Snicm #include <time.h> 268ab000fcSnicm #include <unistd.h> 27f260bbaaSnicm #include <vis.h> 28175d36ccSnicm 29175d36ccSnicm #include "tmux.h" 30175d36ccSnicm 319b123ae5Snicm /* Command queue flags. */ 329b123ae5Snicm #define CMDQ_FIRED 0x1 339b123ae5Snicm #define CMDQ_WAITING 0x2 349b123ae5Snicm 35040343aeSnicm /* Command queue item type. */ 36040343aeSnicm enum cmdq_type { 37040343aeSnicm CMDQ_COMMAND, 38040343aeSnicm CMDQ_CALLBACK, 39040343aeSnicm }; 40040343aeSnicm 41040343aeSnicm /* Command queue item. */ 42040343aeSnicm struct cmdq_item { 43040343aeSnicm char *name; 44040343aeSnicm struct cmdq_list *queue; 45040343aeSnicm struct cmdq_item *next; 46040343aeSnicm 47040343aeSnicm struct client *client; 48035dc73dSnicm struct client *target_client; 49040343aeSnicm 50040343aeSnicm enum cmdq_type type; 51040343aeSnicm u_int group; 52040343aeSnicm 53040343aeSnicm u_int number; 54040343aeSnicm time_t time; 55040343aeSnicm 56040343aeSnicm int flags; 57040343aeSnicm 58054f42acSnicm struct cmdq_state *state; 59040343aeSnicm struct cmd_find_state source; 60040343aeSnicm struct cmd_find_state target; 61040343aeSnicm 62040343aeSnicm struct cmd_list *cmdlist; 63040343aeSnicm struct cmd *cmd; 64040343aeSnicm 65040343aeSnicm cmdq_cb cb; 66040343aeSnicm void *data; 67040343aeSnicm 68040343aeSnicm TAILQ_ENTRY(cmdq_item) entry; 69040343aeSnicm }; 705c72fbecSnicm TAILQ_HEAD(cmdq_item_list, cmdq_item); 7176851227Snicm 72823b6d6dSnicm /* 73823b6d6dSnicm * Command queue state. This is the context for commands on the command queue. 74823b6d6dSnicm * It holds information about how the commands were fired (the key and flags), 75823b6d6dSnicm * any additional formats for the commands, and the current default target. 76823b6d6dSnicm * Multiple commands can share the same state and a command may update the 77823b6d6dSnicm * default target. 78823b6d6dSnicm */ 79823b6d6dSnicm struct cmdq_state { 80823b6d6dSnicm int references; 81823b6d6dSnicm int flags; 82823b6d6dSnicm 83823b6d6dSnicm struct format_tree *formats; 84823b6d6dSnicm 85823b6d6dSnicm struct key_event event; 86823b6d6dSnicm struct cmd_find_state current; 87823b6d6dSnicm }; 88823b6d6dSnicm 895c72fbecSnicm /* Command queue. */ 905c72fbecSnicm struct cmdq_list { 915c72fbecSnicm struct cmdq_item *item; 925c72fbecSnicm struct cmdq_item_list list; 935c72fbecSnicm }; 945c72fbecSnicm 95765b9a58Snicm /* Get command queue name. */ 96765b9a58Snicm static const char * 97765b9a58Snicm cmdq_name(struct client *c) 98765b9a58Snicm { 996b2831afSnicm static char s[256]; 100765b9a58Snicm 101765b9a58Snicm if (c == NULL) 102765b9a58Snicm return ("<global>"); 1039a3a0de7Snicm if (c->name != NULL) 1046b2831afSnicm xsnprintf(s, sizeof s, "<%s>", c->name); 1059a3a0de7Snicm else 1069a3a0de7Snicm xsnprintf(s, sizeof s, "<%p>", c); 107765b9a58Snicm return (s); 108765b9a58Snicm } 109765b9a58Snicm 110765b9a58Snicm /* Get command queue from client. */ 11168e0a7f2Snicm static struct cmdq_list * 112765b9a58Snicm cmdq_get(struct client *c) 113765b9a58Snicm { 114040343aeSnicm static struct cmdq_list *global_queue; 115040343aeSnicm 116040343aeSnicm if (c == NULL) { 117040343aeSnicm if (global_queue == NULL) 118040343aeSnicm global_queue = cmdq_new(); 119040343aeSnicm return (global_queue); 120040343aeSnicm } 121040343aeSnicm return (c->queue); 122040343aeSnicm } 123040343aeSnicm 124040343aeSnicm /* Create a queue. */ 125040343aeSnicm struct cmdq_list * 126040343aeSnicm cmdq_new(void) 127040343aeSnicm { 128040343aeSnicm struct cmdq_list *queue; 129040343aeSnicm 130040343aeSnicm queue = xcalloc(1, sizeof *queue); 1315c72fbecSnicm TAILQ_INIT (&queue->list); 132040343aeSnicm return (queue); 133040343aeSnicm } 134040343aeSnicm 135040343aeSnicm /* Free a queue. */ 136040343aeSnicm void 137040343aeSnicm cmdq_free(struct cmdq_list *queue) 138040343aeSnicm { 1395c72fbecSnicm if (!TAILQ_EMPTY(&queue->list)) 140040343aeSnicm fatalx("queue not empty"); 141040343aeSnicm free(queue); 142040343aeSnicm } 143040343aeSnicm 144040343aeSnicm /* Get item name. */ 145040343aeSnicm const char * 146040343aeSnicm cmdq_get_name(struct cmdq_item *item) 147040343aeSnicm { 148040343aeSnicm return (item->name); 149040343aeSnicm } 150040343aeSnicm 151040343aeSnicm /* Get item client. */ 152040343aeSnicm struct client * 153040343aeSnicm cmdq_get_client(struct cmdq_item *item) 154040343aeSnicm { 155040343aeSnicm return (item->client); 156040343aeSnicm } 157040343aeSnicm 158035dc73dSnicm /* Get item target client. */ 159035dc73dSnicm struct client * 160035dc73dSnicm cmdq_get_target_client(struct cmdq_item *item) 161035dc73dSnicm { 162035dc73dSnicm return (item->target_client); 163035dc73dSnicm } 164035dc73dSnicm 165c1e0bdabSnicm /* Get item state. */ 166c1e0bdabSnicm struct cmdq_state * 167c1e0bdabSnicm cmdq_get_state(struct cmdq_item *item) 168c1e0bdabSnicm { 169c1e0bdabSnicm return (item->state); 170c1e0bdabSnicm } 171c1e0bdabSnicm 172040343aeSnicm /* Get item target. */ 173040343aeSnicm struct cmd_find_state * 174040343aeSnicm cmdq_get_target(struct cmdq_item *item) 175040343aeSnicm { 176040343aeSnicm return (&item->target); 177040343aeSnicm } 178040343aeSnicm 179040343aeSnicm /* Get item source. */ 180040343aeSnicm struct cmd_find_state * 181040343aeSnicm cmdq_get_source(struct cmdq_item *item) 182040343aeSnicm { 183040343aeSnicm return (&item->source); 184040343aeSnicm } 185040343aeSnicm 186823b6d6dSnicm /* Get state event. */ 187823b6d6dSnicm struct key_event * 188823b6d6dSnicm cmdq_get_event(struct cmdq_item *item) 189040343aeSnicm { 190823b6d6dSnicm return (&item->state->event); 191823b6d6dSnicm } 192823b6d6dSnicm 193823b6d6dSnicm /* Get state current target. */ 194823b6d6dSnicm struct cmd_find_state * 195823b6d6dSnicm cmdq_get_current(struct cmdq_item *item) 196823b6d6dSnicm { 197823b6d6dSnicm return (&item->state->current); 198823b6d6dSnicm } 199823b6d6dSnicm 200823b6d6dSnicm /* Get state flags. */ 201823b6d6dSnicm int 202823b6d6dSnicm cmdq_get_flags(struct cmdq_item *item) 203823b6d6dSnicm { 204823b6d6dSnicm return (item->state->flags); 205040343aeSnicm } 206040343aeSnicm 207c1e0bdabSnicm /* Create a new state. */ 208c1e0bdabSnicm struct cmdq_state * 209c1e0bdabSnicm cmdq_new_state(struct cmd_find_state *current, struct key_event *event, 210c1e0bdabSnicm int flags) 211c1e0bdabSnicm { 212c1e0bdabSnicm struct cmdq_state *state; 213c1e0bdabSnicm 214c1e0bdabSnicm state = xcalloc(1, sizeof *state); 215c1e0bdabSnicm state->references = 1; 216c1e0bdabSnicm state->flags = flags; 217c1e0bdabSnicm 218c1e0bdabSnicm if (event != NULL) 219c1e0bdabSnicm memcpy(&state->event, event, sizeof state->event); 220c1e0bdabSnicm else 221c1e0bdabSnicm state->event.key = KEYC_NONE; 222c1e0bdabSnicm if (current != NULL && cmd_find_valid_state(current)) 223c1e0bdabSnicm cmd_find_copy_state(&state->current, current); 224c1e0bdabSnicm else 225c1e0bdabSnicm cmd_find_clear_state(&state->current, 0); 226c1e0bdabSnicm 227c1e0bdabSnicm return (state); 228c1e0bdabSnicm } 229c1e0bdabSnicm 230c1e0bdabSnicm /* Add a reference to a state. */ 231c1e0bdabSnicm struct cmdq_state * 232c1e0bdabSnicm cmdq_link_state(struct cmdq_state *state) 233c1e0bdabSnicm { 234c1e0bdabSnicm state->references++; 235c1e0bdabSnicm return (state); 236c1e0bdabSnicm } 237c1e0bdabSnicm 238c1e0bdabSnicm /* Make a copy of a state. */ 239c1e0bdabSnicm struct cmdq_state * 24004d313c5Snicm cmdq_copy_state(struct cmdq_state *state, struct cmd_find_state *current) 241c1e0bdabSnicm { 24204d313c5Snicm if (current != NULL) 24304d313c5Snicm return (cmdq_new_state(current, &state->event, state->flags)); 244c1e0bdabSnicm return (cmdq_new_state(&state->current, &state->event, state->flags)); 245c1e0bdabSnicm } 246c1e0bdabSnicm 247c1e0bdabSnicm /* Free a state. */ 248c1e0bdabSnicm void 249c1e0bdabSnicm cmdq_free_state(struct cmdq_state *state) 250c1e0bdabSnicm { 251928e56dcSnicm if (--state->references != 0) 252928e56dcSnicm return; 253928e56dcSnicm 254928e56dcSnicm if (state->formats != NULL) 255928e56dcSnicm format_free(state->formats); 256c1e0bdabSnicm free(state); 257c1e0bdabSnicm } 258c1e0bdabSnicm 259c1e0bdabSnicm /* Add a format to command queue. */ 260c1e0bdabSnicm void 261c1e0bdabSnicm cmdq_add_format(struct cmdq_state *state, const char *key, const char *fmt, ...) 262c1e0bdabSnicm { 263c1e0bdabSnicm va_list ap; 264c1e0bdabSnicm char *value; 265c1e0bdabSnicm 266c1e0bdabSnicm va_start(ap, fmt); 267c1e0bdabSnicm xvasprintf(&value, fmt, ap); 268c1e0bdabSnicm va_end(ap); 269c1e0bdabSnicm 270c1e0bdabSnicm if (state->formats == NULL) 271c1e0bdabSnicm state->formats = format_create(NULL, NULL, FORMAT_NONE, 0); 272c1e0bdabSnicm format_add(state->formats, key, "%s", value); 273c1e0bdabSnicm 274c1e0bdabSnicm free(value); 275c1e0bdabSnicm } 276c1e0bdabSnicm 2779eee7735Snicm /* Add formats to command queue. */ 2789eee7735Snicm void 2799eee7735Snicm cmdq_add_formats(struct cmdq_state *state, struct format_tree *ft) 2809eee7735Snicm { 2819eee7735Snicm if (state->formats == NULL) 2829eee7735Snicm state->formats = format_create(NULL, NULL, FORMAT_NONE, 0); 2839eee7735Snicm format_merge(state->formats, ft); 2849eee7735Snicm } 2859eee7735Snicm 286040343aeSnicm /* Merge formats from item. */ 287040343aeSnicm void 288040343aeSnicm cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft) 289040343aeSnicm { 290040343aeSnicm const struct cmd_entry *entry; 291040343aeSnicm 292040343aeSnicm if (item->cmd != NULL) { 293040343aeSnicm entry = cmd_get_entry(item->cmd); 294040343aeSnicm format_add(ft, "command", "%s", entry->name); 295040343aeSnicm } 296054f42acSnicm if (item->state->formats != NULL) 297054f42acSnicm format_merge(ft, item->state->formats); 298765b9a58Snicm } 299765b9a58Snicm 300765b9a58Snicm /* Append an item. */ 30195cd24f8Snicm struct cmdq_item * 30268e0a7f2Snicm cmdq_append(struct client *c, struct cmdq_item *item) 303765b9a58Snicm { 30468e0a7f2Snicm struct cmdq_list *queue = cmdq_get(c); 30568e0a7f2Snicm struct cmdq_item *next; 306765b9a58Snicm 307765b9a58Snicm do { 30868e0a7f2Snicm next = item->next; 30968e0a7f2Snicm item->next = NULL; 310765b9a58Snicm 311765b9a58Snicm if (c != NULL) 312765b9a58Snicm c->references++; 31368e0a7f2Snicm item->client = c; 314765b9a58Snicm 31568e0a7f2Snicm item->queue = queue; 3165c72fbecSnicm TAILQ_INSERT_TAIL(&queue->list, item, entry); 3175edaef27Snicm log_debug("%s %s: %s", __func__, cmdq_name(c), item->name); 318765b9a58Snicm 31968e0a7f2Snicm item = next; 32068e0a7f2Snicm } while (item != NULL); 3215c72fbecSnicm return (TAILQ_LAST(&queue->list, cmdq_item_list)); 322765b9a58Snicm } 323765b9a58Snicm 324765b9a58Snicm /* Insert an item. */ 32595cd24f8Snicm struct cmdq_item * 32668e0a7f2Snicm cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) 327765b9a58Snicm { 328765b9a58Snicm struct client *c = after->client; 32968e0a7f2Snicm struct cmdq_list *queue = after->queue; 33068e0a7f2Snicm struct cmdq_item *next; 331765b9a58Snicm 332765b9a58Snicm do { 33368e0a7f2Snicm next = item->next; 3345edaef27Snicm item->next = after->next; 3355edaef27Snicm after->next = item; 336765b9a58Snicm 337765b9a58Snicm if (c != NULL) 338765b9a58Snicm c->references++; 33968e0a7f2Snicm item->client = c; 340765b9a58Snicm 34168e0a7f2Snicm item->queue = queue; 3425c72fbecSnicm TAILQ_INSERT_AFTER(&queue->list, after, item, entry); 3435edaef27Snicm log_debug("%s %s: %s after %s", __func__, cmdq_name(c), 3445edaef27Snicm item->name, after->name); 345765b9a58Snicm 346ef3f1b12Snicm after = item; 34768e0a7f2Snicm item = next; 34868e0a7f2Snicm } while (item != NULL); 34995cd24f8Snicm return (after); 350765b9a58Snicm } 351765b9a58Snicm 352844b9093Snicm /* Insert a hook. */ 353844b9093Snicm void 354844b9093Snicm cmdq_insert_hook(struct session *s, struct cmdq_item *item, 355c1e0bdabSnicm struct cmd_find_state *current, const char *fmt, ...) 356844b9093Snicm { 357c1e0bdabSnicm struct cmdq_state *state = item->state; 358ef07bfa2Snicm struct cmd *cmd = item->cmd; 359ef07bfa2Snicm struct args *args = cmd_get_args(cmd); 36005b80794Snicm struct args_entry *ae; 36105b80794Snicm struct args_value *av; 362844b9093Snicm struct options *oo; 363844b9093Snicm va_list ap; 364ef07bfa2Snicm char *name, tmp[32], flag, *arguments; 3651693b10bSnicm u_int i; 366ef07bfa2Snicm const char *value; 367844b9093Snicm struct cmdq_item *new_item; 368c1e0bdabSnicm struct cmdq_state *new_state; 369844b9093Snicm struct options_entry *o; 370844b9093Snicm struct options_array_item *a; 371844b9093Snicm struct cmd_list *cmdlist; 372844b9093Snicm 373054f42acSnicm if (item->state->flags & CMDQ_STATE_NOHOOKS) 374844b9093Snicm return; 375844b9093Snicm if (s == NULL) 376844b9093Snicm oo = global_s_options; 377844b9093Snicm else 378844b9093Snicm oo = s->options; 379844b9093Snicm 380844b9093Snicm va_start(ap, fmt); 381844b9093Snicm xvasprintf(&name, fmt, ap); 382844b9093Snicm va_end(ap); 383844b9093Snicm 384844b9093Snicm o = options_get(oo, name); 385844b9093Snicm if (o == NULL) { 386844b9093Snicm free(name); 387844b9093Snicm return; 388844b9093Snicm } 389844b9093Snicm log_debug("running hook %s (parent %p)", name, item); 390844b9093Snicm 391c1e0bdabSnicm /* 392c1e0bdabSnicm * The hooks get a new state because they should not update the current 393c1e0bdabSnicm * target or formats for any subsequent commands. 394c1e0bdabSnicm */ 395c1e0bdabSnicm new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS); 396c1e0bdabSnicm cmdq_add_format(new_state, "hook", "%s", name); 397c1e0bdabSnicm 398ef07bfa2Snicm arguments = args_print(args); 399ef07bfa2Snicm cmdq_add_format(new_state, "hook_arguments", "%s", arguments); 400ef07bfa2Snicm free(arguments); 401ef07bfa2Snicm 4021693b10bSnicm for (i = 0; i < args_count(args); i++) { 403ef07bfa2Snicm xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i); 4041693b10bSnicm cmdq_add_format(new_state, tmp, "%s", args_string(args, i)); 405ef07bfa2Snicm } 40605b80794Snicm flag = args_first(args, &ae); 407ef07bfa2Snicm while (flag != 0) { 408ef07bfa2Snicm value = args_get(args, flag); 409ef07bfa2Snicm if (value == NULL) { 410ef07bfa2Snicm xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); 411ef07bfa2Snicm cmdq_add_format(new_state, tmp, "1"); 412ef07bfa2Snicm } else { 413ef07bfa2Snicm xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); 414ef07bfa2Snicm cmdq_add_format(new_state, tmp, "%s", value); 415ef07bfa2Snicm } 416ef07bfa2Snicm 417ef07bfa2Snicm i = 0; 41805b80794Snicm av = args_first_value(args, flag); 41905b80794Snicm while (av != NULL) { 420ef07bfa2Snicm xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i); 421825f884aSnicm cmdq_add_format(new_state, tmp, "%s", av->string); 422ef07bfa2Snicm i++; 42305b80794Snicm av = args_next_value(av); 424ef07bfa2Snicm } 425ef07bfa2Snicm 42605b80794Snicm flag = args_next(&ae); 427ef07bfa2Snicm } 428ef07bfa2Snicm 429844b9093Snicm a = options_array_first(o); 430844b9093Snicm while (a != NULL) { 431844b9093Snicm cmdlist = options_array_item_value(a)->cmdlist; 432c1e0bdabSnicm if (cmdlist != NULL) { 433c1e0bdabSnicm new_item = cmdq_get_command(cmdlist, new_state); 43495cd24f8Snicm if (item != NULL) 43595cd24f8Snicm item = cmdq_insert_after(item, new_item); 43695cd24f8Snicm else 43795cd24f8Snicm item = cmdq_append(NULL, new_item); 438c1e0bdabSnicm } 439844b9093Snicm a = options_array_next(a); 440844b9093Snicm } 441844b9093Snicm 442c1e0bdabSnicm cmdq_free_state(new_state); 443844b9093Snicm free(name); 444844b9093Snicm } 445844b9093Snicm 446780ca620Snicm /* Continue processing command queue. */ 447780ca620Snicm void 448780ca620Snicm cmdq_continue(struct cmdq_item *item) 449780ca620Snicm { 450780ca620Snicm item->flags &= ~CMDQ_WAITING; 451780ca620Snicm } 452780ca620Snicm 453765b9a58Snicm /* Remove an item. */ 454765b9a58Snicm static void 45568e0a7f2Snicm cmdq_remove(struct cmdq_item *item) 456765b9a58Snicm { 45768e0a7f2Snicm if (item->client != NULL) 45868e0a7f2Snicm server_client_unref(item->client); 459c26c4f79Snicm if (item->cmdlist != NULL) 46068e0a7f2Snicm cmd_list_free(item->cmdlist); 461c1e0bdabSnicm cmdq_free_state(item->state); 462765b9a58Snicm 4635c72fbecSnicm TAILQ_REMOVE(&item->queue->list, item, entry); 464d6aa0047Snicm 4655edaef27Snicm free(item->name); 46668e0a7f2Snicm free(item); 467765b9a58Snicm } 468765b9a58Snicm 469765b9a58Snicm /* Remove all subsequent items that match this item's group. */ 470765b9a58Snicm static void 47168e0a7f2Snicm cmdq_remove_group(struct cmdq_item *item) 472765b9a58Snicm { 47368e0a7f2Snicm struct cmdq_item *this, *next; 474765b9a58Snicm 475d8804b0bSnicm if (item->group == 0) 476d8804b0bSnicm return; 47768e0a7f2Snicm this = TAILQ_NEXT(item, entry); 478765b9a58Snicm while (this != NULL) { 479765b9a58Snicm next = TAILQ_NEXT(this, entry); 48068e0a7f2Snicm if (this->group == item->group) 481765b9a58Snicm cmdq_remove(this); 482765b9a58Snicm this = next; 483765b9a58Snicm } 484765b9a58Snicm } 485765b9a58Snicm 486afdf680fSnicm /* Empty command callback. */ 487afdf680fSnicm static enum cmd_retval 488afdf680fSnicm cmdq_empty_command(__unused struct cmdq_item *item, __unused void *data) 489afdf680fSnicm { 490afdf680fSnicm return (CMD_RETURN_NORMAL); 491afdf680fSnicm } 492afdf680fSnicm 493765b9a58Snicm /* Get a command for the command queue. */ 49468e0a7f2Snicm struct cmdq_item * 495c1e0bdabSnicm cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state) 496765b9a58Snicm { 49768e0a7f2Snicm struct cmdq_item *item, *first = NULL, *last = NULL; 498765b9a58Snicm struct cmd *cmd; 49990d7ba38Snicm const struct cmd_entry *entry; 500c1e0bdabSnicm int created = 0; 501bebc73f1Snicm 502afdf680fSnicm if ((cmd = cmd_list_first(cmdlist)) == NULL) 503afdf680fSnicm return (cmdq_get_callback(cmdq_empty_command, NULL)); 504afdf680fSnicm 505c1e0bdabSnicm if (state == NULL) { 506c1e0bdabSnicm state = cmdq_new_state(NULL, NULL, 0); 507c1e0bdabSnicm created = 1; 508c1e0bdabSnicm } 509c1e0bdabSnicm 51090d7ba38Snicm while (cmd != NULL) { 51190d7ba38Snicm entry = cmd_get_entry(cmd); 512765b9a58Snicm 51368e0a7f2Snicm item = xcalloc(1, sizeof *item); 51490d7ba38Snicm xasprintf(&item->name, "[%s/%p]", entry->name, item); 51568e0a7f2Snicm item->type = CMDQ_COMMAND; 516765b9a58Snicm 517c1e0bdabSnicm item->group = cmd_get_group(cmd); 518c1e0bdabSnicm item->state = cmdq_link_state(state); 519c1e0bdabSnicm 52068e0a7f2Snicm item->cmdlist = cmdlist; 52168e0a7f2Snicm item->cmd = cmd; 522765b9a58Snicm 523765b9a58Snicm cmdlist->references++; 524c1e0bdabSnicm log_debug("%s: %s group %u", __func__, item->name, item->group); 525765b9a58Snicm 526765b9a58Snicm if (first == NULL) 52768e0a7f2Snicm first = item; 528765b9a58Snicm if (last != NULL) 52968e0a7f2Snicm last->next = item; 53068e0a7f2Snicm last = item; 53190d7ba38Snicm 532c1e0bdabSnicm cmd = cmd_list_next(cmd); 533765b9a58Snicm } 534c1e0bdabSnicm 535c1e0bdabSnicm if (created) 536c1e0bdabSnicm cmdq_free_state(state); 537765b9a58Snicm return (first); 538765b9a58Snicm } 539765b9a58Snicm 540bf0d297eSnicm /* Fill in flag for a command. */ 541bf0d297eSnicm static enum cmd_retval 542bf0d297eSnicm cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, 543bf0d297eSnicm const struct cmd_entry_flag *flag) 544bf0d297eSnicm { 545bf0d297eSnicm const char *value; 546bf0d297eSnicm 547bf0d297eSnicm if (flag->flag == 0) { 54894adf770Snicm cmd_find_from_client(fs, item->target_client, 0); 549bf0d297eSnicm return (CMD_RETURN_NORMAL); 550bf0d297eSnicm } 551bf0d297eSnicm 55290d7ba38Snicm value = args_get(cmd_get_args(item->cmd), flag->flag); 553bf0d297eSnicm if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) { 554bf0d297eSnicm cmd_find_clear_state(fs, 0); 555bf0d297eSnicm return (CMD_RETURN_ERROR); 556bf0d297eSnicm } 557bf0d297eSnicm return (CMD_RETURN_NORMAL); 558bf0d297eSnicm } 559bf0d297eSnicm 5600687354cSnicm /* Add message with command. */ 5610687354cSnicm static void 5620687354cSnicm cmdq_add_message(struct cmdq_item *item) 5630687354cSnicm { 5640687354cSnicm struct client *c = item->client; 5650687354cSnicm struct cmdq_state *state = item->state; 5668ab000fcSnicm const char *key; 5670687354cSnicm char *tmp; 5688ab000fcSnicm uid_t uid; 5698ab000fcSnicm struct passwd *pw; 5708ab000fcSnicm char *user = NULL; 5710687354cSnicm 5720687354cSnicm tmp = cmd_print(item->cmd); 5730687354cSnicm if (c != NULL) { 5748ab000fcSnicm uid = proc_get_peer_uid(c->peer); 5758ab000fcSnicm if (uid != (uid_t)-1 && uid != getuid()) { 5768ab000fcSnicm if ((pw = getpwuid(uid)) != NULL) 5778ab000fcSnicm xasprintf(&user, "[%s]", pw->pw_name); 5788ab000fcSnicm else 5798ab000fcSnicm user = xstrdup("[unknown]"); 5808ab000fcSnicm } else 5818ab000fcSnicm user = xstrdup(""); 5820687354cSnicm if (c->session != NULL && state->event.key != KEYC_NONE) { 5835416581eSnicm key = key_string_lookup_key(state->event.key, 0); 5848ab000fcSnicm server_add_message("%s%s key %s: %s", c->name, user, 5858ab000fcSnicm key, tmp); 5868ab000fcSnicm } else { 5878ab000fcSnicm server_add_message("%s%s command: %s", c->name, user, 5888ab000fcSnicm tmp); 5898ab000fcSnicm } 5908ab000fcSnicm free(user); 5910687354cSnicm } else 5920687354cSnicm server_add_message("command: %s", tmp); 5930687354cSnicm free(tmp); 5940687354cSnicm } 5950687354cSnicm 596765b9a58Snicm /* Fire command on command queue. */ 597765b9a58Snicm static enum cmd_retval 59868e0a7f2Snicm cmdq_fire_command(struct cmdq_item *item) 599765b9a58Snicm { 600035dc73dSnicm const char *name = cmdq_name(item->client); 601054f42acSnicm struct cmdq_state *state = item->state; 60268e0a7f2Snicm struct cmd *cmd = item->cmd; 603035dc73dSnicm struct args *args = cmd_get_args(cmd); 60490d7ba38Snicm const struct cmd_entry *entry = cmd_get_entry(cmd); 605035dc73dSnicm struct client *tc, *saved = item->client; 606765b9a58Snicm enum cmd_retval retval; 607765b9a58Snicm struct cmd_find_state *fsp, fs; 608035dc73dSnicm int flags, quiet = 0; 60947394861Snicm char *tmp; 61047394861Snicm 6110687354cSnicm if (cfg_finished) 6120687354cSnicm cmdq_add_message(item); 61347394861Snicm if (log_get_level() > 1) { 61447394861Snicm tmp = cmd_print(cmd); 61547394861Snicm log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp); 61647394861Snicm free(tmp); 61747394861Snicm } 618765b9a58Snicm 619054f42acSnicm flags = !!(state->flags & CMDQ_STATE_CONTROL); 62068e0a7f2Snicm cmdq_guard(item, "begin", flags); 621765b9a58Snicm 622d655f083Snicm if (item->client == NULL) 623d655f083Snicm item->client = cmd_find_client(item, NULL, 1); 624035dc73dSnicm 625035dc73dSnicm if (entry->flags & CMD_CLIENT_CANFAIL) 626035dc73dSnicm quiet = 1; 627035dc73dSnicm if (entry->flags & CMD_CLIENT_CFLAG) { 628035dc73dSnicm tc = cmd_find_client(item, args_get(args, 'c'), quiet); 629035dc73dSnicm if (tc == NULL && !quiet) { 630035dc73dSnicm retval = CMD_RETURN_ERROR; 631035dc73dSnicm goto out; 632035dc73dSnicm } 633035dc73dSnicm } else if (entry->flags & CMD_CLIENT_TFLAG) { 634035dc73dSnicm tc = cmd_find_client(item, args_get(args, 't'), quiet); 635035dc73dSnicm if (tc == NULL && !quiet) { 636035dc73dSnicm retval = CMD_RETURN_ERROR; 637035dc73dSnicm goto out; 638035dc73dSnicm } 639035dc73dSnicm } else 640035dc73dSnicm tc = cmd_find_client(item, NULL, 1); 641035dc73dSnicm item->target_client = tc; 642035dc73dSnicm 643bf0d297eSnicm retval = cmdq_find_flag(item, &item->source, &entry->source); 644bf0d297eSnicm if (retval == CMD_RETURN_ERROR) 645765b9a58Snicm goto out; 646bf0d297eSnicm retval = cmdq_find_flag(item, &item->target, &entry->target); 647765b9a58Snicm if (retval == CMD_RETURN_ERROR) 648765b9a58Snicm goto out; 649765b9a58Snicm 650bf0d297eSnicm retval = entry->exec(cmd, item); 651bf0d297eSnicm if (retval == CMD_RETURN_ERROR) 652bf0d297eSnicm goto out; 653bf0d297eSnicm 654bf0d297eSnicm if (entry->flags & CMD_AFTERHOOK) { 655bf0d297eSnicm if (cmd_find_valid_state(&item->target)) 656bf0d297eSnicm fsp = &item->target; 657054f42acSnicm else if (cmd_find_valid_state(&item->state->current)) 658054f42acSnicm fsp = &item->state->current; 6590772530eSnicm else if (cmd_find_from_client(&fs, item->client, 0) == 0) 660765b9a58Snicm fsp = &fs; 66193e732aaSnicm else 66293e732aaSnicm goto out; 663844b9093Snicm cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name); 664765b9a58Snicm } 665765b9a58Snicm 666765b9a58Snicm out: 667035dc73dSnicm item->client = saved; 6681adaafb7Snicm if (retval == CMD_RETURN_ERROR) { 6691adaafb7Snicm fsp = NULL; 6701adaafb7Snicm if (cmd_find_valid_state(&item->target)) 6711adaafb7Snicm fsp = &item->target; 6721adaafb7Snicm else if (cmd_find_valid_state(&item->state->current)) 6731adaafb7Snicm fsp = &item->state->current; 6741adaafb7Snicm else if (cmd_find_from_client(&fs, item->client, 0) == 0) 6751adaafb7Snicm fsp = &fs; 6761adaafb7Snicm cmdq_insert_hook(fsp != NULL ? fsp->s : NULL, item, fsp, 6771adaafb7Snicm "command-error"); 67868e0a7f2Snicm cmdq_guard(item, "error", flags); 6791adaafb7Snicm } else 68068e0a7f2Snicm cmdq_guard(item, "end", flags); 681765b9a58Snicm return (retval); 682765b9a58Snicm } 683765b9a58Snicm 684765b9a58Snicm /* Get a callback for the command queue. */ 68568e0a7f2Snicm struct cmdq_item * 686d6aa0047Snicm cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) 687175d36ccSnicm { 68868e0a7f2Snicm struct cmdq_item *item; 689175d36ccSnicm 69068e0a7f2Snicm item = xcalloc(1, sizeof *item); 6915edaef27Snicm xasprintf(&item->name, "[%s/%p]", name, item); 69268e0a7f2Snicm item->type = CMDQ_CALLBACK; 693c1e0bdabSnicm 69468e0a7f2Snicm item->group = 0; 695c1e0bdabSnicm item->state = cmdq_new_state(NULL, NULL, 0); 696175d36ccSnicm 69768e0a7f2Snicm item->cb = cb; 69868e0a7f2Snicm item->data = data; 6994fc586aaSnicm 70068e0a7f2Snicm return (item); 701175d36ccSnicm } 702175d36ccSnicm 7030f3d1a91Snicm /* Generic error callback. */ 7040f3d1a91Snicm static enum cmd_retval 7050f3d1a91Snicm cmdq_error_callback(struct cmdq_item *item, void *data) 7060f3d1a91Snicm { 7070f3d1a91Snicm char *error = data; 7080f3d1a91Snicm 7090f3d1a91Snicm cmdq_error(item, "%s", error); 7100f3d1a91Snicm free(error); 7110f3d1a91Snicm 7120f3d1a91Snicm return (CMD_RETURN_NORMAL); 7130f3d1a91Snicm } 7140f3d1a91Snicm 7150f3d1a91Snicm /* Get an error callback for the command queue. */ 7160f3d1a91Snicm struct cmdq_item * 7170f3d1a91Snicm cmdq_get_error(const char *error) 7180f3d1a91Snicm { 7190f3d1a91Snicm return (cmdq_get_callback(cmdq_error_callback, xstrdup(error))); 7200f3d1a91Snicm } 7210f3d1a91Snicm 722765b9a58Snicm /* Fire callback on callback queue. */ 723765b9a58Snicm static enum cmd_retval 72468e0a7f2Snicm cmdq_fire_callback(struct cmdq_item *item) 725175d36ccSnicm { 72668e0a7f2Snicm return (item->cb(item, item->data)); 727765b9a58Snicm } 7289a67a5acSnicm 729765b9a58Snicm /* Process next item on command queue. */ 730765b9a58Snicm u_int 731765b9a58Snicm cmdq_next(struct client *c) 732765b9a58Snicm { 73368e0a7f2Snicm struct cmdq_list *queue = cmdq_get(c); 734765b9a58Snicm const char *name = cmdq_name(c); 73568e0a7f2Snicm struct cmdq_item *item; 736765b9a58Snicm enum cmd_retval retval; 737765b9a58Snicm u_int items = 0; 738765b9a58Snicm static u_int number; 739765b9a58Snicm 7405c72fbecSnicm if (TAILQ_EMPTY(&queue->list)) { 741765b9a58Snicm log_debug("%s %s: empty", __func__, name); 742765b9a58Snicm return (0); 743765b9a58Snicm } 7445c72fbecSnicm if (TAILQ_FIRST(&queue->list)->flags & CMDQ_WAITING) { 745765b9a58Snicm log_debug("%s %s: waiting", __func__, name); 746722808c8Snicm return (0); 747722808c8Snicm } 748175d36ccSnicm 749765b9a58Snicm log_debug("%s %s: enter", __func__, name); 750765b9a58Snicm for (;;) { 7515c72fbecSnicm item = queue->item = TAILQ_FIRST(&queue->list); 75268e0a7f2Snicm if (item == NULL) 753765b9a58Snicm break; 754d6aa0047Snicm log_debug("%s %s: %s (%d), flags %x", __func__, name, 755d6aa0047Snicm item->name, item->type, item->flags); 756765b9a58Snicm 757765b9a58Snicm /* 758765b9a58Snicm * Any item with the waiting flag set waits until an external 759765b9a58Snicm * event clears the flag (for example, a job - look at 760765b9a58Snicm * run-shell). 761765b9a58Snicm */ 76268e0a7f2Snicm if (item->flags & CMDQ_WAITING) 763765b9a58Snicm goto waiting; 764765b9a58Snicm 765765b9a58Snicm /* 766765b9a58Snicm * Items are only fired once, once the fired flag is set, a 767765b9a58Snicm * waiting flag can only be cleared by an external event. 768765b9a58Snicm */ 76968e0a7f2Snicm if (~item->flags & CMDQ_FIRED) { 77068e0a7f2Snicm item->time = time(NULL); 77168e0a7f2Snicm item->number = ++number; 772765b9a58Snicm 77333a044c7Snicm switch (item->type) { 77468e0a7f2Snicm case CMDQ_COMMAND: 77568e0a7f2Snicm retval = cmdq_fire_command(item); 776765b9a58Snicm 777765b9a58Snicm /* 778765b9a58Snicm * If a command returns an error, remove any 779765b9a58Snicm * subsequent commands in the same group. 780765b9a58Snicm */ 781765b9a58Snicm if (retval == CMD_RETURN_ERROR) 78268e0a7f2Snicm cmdq_remove_group(item); 783765b9a58Snicm break; 78468e0a7f2Snicm case CMDQ_CALLBACK: 78568e0a7f2Snicm retval = cmdq_fire_callback(item); 786765b9a58Snicm break; 787765b9a58Snicm default: 788765b9a58Snicm retval = CMD_RETURN_ERROR; 789765b9a58Snicm break; 790765b9a58Snicm } 79168e0a7f2Snicm item->flags |= CMDQ_FIRED; 792765b9a58Snicm 793765b9a58Snicm if (retval == CMD_RETURN_WAIT) { 79468e0a7f2Snicm item->flags |= CMDQ_WAITING; 795765b9a58Snicm goto waiting; 796765b9a58Snicm } 797765b9a58Snicm items++; 798765b9a58Snicm } 79968e0a7f2Snicm cmdq_remove(item); 800765b9a58Snicm } 8015c72fbecSnicm queue->item = NULL; 802765b9a58Snicm 803765b9a58Snicm log_debug("%s %s: exit (empty)", __func__, name); 804765b9a58Snicm return (items); 805765b9a58Snicm 806765b9a58Snicm waiting: 807765b9a58Snicm log_debug("%s %s: exit (wait)", __func__, name); 808765b9a58Snicm return (items); 809765b9a58Snicm } 810765b9a58Snicm 8115c72fbecSnicm /* Get running item if any. */ 8125c72fbecSnicm struct cmdq_item * 8135c72fbecSnicm cmdq_running(struct client *c) 8145c72fbecSnicm { 8155c72fbecSnicm struct cmdq_list *queue = cmdq_get(c); 8165c72fbecSnicm 81770449ab2Snicm if (queue->item == NULL) 81870449ab2Snicm return (NULL); 81970449ab2Snicm if (queue->item->flags & CMDQ_WAITING) 82070449ab2Snicm return (NULL); 8215c72fbecSnicm return (queue->item); 8225c72fbecSnicm } 8235c72fbecSnicm 824765b9a58Snicm /* Print a guard line. */ 825765b9a58Snicm void 82668e0a7f2Snicm cmdq_guard(struct cmdq_item *item, const char *guard, int flags) 827765b9a58Snicm { 82868e0a7f2Snicm struct client *c = item->client; 829f4bc7c7aSnicm long t = item->time; 830f4bc7c7aSnicm u_int number = item->number; 831765b9a58Snicm 832f4bc7c7aSnicm if (c != NULL && (c->flags & CLIENT_CONTROL)) 833a34cf9c8Snicm control_write(c, "%%%s %ld %u %d", guard, t, number, flags); 834175d36ccSnicm } 835175d36ccSnicm 836175d36ccSnicm /* Show message from command. */ 83774d4b937Snicm void 838*3a775927Snicm cmdq_print_data(struct cmdq_item *item, struct evbuffer *evb) 839175d36ccSnicm { 840*3a775927Snicm server_client_print(item->client, 1, evb); 841f260bbaaSnicm } 842f260bbaaSnicm 843f260bbaaSnicm /* Show message from command. */ 844f260bbaaSnicm void 845f260bbaaSnicm cmdq_print(struct cmdq_item *item, const char *fmt, ...) 846f260bbaaSnicm { 847f260bbaaSnicm va_list ap; 848f260bbaaSnicm struct evbuffer *evb; 849f260bbaaSnicm 850f260bbaaSnicm evb = evbuffer_new(); 851f260bbaaSnicm if (evb == NULL) 852f260bbaaSnicm fatalx("out of memory"); 853f260bbaaSnicm 854f260bbaaSnicm va_start(ap, fmt); 855f260bbaaSnicm evbuffer_add_vprintf(evb, fmt, ap); 856f260bbaaSnicm va_end(ap); 857f260bbaaSnicm 858*3a775927Snicm cmdq_print_data(item, evb); 859f260bbaaSnicm evbuffer_free(evb); 860175d36ccSnicm } 861175d36ccSnicm 862175d36ccSnicm /* Show error from command. */ 86374d4b937Snicm void 86468e0a7f2Snicm cmdq_error(struct cmdq_item *item, const char *fmt, ...) 865175d36ccSnicm { 86668e0a7f2Snicm struct client *c = item->client; 86768e0a7f2Snicm struct cmd *cmd = item->cmd; 868175d36ccSnicm va_list ap; 86990d7ba38Snicm char *msg, *tmp; 87090d7ba38Snicm const char *file; 87190d7ba38Snicm u_int line; 872175d36ccSnicm 873175d36ccSnicm va_start(ap, fmt); 874f4bc7c7aSnicm xvasprintf(&msg, fmt, ap); 875175d36ccSnicm va_end(ap); 876175d36ccSnicm 8776e8d04c2Snicm log_debug("%s: %s", __func__, msg); 8786e8d04c2Snicm 87990d7ba38Snicm if (c == NULL) { 88090d7ba38Snicm cmd_get_source(cmd, &file, &line); 88190d7ba38Snicm cfg_add_cause("%s:%u: %s", file, line, msg); 88290d7ba38Snicm } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 8830687354cSnicm server_add_message("%s message: %s", c->name, msg); 88462f1fdfdSnicm if (~c->flags & CLIENT_UTF8) { 88562f1fdfdSnicm tmp = msg; 88662f1fdfdSnicm msg = utf8_sanitize(tmp); 88762f1fdfdSnicm free(tmp); 88862f1fdfdSnicm } 88937b4450bSnicm if (c->flags & CLIENT_CONTROL) 890a34cf9c8Snicm control_write(c, "%s", msg); 89137b4450bSnicm else 892f4bc7c7aSnicm file_error(c, "%s\n", msg); 893f6a3ada0Snicm c->retval = 1; 894175d36ccSnicm } else { 895175d36ccSnicm *msg = toupper((u_char) *msg); 896e7e79d0aSnicm status_message_set(c, -1, 1, 0, "%s", msg); 897175d36ccSnicm } 898175d36ccSnicm 899175d36ccSnicm free(msg); 900175d36ccSnicm } 901