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