1 /* $OpenBSD: cmd-queue.c,v 1.84 2020/04/13 13:32:09 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 memcpy(&shared->mouse, m, sizeof shared->mouse); 338 shared->flags = flags; 339 last_group = group; 340 } 341 entry = cmd_get_entry(cmd); 342 343 item = xcalloc(1, sizeof *item); 344 xasprintf(&item->name, "[%s/%p]", entry->name, item); 345 item->type = CMDQ_COMMAND; 346 item->group = group; 347 348 item->shared = shared; 349 item->cmdlist = cmdlist; 350 item->cmd = cmd; 351 352 log_debug("%s: %s group %u", __func__, item->name, item->group); 353 354 shared->references++; 355 cmdlist->references++; 356 357 if (first == NULL) 358 first = item; 359 if (last != NULL) 360 last->next = item; 361 last = item; 362 363 cmd = cmd_list_next(cmd, &group); 364 } 365 return (first); 366 } 367 368 /* Fill in flag for a command. */ 369 static enum cmd_retval 370 cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, 371 const struct cmd_entry_flag *flag) 372 { 373 const char *value; 374 375 if (flag->flag == 0) { 376 cmd_find_clear_state(fs, 0); 377 return (CMD_RETURN_NORMAL); 378 } 379 380 value = args_get(cmd_get_args(item->cmd), flag->flag); 381 if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) { 382 cmd_find_clear_state(fs, 0); 383 return (CMD_RETURN_ERROR); 384 } 385 return (CMD_RETURN_NORMAL); 386 } 387 388 /* Fire command on command queue. */ 389 static enum cmd_retval 390 cmdq_fire_command(struct cmdq_item *item) 391 { 392 struct client *c = item->client; 393 const char *name = cmdq_name(c); 394 struct cmdq_shared *shared = item->shared; 395 struct cmd *cmd = item->cmd; 396 const struct cmd_entry *entry = cmd_get_entry(cmd); 397 enum cmd_retval retval; 398 struct cmd_find_state *fsp, fs; 399 int flags; 400 char *tmp; 401 402 if (log_get_level() > 1) { 403 tmp = cmd_print(cmd); 404 log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp); 405 free(tmp); 406 } 407 408 flags = !!(shared->flags & CMDQ_SHARED_CONTROL); 409 cmdq_guard(item, "begin", flags); 410 411 if (item->client == NULL) 412 item->client = cmd_find_client(item, NULL, 1); 413 retval = cmdq_find_flag(item, &item->source, &entry->source); 414 if (retval == CMD_RETURN_ERROR) 415 goto out; 416 retval = cmdq_find_flag(item, &item->target, &entry->target); 417 if (retval == CMD_RETURN_ERROR) 418 goto out; 419 420 retval = entry->exec(cmd, item); 421 if (retval == CMD_RETURN_ERROR) 422 goto out; 423 424 if (entry->flags & CMD_AFTERHOOK) { 425 if (cmd_find_valid_state(&item->target)) 426 fsp = &item->target; 427 else if (cmd_find_valid_state(&item->shared->current)) 428 fsp = &item->shared->current; 429 else if (cmd_find_from_client(&fs, item->client, 0) == 0) 430 fsp = &fs; 431 else 432 goto out; 433 cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name); 434 } 435 436 out: 437 item->client = c; 438 if (retval == CMD_RETURN_ERROR) 439 cmdq_guard(item, "error", flags); 440 else 441 cmdq_guard(item, "end", flags); 442 return (retval); 443 } 444 445 /* Get a callback for the command queue. */ 446 struct cmdq_item * 447 cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) 448 { 449 struct cmdq_item *item; 450 451 item = xcalloc(1, sizeof *item); 452 xasprintf(&item->name, "[%s/%p]", name, item); 453 item->type = CMDQ_CALLBACK; 454 item->group = 0; 455 456 item->cb = cb; 457 item->data = data; 458 459 return (item); 460 } 461 462 /* Generic error callback. */ 463 static enum cmd_retval 464 cmdq_error_callback(struct cmdq_item *item, void *data) 465 { 466 char *error = data; 467 468 cmdq_error(item, "%s", error); 469 free(error); 470 471 return (CMD_RETURN_NORMAL); 472 } 473 474 /* Get an error callback for the command queue. */ 475 struct cmdq_item * 476 cmdq_get_error(const char *error) 477 { 478 return (cmdq_get_callback(cmdq_error_callback, xstrdup(error))); 479 } 480 481 /* Fire callback on callback queue. */ 482 static enum cmd_retval 483 cmdq_fire_callback(struct cmdq_item *item) 484 { 485 return (item->cb(item, item->data)); 486 } 487 488 /* Add a format to command queue. */ 489 void 490 cmdq_format(struct cmdq_item *item, const char *key, const char *fmt, ...) 491 { 492 struct cmdq_shared *shared = item->shared; 493 va_list ap; 494 char *value; 495 496 va_start(ap, fmt); 497 xvasprintf(&value, fmt, ap); 498 va_end(ap); 499 500 if (shared->formats == NULL) 501 shared->formats = format_create(NULL, NULL, FORMAT_NONE, 0); 502 format_add(shared->formats, key, "%s", value); 503 504 free(value); 505 } 506 507 /* Process next item on command queue. */ 508 u_int 509 cmdq_next(struct client *c) 510 { 511 struct cmdq_list *queue = cmdq_get(c); 512 const char *name = cmdq_name(c); 513 struct cmdq_item *item; 514 enum cmd_retval retval; 515 u_int items = 0; 516 static u_int number; 517 518 if (TAILQ_EMPTY(queue)) { 519 log_debug("%s %s: empty", __func__, name); 520 return (0); 521 } 522 if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) { 523 log_debug("%s %s: waiting", __func__, name); 524 return (0); 525 } 526 527 log_debug("%s %s: enter", __func__, name); 528 for (;;) { 529 item = TAILQ_FIRST(queue); 530 if (item == NULL) 531 break; 532 log_debug("%s %s: %s (%d), flags %x", __func__, name, 533 item->name, item->type, item->flags); 534 535 /* 536 * Any item with the waiting flag set waits until an external 537 * event clears the flag (for example, a job - look at 538 * run-shell). 539 */ 540 if (item->flags & CMDQ_WAITING) 541 goto waiting; 542 543 /* 544 * Items are only fired once, once the fired flag is set, a 545 * waiting flag can only be cleared by an external event. 546 */ 547 if (~item->flags & CMDQ_FIRED) { 548 item->time = time(NULL); 549 item->number = ++number; 550 551 switch (item->type) { 552 case CMDQ_COMMAND: 553 retval = cmdq_fire_command(item); 554 555 /* 556 * If a command returns an error, remove any 557 * subsequent commands in the same group. 558 */ 559 if (retval == CMD_RETURN_ERROR) 560 cmdq_remove_group(item); 561 break; 562 case CMDQ_CALLBACK: 563 retval = cmdq_fire_callback(item); 564 break; 565 default: 566 retval = CMD_RETURN_ERROR; 567 break; 568 } 569 item->flags |= CMDQ_FIRED; 570 571 if (retval == CMD_RETURN_WAIT) { 572 item->flags |= CMDQ_WAITING; 573 goto waiting; 574 } 575 items++; 576 } 577 cmdq_remove(item); 578 } 579 580 log_debug("%s %s: exit (empty)", __func__, name); 581 return (items); 582 583 waiting: 584 log_debug("%s %s: exit (wait)", __func__, name); 585 return (items); 586 } 587 588 /* Print a guard line. */ 589 void 590 cmdq_guard(struct cmdq_item *item, const char *guard, int flags) 591 { 592 struct client *c = item->client; 593 long t = item->time; 594 u_int number = item->number; 595 596 if (c != NULL && (c->flags & CLIENT_CONTROL)) 597 file_print(c, "%%%s %ld %u %d\n", guard, t, number, flags); 598 } 599 600 /* Show message from command. */ 601 void 602 cmdq_print(struct cmdq_item *item, const char *fmt, ...) 603 { 604 struct client *c = item->client; 605 struct window_pane *wp; 606 struct window_mode_entry *wme; 607 va_list ap; 608 char *tmp, *msg; 609 610 va_start(ap, fmt); 611 xvasprintf(&msg, fmt, ap); 612 va_end(ap); 613 614 log_debug("%s: %s", __func__, msg); 615 616 if (c == NULL) 617 /* nothing */; 618 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 619 if (~c->flags & CLIENT_UTF8) { 620 tmp = msg; 621 msg = utf8_sanitize(tmp); 622 free(tmp); 623 } 624 file_print(c, "%s\n", msg); 625 } else { 626 wp = c->session->curw->window->active; 627 wme = TAILQ_FIRST(&wp->modes); 628 if (wme == NULL || wme->mode != &window_view_mode) { 629 window_pane_set_mode(wp, NULL, &window_view_mode, NULL, 630 NULL); 631 } 632 window_copy_add(wp, "%s", msg); 633 } 634 635 free(msg); 636 } 637 638 /* Show error from command. */ 639 void 640 cmdq_error(struct cmdq_item *item, const char *fmt, ...) 641 { 642 struct client *c = item->client; 643 struct cmd *cmd = item->cmd; 644 va_list ap; 645 char *msg, *tmp; 646 const char *file; 647 u_int line; 648 649 va_start(ap, fmt); 650 xvasprintf(&msg, fmt, ap); 651 va_end(ap); 652 653 log_debug("%s: %s", __func__, msg); 654 655 if (c == NULL) { 656 cmd_get_source(cmd, &file, &line); 657 cfg_add_cause("%s:%u: %s", file, line, msg); 658 } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 659 if (~c->flags & CLIENT_UTF8) { 660 tmp = msg; 661 msg = utf8_sanitize(tmp); 662 free(tmp); 663 } 664 if (c->flags & CLIENT_CONTROL) 665 file_print(c, "%s\n", msg); 666 else 667 file_error(c, "%s\n", msg); 668 c->retval = 1; 669 } else { 670 *msg = toupper((u_char) *msg); 671 status_message_set(c, "%s", msg); 672 } 673 674 free(msg); 675 } 676