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