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