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