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