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