1 /* $OpenBSD: cmd-queue.c,v 1.65 2019/05/03 18:59:58 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 cmd *cmd = item->cmd; 270 const struct cmd_entry *entry = cmd->entry; 271 enum cmd_retval retval; 272 struct cmd_find_state *fsp, fs; 273 int flags; 274 275 flags = !!(cmd->flags & CMD_CONTROL); 276 cmdq_guard(item, "begin", flags); 277 278 if (item->client == NULL) 279 item->client = cmd_find_client(item, NULL, 1); 280 retval = cmdq_find_flag(item, &item->source, &entry->source); 281 if (retval == CMD_RETURN_ERROR) 282 goto out; 283 retval = cmdq_find_flag(item, &item->target, &entry->target); 284 if (retval == CMD_RETURN_ERROR) 285 goto out; 286 287 retval = entry->exec(cmd, item); 288 if (retval == CMD_RETURN_ERROR) 289 goto out; 290 291 if (entry->flags & CMD_AFTERHOOK) { 292 if (cmd_find_valid_state(&item->target)) 293 fsp = &item->target; 294 else if (cmd_find_valid_state(&item->shared->current)) 295 fsp = &item->shared->current; 296 else if (cmd_find_from_client(&fs, item->client, 0) == 0) 297 fsp = &fs; 298 else 299 goto out; 300 cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name); 301 } 302 303 out: 304 item->client = c; 305 if (retval == CMD_RETURN_ERROR) 306 cmdq_guard(item, "error", flags); 307 else 308 cmdq_guard(item, "end", flags); 309 return (retval); 310 } 311 312 /* Get a callback for the command queue. */ 313 struct cmdq_item * 314 cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) 315 { 316 struct cmdq_item *item; 317 318 item = xcalloc(1, sizeof *item); 319 xasprintf(&item->name, "[%s/%p]", name, item); 320 item->type = CMDQ_CALLBACK; 321 322 item->group = 0; 323 item->flags = 0; 324 325 item->cb = cb; 326 item->data = data; 327 328 return (item); 329 } 330 331 /* Fire callback on callback queue. */ 332 static enum cmd_retval 333 cmdq_fire_callback(struct cmdq_item *item) 334 { 335 return (item->cb(item, item->data)); 336 } 337 338 /* Add a format to command queue. */ 339 void 340 cmdq_format(struct cmdq_item *item, const char *key, const char *fmt, ...) 341 { 342 struct cmdq_shared *shared = item->shared; 343 va_list ap; 344 char *value; 345 346 va_start(ap, fmt); 347 xvasprintf(&value, fmt, ap); 348 va_end(ap); 349 350 if (shared->formats == NULL) 351 shared->formats = format_create(NULL, NULL, FORMAT_NONE, 0); 352 format_add(shared->formats, key, "%s", value); 353 354 free(value); 355 } 356 357 /* Process next item on command queue. */ 358 u_int 359 cmdq_next(struct client *c) 360 { 361 struct cmdq_list *queue = cmdq_get(c); 362 const char *name = cmdq_name(c); 363 struct cmdq_item *item; 364 enum cmd_retval retval; 365 u_int items = 0; 366 static u_int number; 367 368 if (TAILQ_EMPTY(queue)) { 369 log_debug("%s %s: empty", __func__, name); 370 return (0); 371 } 372 if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) { 373 log_debug("%s %s: waiting", __func__, name); 374 return (0); 375 } 376 377 log_debug("%s %s: enter", __func__, name); 378 for (;;) { 379 item = TAILQ_FIRST(queue); 380 if (item == NULL) 381 break; 382 log_debug("%s %s: %s (%d), flags %x", __func__, name, 383 item->name, item->type, item->flags); 384 385 /* 386 * Any item with the waiting flag set waits until an external 387 * event clears the flag (for example, a job - look at 388 * run-shell). 389 */ 390 if (item->flags & CMDQ_WAITING) 391 goto waiting; 392 393 /* 394 * Items are only fired once, once the fired flag is set, a 395 * waiting flag can only be cleared by an external event. 396 */ 397 if (~item->flags & CMDQ_FIRED) { 398 item->time = time(NULL); 399 item->number = ++number; 400 401 switch (item->type) { 402 case CMDQ_COMMAND: 403 retval = cmdq_fire_command(item); 404 405 /* 406 * If a command returns an error, remove any 407 * subsequent commands in the same group. 408 */ 409 if (retval == CMD_RETURN_ERROR) 410 cmdq_remove_group(item); 411 break; 412 case CMDQ_CALLBACK: 413 retval = cmdq_fire_callback(item); 414 break; 415 default: 416 retval = CMD_RETURN_ERROR; 417 break; 418 } 419 item->flags |= CMDQ_FIRED; 420 421 if (retval == CMD_RETURN_WAIT) { 422 item->flags |= CMDQ_WAITING; 423 goto waiting; 424 } 425 items++; 426 } 427 cmdq_remove(item); 428 } 429 430 log_debug("%s %s: exit (empty)", __func__, name); 431 return (items); 432 433 waiting: 434 log_debug("%s %s: exit (wait)", __func__, name); 435 return (items); 436 } 437 438 /* Print a guard line. */ 439 void 440 cmdq_guard(struct cmdq_item *item, const char *guard, int flags) 441 { 442 struct client *c = item->client; 443 444 if (c == NULL || !(c->flags & CLIENT_CONTROL)) 445 return; 446 447 evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard, 448 (long)item->time, item->number, flags); 449 server_client_push_stdout(c); 450 } 451 452 /* Show message from command. */ 453 void 454 cmdq_print(struct cmdq_item *item, const char *fmt, ...) 455 { 456 struct client *c = item->client; 457 struct window_pane *wp; 458 struct window_mode_entry *wme; 459 va_list ap; 460 char *tmp, *msg; 461 462 va_start(ap, fmt); 463 464 if (c == NULL) 465 /* nothing */; 466 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 467 if (~c->flags & CLIENT_UTF8) { 468 xvasprintf(&tmp, fmt, ap); 469 msg = utf8_sanitize(tmp); 470 free(tmp); 471 evbuffer_add(c->stdout_data, msg, strlen(msg)); 472 free(msg); 473 } else 474 evbuffer_add_vprintf(c->stdout_data, fmt, ap); 475 evbuffer_add(c->stdout_data, "\n", 1); 476 server_client_push_stdout(c); 477 } else { 478 wp = c->session->curw->window->active; 479 wme = TAILQ_FIRST(&wp->modes); 480 if (wme == NULL || wme->mode != &window_view_mode) 481 window_pane_set_mode(wp, &window_view_mode, NULL, NULL); 482 window_copy_vadd(wp, fmt, ap); 483 } 484 485 va_end(ap); 486 } 487 488 /* Show error from command. */ 489 void 490 cmdq_error(struct cmdq_item *item, const char *fmt, ...) 491 { 492 struct client *c = item->client; 493 struct cmd *cmd = item->cmd; 494 va_list ap; 495 char *msg; 496 size_t msglen; 497 char *tmp; 498 499 va_start(ap, fmt); 500 msglen = xvasprintf(&msg, fmt, ap); 501 va_end(ap); 502 503 log_debug("%s: %s", __func__, msg); 504 505 if (c == NULL) 506 cfg_add_cause("%s:%u: %s", cmd->file, cmd->line, msg); 507 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 508 if (~c->flags & CLIENT_UTF8) { 509 tmp = msg; 510 msg = utf8_sanitize(tmp); 511 free(tmp); 512 msglen = strlen(msg); 513 } 514 evbuffer_add(c->stderr_data, msg, msglen); 515 evbuffer_add(c->stderr_data, "\n", 1); 516 server_client_push_stderr(c); 517 c->retval = 1; 518 } else { 519 *msg = toupper((u_char) *msg); 520 status_message_set(c, "%s", msg); 521 } 522 523 free(msg); 524 } 525