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