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