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