1 /* $OpenBSD: cmd-queue.c,v 1.89 2020/04/13 20:51:57 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 21 #include <ctype.h> 22 #include <stdlib.h> 23 #include <string.h> 24 #include <time.h> 25 26 #include "tmux.h" 27 28 /* Command queue flags. */ 29 #define CMDQ_FIRED 0x1 30 #define CMDQ_WAITING 0x2 31 32 /* Command queue item type. */ 33 enum cmdq_type { 34 CMDQ_COMMAND, 35 CMDQ_CALLBACK, 36 }; 37 38 /* Command queue item. */ 39 struct cmdq_item { 40 char *name; 41 struct cmdq_list *queue; 42 struct cmdq_item *next; 43 44 struct client *client; 45 struct client *target_client; 46 47 enum cmdq_type type; 48 u_int group; 49 50 u_int number; 51 time_t time; 52 53 int flags; 54 55 struct cmdq_state *state; 56 struct cmd_find_state source; 57 struct cmd_find_state target; 58 59 struct cmd_list *cmdlist; 60 struct cmd *cmd; 61 62 cmdq_cb cb; 63 void *data; 64 65 TAILQ_ENTRY(cmdq_item) entry; 66 }; 67 TAILQ_HEAD(cmdq_list, cmdq_item); 68 69 /* 70 * Command queue state. This is the context for commands on the command queue. 71 * It holds information about how the commands were fired (the key and flags), 72 * any additional formats for the commands, and the current default target. 73 * Multiple commands can share the same state and a command may update the 74 * default target. 75 */ 76 struct cmdq_state { 77 int references; 78 int flags; 79 80 struct format_tree *formats; 81 82 struct key_event event; 83 struct cmd_find_state current; 84 }; 85 86 /* Get command queue name. */ 87 static const char * 88 cmdq_name(struct client *c) 89 { 90 static char s[256]; 91 92 if (c == NULL) 93 return ("<global>"); 94 if (c->name != NULL) 95 xsnprintf(s, sizeof s, "<%s>", c->name); 96 else 97 xsnprintf(s, sizeof s, "<%p>", c); 98 return (s); 99 } 100 101 /* Get command queue from client. */ 102 static struct cmdq_list * 103 cmdq_get(struct client *c) 104 { 105 static struct cmdq_list *global_queue; 106 107 if (c == NULL) { 108 if (global_queue == NULL) 109 global_queue = cmdq_new(); 110 return (global_queue); 111 } 112 return (c->queue); 113 } 114 115 /* Create a queue. */ 116 struct cmdq_list * 117 cmdq_new(void) 118 { 119 struct cmdq_list *queue; 120 121 queue = xcalloc (1, sizeof *queue); 122 TAILQ_INIT (queue); 123 return (queue); 124 } 125 126 /* Free a queue. */ 127 void 128 cmdq_free(struct cmdq_list *queue) 129 { 130 if (!TAILQ_EMPTY(queue)) 131 fatalx("queue not empty"); 132 free(queue); 133 } 134 135 /* Get item name. */ 136 const char * 137 cmdq_get_name(struct cmdq_item *item) 138 { 139 return (item->name); 140 } 141 142 /* Get item client. */ 143 struct client * 144 cmdq_get_client(struct cmdq_item *item) 145 { 146 return (item->client); 147 } 148 149 /* Get item target client. */ 150 struct client * 151 cmdq_get_target_client(struct cmdq_item *item) 152 { 153 return (item->target_client); 154 } 155 156 /* Get item state. */ 157 struct cmdq_state * 158 cmdq_get_state(struct cmdq_item *item) 159 { 160 return (item->state); 161 } 162 163 /* Get item target. */ 164 struct cmd_find_state * 165 cmdq_get_target(struct cmdq_item *item) 166 { 167 return (&item->target); 168 } 169 170 /* Get item source. */ 171 struct cmd_find_state * 172 cmdq_get_source(struct cmdq_item *item) 173 { 174 return (&item->source); 175 } 176 177 /* Get state event. */ 178 struct key_event * 179 cmdq_get_event(struct cmdq_item *item) 180 { 181 return (&item->state->event); 182 } 183 184 /* Get state current target. */ 185 struct cmd_find_state * 186 cmdq_get_current(struct cmdq_item *item) 187 { 188 return (&item->state->current); 189 } 190 191 /* Get state flags. */ 192 int 193 cmdq_get_flags(struct cmdq_item *item) 194 { 195 return (item->state->flags); 196 } 197 198 /* Create a new state. */ 199 struct cmdq_state * 200 cmdq_new_state(struct cmd_find_state *current, struct key_event *event, 201 int flags) 202 { 203 struct cmdq_state *state; 204 205 state = xcalloc(1, sizeof *state); 206 state->references = 1; 207 state->flags = flags; 208 209 if (event != NULL) 210 memcpy(&state->event, event, sizeof state->event); 211 else 212 state->event.key = KEYC_NONE; 213 if (current != NULL && cmd_find_valid_state(current)) 214 cmd_find_copy_state(&state->current, current); 215 else 216 cmd_find_clear_state(&state->current, 0); 217 218 return (state); 219 } 220 221 /* Add a reference to a state. */ 222 struct cmdq_state * 223 cmdq_link_state(struct cmdq_state *state) 224 { 225 state->references++; 226 return (state); 227 } 228 229 /* Make a copy of a state. */ 230 struct cmdq_state * 231 cmdq_copy_state(struct cmdq_state *state) 232 { 233 return (cmdq_new_state(&state->current, &state->event, state->flags)); 234 } 235 236 /* Free a state. */ 237 void 238 cmdq_free_state(struct cmdq_state *state) 239 { 240 if (--state->references == 0) 241 free(state); 242 } 243 244 /* Add a format to command queue. */ 245 void 246 cmdq_add_format(struct cmdq_state *state, const char *key, const char *fmt, ...) 247 { 248 va_list ap; 249 char *value; 250 251 va_start(ap, fmt); 252 xvasprintf(&value, fmt, ap); 253 va_end(ap); 254 255 if (state->formats == NULL) 256 state->formats = format_create(NULL, NULL, FORMAT_NONE, 0); 257 format_add(state->formats, key, "%s", value); 258 259 free(value); 260 } 261 262 /* Merge formats from item. */ 263 void 264 cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft) 265 { 266 const struct cmd_entry *entry; 267 268 if (item->cmd != NULL) { 269 entry = cmd_get_entry (item->cmd); 270 format_add(ft, "command", "%s", entry->name); 271 } 272 if (item->state->formats != NULL) 273 format_merge(ft, item->state->formats); 274 } 275 276 /* Append an item. */ 277 struct cmdq_item * 278 cmdq_append(struct client *c, struct cmdq_item *item) 279 { 280 struct cmdq_list *queue = cmdq_get(c); 281 struct cmdq_item *next; 282 283 do { 284 next = item->next; 285 item->next = NULL; 286 287 if (c != NULL) 288 c->references++; 289 item->client = c; 290 291 item->queue = queue; 292 TAILQ_INSERT_TAIL(queue, item, entry); 293 log_debug("%s %s: %s", __func__, cmdq_name(c), item->name); 294 295 item = next; 296 } while (item != NULL); 297 return (TAILQ_LAST(queue, cmdq_list)); 298 } 299 300 /* Insert an item. */ 301 struct cmdq_item * 302 cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) 303 { 304 struct client *c = after->client; 305 struct cmdq_list *queue = after->queue; 306 struct cmdq_item *next; 307 308 do { 309 next = item->next; 310 item->next = after->next; 311 after->next = item; 312 313 if (c != NULL) 314 c->references++; 315 item->client = c; 316 317 item->queue = queue; 318 TAILQ_INSERT_AFTER(queue, after, item, entry); 319 log_debug("%s %s: %s after %s", __func__, cmdq_name(c), 320 item->name, after->name); 321 322 after = item; 323 item = next; 324 } while (item != NULL); 325 return (after); 326 } 327 328 /* Insert a hook. */ 329 void 330 cmdq_insert_hook(struct session *s, struct cmdq_item *item, 331 struct cmd_find_state *current, const char *fmt, ...) 332 { 333 struct cmdq_state *state = item->state; 334 struct options *oo; 335 va_list ap; 336 char *name; 337 struct cmdq_item *new_item; 338 struct cmdq_state *new_state; 339 struct options_entry *o; 340 struct options_array_item *a; 341 struct cmd_list *cmdlist; 342 343 if (item->state->flags & CMDQ_STATE_NOHOOKS) 344 return; 345 if (s == NULL) 346 oo = global_s_options; 347 else 348 oo = s->options; 349 350 va_start(ap, fmt); 351 xvasprintf(&name, fmt, ap); 352 va_end(ap); 353 354 o = options_get(oo, name); 355 if (o == NULL) { 356 free(name); 357 return; 358 } 359 log_debug("running hook %s (parent %p)", name, item); 360 361 /* 362 * The hooks get a new state because they should not update the current 363 * target or formats for any subsequent commands. 364 */ 365 new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS); 366 cmdq_add_format(new_state, "hook", "%s", name); 367 368 a = options_array_first(o); 369 while (a != NULL) { 370 cmdlist = options_array_item_value(a)->cmdlist; 371 if (cmdlist != NULL) { 372 new_item = cmdq_get_command(cmdlist, new_state); 373 if (item != NULL) 374 item = cmdq_insert_after(item, new_item); 375 else 376 item = cmdq_append(NULL, new_item); 377 } 378 a = options_array_next(a); 379 } 380 381 cmdq_free_state(new_state); 382 free(name); 383 } 384 385 /* Continue processing command queue. */ 386 void 387 cmdq_continue(struct cmdq_item *item) 388 { 389 item->flags &= ~CMDQ_WAITING; 390 } 391 392 /* Remove an item. */ 393 static void 394 cmdq_remove(struct cmdq_item *item) 395 { 396 if (item->client != NULL) 397 server_client_unref(item->client); 398 if (item->cmdlist != NULL) 399 cmd_list_free(item->cmdlist); 400 cmdq_free_state(item->state); 401 402 TAILQ_REMOVE(item->queue, item, entry); 403 404 free(item->name); 405 free(item); 406 } 407 408 /* Remove all subsequent items that match this item's group. */ 409 static void 410 cmdq_remove_group(struct cmdq_item *item) 411 { 412 struct cmdq_item *this, *next; 413 414 if (item->group == 0) 415 return; 416 this = TAILQ_NEXT(item, entry); 417 while (this != NULL) { 418 next = TAILQ_NEXT(this, entry); 419 if (this->group == item->group) 420 cmdq_remove(this); 421 this = next; 422 } 423 } 424 425 /* Get a command for the command queue. */ 426 struct cmdq_item * 427 cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state) 428 { 429 struct cmdq_item *item, *first = NULL, *last = NULL; 430 struct cmd *cmd; 431 const struct cmd_entry *entry; 432 int created = 0; 433 434 if (state == NULL) { 435 state = cmdq_new_state(NULL, NULL, 0); 436 created = 1; 437 } 438 439 cmd = cmd_list_first(cmdlist); 440 while (cmd != NULL) { 441 entry = cmd_get_entry(cmd); 442 443 item = xcalloc(1, sizeof *item); 444 xasprintf(&item->name, "[%s/%p]", entry->name, item); 445 item->type = CMDQ_COMMAND; 446 447 item->group = cmd_get_group(cmd); 448 item->state = cmdq_link_state(state); 449 450 item->cmdlist = cmdlist; 451 item->cmd = cmd; 452 453 cmdlist->references++; 454 log_debug("%s: %s group %u", __func__, item->name, item->group); 455 456 if (first == NULL) 457 first = item; 458 if (last != NULL) 459 last->next = item; 460 last = item; 461 462 cmd = cmd_list_next(cmd); 463 } 464 465 if (created) 466 cmdq_free_state(state); 467 return (first); 468 } 469 470 /* Fill in flag for a command. */ 471 static enum cmd_retval 472 cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, 473 const struct cmd_entry_flag *flag) 474 { 475 const char *value; 476 477 if (flag->flag == 0) { 478 cmd_find_clear_state(fs, 0); 479 return (CMD_RETURN_NORMAL); 480 } 481 482 value = args_get(cmd_get_args(item->cmd), flag->flag); 483 if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) { 484 cmd_find_clear_state(fs, 0); 485 return (CMD_RETURN_ERROR); 486 } 487 return (CMD_RETURN_NORMAL); 488 } 489 490 /* Fire command on command queue. */ 491 static enum cmd_retval 492 cmdq_fire_command(struct cmdq_item *item) 493 { 494 const char *name = cmdq_name(item->client); 495 struct cmdq_state *state = item->state; 496 struct cmd *cmd = item->cmd; 497 struct args *args = cmd_get_args(cmd); 498 const struct cmd_entry *entry = cmd_get_entry(cmd); 499 struct client *tc, *saved = item->client; 500 enum cmd_retval retval; 501 struct cmd_find_state *fsp, fs; 502 int flags, quiet = 0; 503 char *tmp; 504 505 if (log_get_level() > 1) { 506 tmp = cmd_print(cmd); 507 log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp); 508 free(tmp); 509 } 510 511 flags = !!(state->flags & CMDQ_STATE_CONTROL); 512 cmdq_guard(item, "begin", flags); 513 514 if (item->client == NULL) 515 item->client = cmd_find_client(item, NULL, 1); 516 517 if (entry->flags & CMD_CLIENT_CANFAIL) 518 quiet = 1; 519 if (entry->flags & CMD_CLIENT_CFLAG) { 520 tc = cmd_find_client(item, args_get(args, 'c'), quiet); 521 if (tc == NULL && !quiet) { 522 retval = CMD_RETURN_ERROR; 523 goto out; 524 } 525 } else if (entry->flags & CMD_CLIENT_TFLAG) { 526 tc = cmd_find_client(item, args_get(args, 't'), quiet); 527 if (tc == NULL && !quiet) { 528 retval = CMD_RETURN_ERROR; 529 goto out; 530 } 531 } else 532 tc = cmd_find_client(item, NULL, 1); 533 item->target_client = tc; 534 535 retval = cmdq_find_flag(item, &item->source, &entry->source); 536 if (retval == CMD_RETURN_ERROR) 537 goto out; 538 retval = cmdq_find_flag(item, &item->target, &entry->target); 539 if (retval == CMD_RETURN_ERROR) 540 goto out; 541 542 543 retval = entry->exec(cmd, item); 544 if (retval == CMD_RETURN_ERROR) 545 goto out; 546 547 if (entry->flags & CMD_AFTERHOOK) { 548 if (cmd_find_valid_state(&item->target)) 549 fsp = &item->target; 550 else if (cmd_find_valid_state(&item->state->current)) 551 fsp = &item->state->current; 552 else if (cmd_find_from_client(&fs, item->client, 0) == 0) 553 fsp = &fs; 554 else 555 goto out; 556 cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name); 557 } 558 559 out: 560 item->client = saved; 561 if (retval == CMD_RETURN_ERROR) 562 cmdq_guard(item, "error", flags); 563 else 564 cmdq_guard(item, "end", flags); 565 return (retval); 566 } 567 568 /* Get a callback for the command queue. */ 569 struct cmdq_item * 570 cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) 571 { 572 struct cmdq_item *item; 573 574 item = xcalloc(1, sizeof *item); 575 xasprintf(&item->name, "[%s/%p]", name, item); 576 item->type = CMDQ_CALLBACK; 577 578 item->group = 0; 579 item->state = cmdq_new_state(NULL, NULL, 0); 580 581 item->cb = cb; 582 item->data = data; 583 584 return (item); 585 } 586 587 /* Generic error callback. */ 588 static enum cmd_retval 589 cmdq_error_callback(struct cmdq_item *item, void *data) 590 { 591 char *error = data; 592 593 cmdq_error(item, "%s", error); 594 free(error); 595 596 return (CMD_RETURN_NORMAL); 597 } 598 599 /* Get an error callback for the command queue. */ 600 struct cmdq_item * 601 cmdq_get_error(const char *error) 602 { 603 return (cmdq_get_callback(cmdq_error_callback, xstrdup(error))); 604 } 605 606 /* Fire callback on callback queue. */ 607 static enum cmd_retval 608 cmdq_fire_callback(struct cmdq_item *item) 609 { 610 return (item->cb(item, item->data)); 611 } 612 613 /* Process next item on command queue. */ 614 u_int 615 cmdq_next(struct client *c) 616 { 617 struct cmdq_list *queue = cmdq_get(c); 618 const char *name = cmdq_name(c); 619 struct cmdq_item *item; 620 enum cmd_retval retval; 621 u_int items = 0; 622 static u_int number; 623 624 if (TAILQ_EMPTY(queue)) { 625 log_debug("%s %s: empty", __func__, name); 626 return (0); 627 } 628 if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) { 629 log_debug("%s %s: waiting", __func__, name); 630 return (0); 631 } 632 633 log_debug("%s %s: enter", __func__, name); 634 for (;;) { 635 item = TAILQ_FIRST(queue); 636 if (item == NULL) 637 break; 638 log_debug("%s %s: %s (%d), flags %x", __func__, name, 639 item->name, item->type, item->flags); 640 641 /* 642 * Any item with the waiting flag set waits until an external 643 * event clears the flag (for example, a job - look at 644 * run-shell). 645 */ 646 if (item->flags & CMDQ_WAITING) 647 goto waiting; 648 649 /* 650 * Items are only fired once, once the fired flag is set, a 651 * waiting flag can only be cleared by an external event. 652 */ 653 if (~item->flags & CMDQ_FIRED) { 654 item->time = time(NULL); 655 item->number = ++number; 656 657 switch (item->type) { 658 case CMDQ_COMMAND: 659 retval = cmdq_fire_command(item); 660 661 /* 662 * If a command returns an error, remove any 663 * subsequent commands in the same group. 664 */ 665 if (retval == CMD_RETURN_ERROR) 666 cmdq_remove_group(item); 667 break; 668 case CMDQ_CALLBACK: 669 retval = cmdq_fire_callback(item); 670 break; 671 default: 672 retval = CMD_RETURN_ERROR; 673 break; 674 } 675 item->flags |= CMDQ_FIRED; 676 677 if (retval == CMD_RETURN_WAIT) { 678 item->flags |= CMDQ_WAITING; 679 goto waiting; 680 } 681 items++; 682 } 683 cmdq_remove(item); 684 } 685 686 log_debug("%s %s: exit (empty)", __func__, name); 687 return (items); 688 689 waiting: 690 log_debug("%s %s: exit (wait)", __func__, name); 691 return (items); 692 } 693 694 /* Print a guard line. */ 695 void 696 cmdq_guard(struct cmdq_item *item, const char *guard, int flags) 697 { 698 struct client *c = item->client; 699 long t = item->time; 700 u_int number = item->number; 701 702 if (c != NULL && (c->flags & CLIENT_CONTROL)) 703 file_print(c, "%%%s %ld %u %d\n", guard, t, number, flags); 704 } 705 706 /* Show message from command. */ 707 void 708 cmdq_print(struct cmdq_item *item, const char *fmt, ...) 709 { 710 struct client *c = item->client; 711 struct window_pane *wp; 712 struct window_mode_entry *wme; 713 va_list ap; 714 char *tmp, *msg; 715 716 va_start(ap, fmt); 717 xvasprintf(&msg, fmt, ap); 718 va_end(ap); 719 720 log_debug("%s: %s", __func__, msg); 721 722 if (c == NULL) 723 /* nothing */; 724 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 725 if (~c->flags & CLIENT_UTF8) { 726 tmp = msg; 727 msg = utf8_sanitize(tmp); 728 free(tmp); 729 } 730 file_print(c, "%s\n", msg); 731 } else { 732 wp = c->session->curw->window->active; 733 wme = TAILQ_FIRST(&wp->modes); 734 if (wme == NULL || wme->mode != &window_view_mode) { 735 window_pane_set_mode(wp, NULL, &window_view_mode, NULL, 736 NULL); 737 } 738 window_copy_add(wp, "%s", msg); 739 } 740 741 free(msg); 742 } 743 744 /* Show error from command. */ 745 void 746 cmdq_error(struct cmdq_item *item, const char *fmt, ...) 747 { 748 struct client *c = item->client; 749 struct cmd *cmd = item->cmd; 750 va_list ap; 751 char *msg, *tmp; 752 const char *file; 753 u_int line; 754 755 va_start(ap, fmt); 756 xvasprintf(&msg, fmt, ap); 757 va_end(ap); 758 759 log_debug("%s: %s", __func__, msg); 760 761 if (c == NULL) { 762 cmd_get_source(cmd, &file, &line); 763 cfg_add_cause("%s:%u: %s", file, line, msg); 764 } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 765 if (~c->flags & CLIENT_UTF8) { 766 tmp = msg; 767 msg = utf8_sanitize(tmp); 768 free(tmp); 769 } 770 if (c->flags & CLIENT_CONTROL) 771 file_print(c, "%s\n", msg); 772 else 773 file_error(c, "%s\n", msg); 774 c->retval = 1; 775 } else { 776 *msg = toupper((u_char) *msg); 777 status_message_set(c, "%s", msg); 778 } 779 780 free(msg); 781 } 782