1 /* $OpenBSD: cmd-queue.c,v 1.46 2016/10/18 08:46: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 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 279 /* Process next item on command queue. */ 280 u_int 281 cmdq_next(struct client *c) 282 { 283 struct cmdq_list *queue = cmdq_get(c); 284 const char *name = cmdq_name(c); 285 struct cmdq_item *item; 286 enum cmd_retval retval; 287 u_int items = 0; 288 static u_int number; 289 290 if (TAILQ_EMPTY(queue)) { 291 log_debug("%s %s: empty", __func__, name); 292 return (0); 293 } 294 if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) { 295 log_debug("%s %s: waiting", __func__, name); 296 return (0); 297 } 298 299 log_debug("%s %s: enter", __func__, name); 300 for (;;) { 301 item = TAILQ_FIRST(queue); 302 if (item == NULL) 303 break; 304 log_debug("%s %s: %s (%d), flags %x", __func__, name, 305 item->name, item->type, item->flags); 306 307 /* 308 * Any item with the waiting flag set waits until an external 309 * event clears the flag (for example, a job - look at 310 * run-shell). 311 */ 312 if (item->flags & CMDQ_WAITING) 313 goto waiting; 314 315 /* 316 * Items are only fired once, once the fired flag is set, a 317 * waiting flag can only be cleared by an external event. 318 */ 319 if (~item->flags & CMDQ_FIRED) { 320 item->time = time(NULL); 321 item->number = ++number; 322 323 switch (item->type) 324 { 325 case CMDQ_COMMAND: 326 retval = cmdq_fire_command(item); 327 328 /* 329 * If a command returns an error, remove any 330 * subsequent commands in the same group. 331 */ 332 if (retval == CMD_RETURN_ERROR) 333 cmdq_remove_group(item); 334 break; 335 case CMDQ_CALLBACK: 336 retval = cmdq_fire_callback(item); 337 break; 338 default: 339 retval = CMD_RETURN_ERROR; 340 break; 341 } 342 item->flags |= CMDQ_FIRED; 343 344 if (retval == CMD_RETURN_WAIT) { 345 item->flags |= CMDQ_WAITING; 346 goto waiting; 347 } 348 items++; 349 } 350 cmdq_remove(item); 351 } 352 353 log_debug("%s %s: exit (empty)", __func__, name); 354 return (items); 355 356 waiting: 357 log_debug("%s %s: exit (wait)", __func__, name); 358 return (items); 359 } 360 361 /* Print a guard line. */ 362 void 363 cmdq_guard(struct cmdq_item *item, const char *guard, int flags) 364 { 365 struct client *c = item->client; 366 367 if (c == NULL || !(c->flags & CLIENT_CONTROL)) 368 return; 369 370 evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard, 371 (long)item->time, item->number, flags); 372 server_client_push_stdout(c); 373 } 374 375 /* Show message from command. */ 376 void 377 cmdq_print(struct cmdq_item *item, const char *fmt, ...) 378 { 379 struct client *c = item->client; 380 struct window *w; 381 va_list ap; 382 char *tmp, *msg; 383 384 va_start(ap, fmt); 385 386 if (c == NULL) 387 /* nothing */; 388 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 389 if (~c->flags & CLIENT_UTF8) { 390 xvasprintf(&tmp, fmt, ap); 391 msg = utf8_sanitize(tmp); 392 free(tmp); 393 evbuffer_add(c->stdout_data, msg, strlen(msg)); 394 free(msg); 395 } else 396 evbuffer_add_vprintf(c->stdout_data, fmt, ap); 397 evbuffer_add(c->stdout_data, "\n", 1); 398 server_client_push_stdout(c); 399 } else { 400 w = c->session->curw->window; 401 if (w->active->mode != &window_copy_mode) { 402 window_pane_reset_mode(w->active); 403 window_pane_set_mode(w->active, &window_copy_mode); 404 window_copy_init_for_output(w->active); 405 } 406 window_copy_vadd(w->active, fmt, ap); 407 } 408 409 va_end(ap); 410 } 411 412 /* Show error from command. */ 413 void 414 cmdq_error(struct cmdq_item *item, const char *fmt, ...) 415 { 416 struct client *c = item->client; 417 struct cmd *cmd = item->cmd; 418 va_list ap; 419 char *msg; 420 size_t msglen; 421 char *tmp; 422 423 va_start(ap, fmt); 424 msglen = xvasprintf(&msg, fmt, ap); 425 va_end(ap); 426 427 if (c == NULL) 428 cfg_add_cause("%s:%u: %s", cmd->file, cmd->line, msg); 429 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 430 if (~c->flags & CLIENT_UTF8) { 431 tmp = msg; 432 msg = utf8_sanitize(tmp); 433 free(tmp); 434 msglen = strlen(msg); 435 } 436 evbuffer_add(c->stderr_data, msg, msglen); 437 evbuffer_add(c->stderr_data, "\n", 1); 438 server_client_push_stderr(c); 439 c->retval = 1; 440 } else { 441 *msg = toupper((u_char) *msg); 442 status_message_set(c, "%s", msg); 443 } 444 445 free(msg); 446 } 447