1 /* $OpenBSD: cmd-queue.c,v 1.85 2020/04/13 13:42:35 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 46 enum cmdq_type type; 47 u_int group; 48 49 u_int number; 50 time_t time; 51 52 int flags; 53 54 struct cmdq_shared *shared; 55 struct cmd_find_state source; 56 struct cmd_find_state target; 57 58 struct cmd_list *cmdlist; 59 struct cmd *cmd; 60 61 cmdq_cb cb; 62 void *data; 63 64 TAILQ_ENTRY(cmdq_item) entry; 65 }; 66 TAILQ_HEAD(cmdq_list, cmdq_item); 67 68 /* Get command queue name. */ 69 static const char * 70 cmdq_name(struct client *c) 71 { 72 static char s[256]; 73 74 if (c == NULL) 75 return ("<global>"); 76 if (c->name != NULL) 77 xsnprintf(s, sizeof s, "<%s>", c->name); 78 else 79 xsnprintf(s, sizeof s, "<%p>", c); 80 return (s); 81 } 82 83 /* Get command queue from client. */ 84 static struct cmdq_list * 85 cmdq_get(struct client *c) 86 { 87 static struct cmdq_list *global_queue; 88 89 if (c == NULL) { 90 if (global_queue == NULL) 91 global_queue = cmdq_new(); 92 return (global_queue); 93 } 94 return (c->queue); 95 } 96 97 /* Create a queue. */ 98 struct cmdq_list * 99 cmdq_new(void) 100 { 101 struct cmdq_list *queue; 102 103 queue = xcalloc (1, sizeof *queue); 104 TAILQ_INIT (queue); 105 return (queue); 106 } 107 108 /* Free a queue. */ 109 void 110 cmdq_free(struct cmdq_list *queue) 111 { 112 if (!TAILQ_EMPTY(queue)) 113 fatalx("queue not empty"); 114 free(queue); 115 } 116 117 /* Get item name. */ 118 const char * 119 cmdq_get_name(struct cmdq_item *item) 120 { 121 return (item->name); 122 } 123 124 /* Get item client. */ 125 struct client * 126 cmdq_get_client(struct cmdq_item *item) 127 { 128 return (item->client); 129 } 130 131 /* Get item target. */ 132 struct cmd_find_state * 133 cmdq_get_target(struct cmdq_item *item) 134 { 135 return (&item->target); 136 } 137 138 /* Get item source. */ 139 struct cmd_find_state * 140 cmdq_get_source(struct cmdq_item *item) 141 { 142 return (&item->source); 143 } 144 145 /* Get item shared. */ 146 struct cmdq_shared * 147 cmdq_get_shared(struct cmdq_item *item) 148 { 149 return (item->shared); 150 } 151 152 /* Merge formats from item. */ 153 void 154 cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft) 155 { 156 const struct cmd_entry *entry; 157 158 if (item->cmd != NULL) { 159 entry = cmd_get_entry (item->cmd); 160 format_add(ft, "command", "%s", entry->name); 161 } 162 if (item->shared->formats != NULL) 163 format_merge(ft, item->shared->formats); 164 } 165 166 /* Append an item. */ 167 struct cmdq_item * 168 cmdq_append(struct client *c, struct cmdq_item *item) 169 { 170 struct cmdq_list *queue = cmdq_get(c); 171 struct cmdq_item *next; 172 173 do { 174 next = item->next; 175 item->next = NULL; 176 177 if (c != NULL) 178 c->references++; 179 item->client = c; 180 181 item->queue = queue; 182 TAILQ_INSERT_TAIL(queue, item, entry); 183 log_debug("%s %s: %s", __func__, cmdq_name(c), item->name); 184 185 item = next; 186 } while (item != NULL); 187 return (TAILQ_LAST(queue, cmdq_list)); 188 } 189 190 /* Insert an item. */ 191 struct cmdq_item * 192 cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) 193 { 194 struct client *c = after->client; 195 struct cmdq_list *queue = after->queue; 196 struct cmdq_item *next; 197 198 do { 199 next = item->next; 200 item->next = after->next; 201 after->next = item; 202 203 if (c != NULL) 204 c->references++; 205 item->client = c; 206 207 item->queue = queue; 208 TAILQ_INSERT_AFTER(queue, after, item, entry); 209 log_debug("%s %s: %s after %s", __func__, cmdq_name(c), 210 item->name, after->name); 211 212 after = item; 213 item = next; 214 } while (item != NULL); 215 return (after); 216 } 217 218 /* Insert a hook. */ 219 void 220 cmdq_insert_hook(struct session *s, struct cmdq_item *item, 221 struct cmd_find_state *fs, const char *fmt, ...) 222 { 223 struct options *oo; 224 va_list ap; 225 char *name; 226 struct cmdq_item *new_item; 227 struct options_entry *o; 228 struct options_array_item *a; 229 struct cmd_list *cmdlist; 230 231 if (item->shared->flags & CMDQ_SHARED_NOHOOKS) 232 return; 233 if (s == NULL) 234 oo = global_s_options; 235 else 236 oo = s->options; 237 238 va_start(ap, fmt); 239 xvasprintf(&name, fmt, ap); 240 va_end(ap); 241 242 o = options_get(oo, name); 243 if (o == NULL) { 244 free(name); 245 return; 246 } 247 log_debug("running hook %s (parent %p)", name, item); 248 249 a = options_array_first(o); 250 while (a != NULL) { 251 cmdlist = options_array_item_value(a)->cmdlist; 252 if (cmdlist == NULL) { 253 a = options_array_next(a); 254 continue; 255 } 256 257 new_item = cmdq_get_command(cmdlist, fs, NULL, 258 CMDQ_SHARED_NOHOOKS); 259 cmdq_format(new_item, "hook", "%s", name); 260 if (item != NULL) 261 item = cmdq_insert_after(item, new_item); 262 else 263 item = cmdq_append(NULL, new_item); 264 265 a = options_array_next(a); 266 } 267 268 free(name); 269 } 270 271 /* Continue processing command queue. */ 272 void 273 cmdq_continue(struct cmdq_item *item) 274 { 275 item->flags &= ~CMDQ_WAITING; 276 } 277 278 /* Remove an item. */ 279 static void 280 cmdq_remove(struct cmdq_item *item) 281 { 282 if (item->shared != NULL && --item->shared->references == 0) { 283 if (item->shared->formats != NULL) 284 format_free(item->shared->formats); 285 free(item->shared); 286 } 287 288 if (item->client != NULL) 289 server_client_unref(item->client); 290 291 if (item->cmdlist != NULL) 292 cmd_list_free(item->cmdlist); 293 294 TAILQ_REMOVE(item->queue, item, entry); 295 296 free(item->name); 297 free(item); 298 } 299 300 /* Remove all subsequent items that match this item's group. */ 301 static void 302 cmdq_remove_group(struct cmdq_item *item) 303 { 304 struct cmdq_item *this, *next; 305 306 if (item->group == 0) 307 return; 308 this = TAILQ_NEXT(item, entry); 309 while (this != NULL) { 310 next = TAILQ_NEXT(this, entry); 311 if (this->group == item->group) 312 cmdq_remove(this); 313 this = next; 314 } 315 } 316 317 /* Get a command for the command queue. */ 318 struct cmdq_item * 319 cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current, 320 struct mouse_event *m, int flags) 321 { 322 struct cmdq_item *item, *first = NULL, *last = NULL; 323 struct cmd *cmd; 324 const struct cmd_entry *entry; 325 struct cmdq_shared *shared = NULL; 326 u_int group, last_group = 0; 327 328 cmd = cmd_list_first(cmdlist, &group); 329 while (cmd != NULL) { 330 if (group != last_group) { 331 shared = xcalloc(1, sizeof *shared); 332 if (current != NULL) 333 cmd_find_copy_state(&shared->current, current); 334 else 335 cmd_find_clear_state(&shared->current, 0); 336 if (m != NULL) { 337 shared->event.key = KEYC_NONE; 338 memcpy(&shared->event.m, m, 339 sizeof shared->event.m); 340 } 341 shared->flags = flags; 342 last_group = group; 343 } 344 entry = cmd_get_entry(cmd); 345 346 item = xcalloc(1, sizeof *item); 347 xasprintf(&item->name, "[%s/%p]", entry->name, item); 348 item->type = CMDQ_COMMAND; 349 item->group = group; 350 351 item->shared = shared; 352 item->cmdlist = cmdlist; 353 item->cmd = cmd; 354 355 log_debug("%s: %s group %u", __func__, item->name, item->group); 356 357 shared->references++; 358 cmdlist->references++; 359 360 if (first == NULL) 361 first = item; 362 if (last != NULL) 363 last->next = item; 364 last = item; 365 366 cmd = cmd_list_next(cmd, &group); 367 } 368 return (first); 369 } 370 371 /* Fill in flag for a command. */ 372 static enum cmd_retval 373 cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, 374 const struct cmd_entry_flag *flag) 375 { 376 const char *value; 377 378 if (flag->flag == 0) { 379 cmd_find_clear_state(fs, 0); 380 return (CMD_RETURN_NORMAL); 381 } 382 383 value = args_get(cmd_get_args(item->cmd), flag->flag); 384 if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) { 385 cmd_find_clear_state(fs, 0); 386 return (CMD_RETURN_ERROR); 387 } 388 return (CMD_RETURN_NORMAL); 389 } 390 391 /* Fire command on command queue. */ 392 static enum cmd_retval 393 cmdq_fire_command(struct cmdq_item *item) 394 { 395 struct client *c = item->client; 396 const char *name = cmdq_name(c); 397 struct cmdq_shared *shared = item->shared; 398 struct cmd *cmd = item->cmd; 399 const struct cmd_entry *entry = cmd_get_entry(cmd); 400 enum cmd_retval retval; 401 struct cmd_find_state *fsp, fs; 402 int flags; 403 char *tmp; 404 405 if (log_get_level() > 1) { 406 tmp = cmd_print(cmd); 407 log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp); 408 free(tmp); 409 } 410 411 flags = !!(shared->flags & CMDQ_SHARED_CONTROL); 412 cmdq_guard(item, "begin", flags); 413 414 if (item->client == NULL) 415 item->client = cmd_find_client(item, NULL, 1); 416 retval = cmdq_find_flag(item, &item->source, &entry->source); 417 if (retval == CMD_RETURN_ERROR) 418 goto out; 419 retval = cmdq_find_flag(item, &item->target, &entry->target); 420 if (retval == CMD_RETURN_ERROR) 421 goto out; 422 423 retval = entry->exec(cmd, item); 424 if (retval == CMD_RETURN_ERROR) 425 goto out; 426 427 if (entry->flags & CMD_AFTERHOOK) { 428 if (cmd_find_valid_state(&item->target)) 429 fsp = &item->target; 430 else if (cmd_find_valid_state(&item->shared->current)) 431 fsp = &item->shared->current; 432 else if (cmd_find_from_client(&fs, item->client, 0) == 0) 433 fsp = &fs; 434 else 435 goto out; 436 cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name); 437 } 438 439 out: 440 item->client = c; 441 if (retval == CMD_RETURN_ERROR) 442 cmdq_guard(item, "error", flags); 443 else 444 cmdq_guard(item, "end", flags); 445 return (retval); 446 } 447 448 /* Get a callback for the command queue. */ 449 struct cmdq_item * 450 cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) 451 { 452 struct cmdq_item *item; 453 454 item = xcalloc(1, sizeof *item); 455 xasprintf(&item->name, "[%s/%p]", name, item); 456 item->type = CMDQ_CALLBACK; 457 item->group = 0; 458 459 item->cb = cb; 460 item->data = data; 461 462 return (item); 463 } 464 465 /* Generic error callback. */ 466 static enum cmd_retval 467 cmdq_error_callback(struct cmdq_item *item, void *data) 468 { 469 char *error = data; 470 471 cmdq_error(item, "%s", error); 472 free(error); 473 474 return (CMD_RETURN_NORMAL); 475 } 476 477 /* Get an error callback for the command queue. */ 478 struct cmdq_item * 479 cmdq_get_error(const char *error) 480 { 481 return (cmdq_get_callback(cmdq_error_callback, xstrdup(error))); 482 } 483 484 /* Fire callback on callback queue. */ 485 static enum cmd_retval 486 cmdq_fire_callback(struct cmdq_item *item) 487 { 488 return (item->cb(item, item->data)); 489 } 490 491 /* Add a format to command queue. */ 492 void 493 cmdq_format(struct cmdq_item *item, const char *key, const char *fmt, ...) 494 { 495 struct cmdq_shared *shared = item->shared; 496 va_list ap; 497 char *value; 498 499 va_start(ap, fmt); 500 xvasprintf(&value, fmt, ap); 501 va_end(ap); 502 503 if (shared->formats == NULL) 504 shared->formats = format_create(NULL, NULL, FORMAT_NONE, 0); 505 format_add(shared->formats, key, "%s", value); 506 507 free(value); 508 } 509 510 /* Process next item on command queue. */ 511 u_int 512 cmdq_next(struct client *c) 513 { 514 struct cmdq_list *queue = cmdq_get(c); 515 const char *name = cmdq_name(c); 516 struct cmdq_item *item; 517 enum cmd_retval retval; 518 u_int items = 0; 519 static u_int number; 520 521 if (TAILQ_EMPTY(queue)) { 522 log_debug("%s %s: empty", __func__, name); 523 return (0); 524 } 525 if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) { 526 log_debug("%s %s: waiting", __func__, name); 527 return (0); 528 } 529 530 log_debug("%s %s: enter", __func__, name); 531 for (;;) { 532 item = TAILQ_FIRST(queue); 533 if (item == NULL) 534 break; 535 log_debug("%s %s: %s (%d), flags %x", __func__, name, 536 item->name, item->type, item->flags); 537 538 /* 539 * Any item with the waiting flag set waits until an external 540 * event clears the flag (for example, a job - look at 541 * run-shell). 542 */ 543 if (item->flags & CMDQ_WAITING) 544 goto waiting; 545 546 /* 547 * Items are only fired once, once the fired flag is set, a 548 * waiting flag can only be cleared by an external event. 549 */ 550 if (~item->flags & CMDQ_FIRED) { 551 item->time = time(NULL); 552 item->number = ++number; 553 554 switch (item->type) { 555 case CMDQ_COMMAND: 556 retval = cmdq_fire_command(item); 557 558 /* 559 * If a command returns an error, remove any 560 * subsequent commands in the same group. 561 */ 562 if (retval == CMD_RETURN_ERROR) 563 cmdq_remove_group(item); 564 break; 565 case CMDQ_CALLBACK: 566 retval = cmdq_fire_callback(item); 567 break; 568 default: 569 retval = CMD_RETURN_ERROR; 570 break; 571 } 572 item->flags |= CMDQ_FIRED; 573 574 if (retval == CMD_RETURN_WAIT) { 575 item->flags |= CMDQ_WAITING; 576 goto waiting; 577 } 578 items++; 579 } 580 cmdq_remove(item); 581 } 582 583 log_debug("%s %s: exit (empty)", __func__, name); 584 return (items); 585 586 waiting: 587 log_debug("%s %s: exit (wait)", __func__, name); 588 return (items); 589 } 590 591 /* Print a guard line. */ 592 void 593 cmdq_guard(struct cmdq_item *item, const char *guard, int flags) 594 { 595 struct client *c = item->client; 596 long t = item->time; 597 u_int number = item->number; 598 599 if (c != NULL && (c->flags & CLIENT_CONTROL)) 600 file_print(c, "%%%s %ld %u %d\n", guard, t, number, flags); 601 } 602 603 /* Show message from command. */ 604 void 605 cmdq_print(struct cmdq_item *item, const char *fmt, ...) 606 { 607 struct client *c = item->client; 608 struct window_pane *wp; 609 struct window_mode_entry *wme; 610 va_list ap; 611 char *tmp, *msg; 612 613 va_start(ap, fmt); 614 xvasprintf(&msg, fmt, ap); 615 va_end(ap); 616 617 log_debug("%s: %s", __func__, msg); 618 619 if (c == NULL) 620 /* nothing */; 621 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 622 if (~c->flags & CLIENT_UTF8) { 623 tmp = msg; 624 msg = utf8_sanitize(tmp); 625 free(tmp); 626 } 627 file_print(c, "%s\n", msg); 628 } else { 629 wp = c->session->curw->window->active; 630 wme = TAILQ_FIRST(&wp->modes); 631 if (wme == NULL || wme->mode != &window_view_mode) { 632 window_pane_set_mode(wp, NULL, &window_view_mode, NULL, 633 NULL); 634 } 635 window_copy_add(wp, "%s", msg); 636 } 637 638 free(msg); 639 } 640 641 /* Show error from command. */ 642 void 643 cmdq_error(struct cmdq_item *item, const char *fmt, ...) 644 { 645 struct client *c = item->client; 646 struct cmd *cmd = item->cmd; 647 va_list ap; 648 char *msg, *tmp; 649 const char *file; 650 u_int line; 651 652 va_start(ap, fmt); 653 xvasprintf(&msg, fmt, ap); 654 va_end(ap); 655 656 log_debug("%s: %s", __func__, msg); 657 658 if (c == NULL) { 659 cmd_get_source(cmd, &file, &line); 660 cfg_add_cause("%s:%u: %s", file, line, msg); 661 } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 662 if (~c->flags & CLIENT_UTF8) { 663 tmp = msg; 664 msg = utf8_sanitize(tmp); 665 free(tmp); 666 } 667 if (c->flags & CLIENT_CONTROL) 668 file_print(c, "%s\n", msg); 669 else 670 file_error(c, "%s\n", msg); 671 c->retval = 1; 672 } else { 673 *msg = toupper((u_char) *msg); 674 status_message_set(c, "%s", msg); 675 } 676 677 free(msg); 678 } 679