1 /* $OpenBSD: cmd-queue.c,v 1.43 2016/10/16 17:55:14 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 cmd_q_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 cmd_q_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 cmd_q *cmdq) 55 { 56 struct cmd_q_list *queue = cmdq_get(c); 57 struct cmd_q *next; 58 59 do { 60 next = cmdq->next; 61 cmdq->next = NULL; 62 63 if (c != NULL) 64 c->references++; 65 cmdq->client = c; 66 67 cmdq->queue = queue; 68 TAILQ_INSERT_TAIL(queue, cmdq, entry); 69 70 cmdq = next; 71 } while (cmdq != NULL); 72 } 73 74 /* Insert an item. */ 75 void 76 cmdq_insert_after(struct cmd_q *after, struct cmd_q *cmdq) 77 { 78 struct client *c = after->client; 79 struct cmd_q_list *queue = after->queue; 80 struct cmd_q *next; 81 82 do { 83 next = cmdq->next; 84 cmdq->next = NULL; 85 86 if (c != NULL) 87 c->references++; 88 cmdq->client = c; 89 90 cmdq->queue = queue; 91 if (after->next != NULL) 92 TAILQ_INSERT_AFTER(queue, after->next, cmdq, entry); 93 else 94 TAILQ_INSERT_AFTER(queue, after, cmdq, entry); 95 after->next = cmdq; 96 97 cmdq = next; 98 } while (cmdq != NULL); 99 } 100 101 /* Remove an item. */ 102 static void 103 cmdq_remove(struct cmd_q *cmdq) 104 { 105 free((void *)cmdq->hook); 106 107 if (cmdq->client != NULL) 108 server_client_unref(cmdq->client); 109 110 if (cmdq->type == CMD_Q_COMMAND) 111 cmd_list_free(cmdq->cmdlist); 112 113 TAILQ_REMOVE(cmdq->queue, cmdq, entry); 114 free(cmdq); 115 } 116 117 /* Set command group. */ 118 static u_int 119 cmdq_next_group(void) 120 { 121 static u_int group; 122 123 return (++group); 124 } 125 126 /* Remove all subsequent items that match this item's group. */ 127 static void 128 cmdq_remove_group(struct cmd_q *cmdq) 129 { 130 struct cmd_q *this, *next; 131 132 this = TAILQ_NEXT(cmdq, entry); 133 while (this != NULL) { 134 next = TAILQ_NEXT(this, entry); 135 if (this->group == cmdq->group) 136 cmdq_remove(this); 137 this = next; 138 } 139 } 140 141 /* Get a command for the command queue. */ 142 struct cmd_q * 143 cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current, 144 struct mouse_event *m, int flags) 145 { 146 struct cmd_q *cmdq, *first = NULL, *last = NULL; 147 struct cmd *cmd; 148 u_int group = cmdq_next_group(); 149 150 TAILQ_FOREACH(cmd, &cmdlist->list, qentry) { 151 cmdq = xcalloc(1, sizeof *cmdq); 152 cmdq->type = CMD_Q_COMMAND; 153 cmdq->group = group; 154 cmdq->flags = flags; 155 156 cmdq->cmdlist = cmdlist; 157 cmdq->cmd = cmd; 158 159 if (current != NULL) 160 cmd_find_copy_state(&cmdq->current, current); 161 if (m != NULL) 162 memcpy(&cmdq->mouse, m, sizeof cmdq->mouse); 163 cmdlist->references++; 164 165 if (first == NULL) 166 first = cmdq; 167 if (last != NULL) 168 last->next = cmdq; 169 last = cmdq; 170 } 171 return (first); 172 } 173 174 /* Fire command on command queue. */ 175 static enum cmd_retval 176 cmdq_fire_command(struct cmd_q *cmdq) 177 { 178 struct client *c = cmdq->client; 179 struct cmd *cmd = cmdq->cmd; 180 enum cmd_retval retval; 181 const char *name; 182 struct cmd_find_state *fsp, fs; 183 int flags; 184 185 flags = !!(cmd->flags & CMD_CONTROL); 186 cmdq_guard(cmdq, "begin", flags); 187 188 if (cmd_prepare_state(cmd, cmdq) != 0) { 189 retval = CMD_RETURN_ERROR; 190 goto out; 191 } 192 if (cmdq->client == NULL) 193 cmdq->client = cmd_find_client(cmdq, NULL, CMD_FIND_QUIET); 194 195 retval = cmd->entry->exec(cmd, cmdq); 196 if (retval == CMD_RETURN_ERROR) 197 goto out; 198 199 if (cmd->entry->flags & CMD_AFTERHOOK) { 200 name = cmd->entry->name; 201 if (cmd_find_valid_state(&cmdq->state.tflag)) 202 fsp = &cmdq->state.tflag; 203 else { 204 if (cmd_find_current(&fs, cmdq, CMD_FIND_QUIET) != 0) 205 goto out; 206 fsp = &fs; 207 } 208 hooks_insert(fsp->s->hooks, cmdq, fsp, "after-%s", name); 209 } 210 211 out: 212 cmdq->client = c; 213 if (retval == CMD_RETURN_ERROR) 214 cmdq_guard(cmdq, "error", flags); 215 else 216 cmdq_guard(cmdq, "end", flags); 217 return (retval); 218 } 219 220 /* Get a callback for the command queue. */ 221 struct cmd_q * 222 cmdq_get_callback(cmd_q_cb cb, void *data) 223 { 224 struct cmd_q *cmdq; 225 226 cmdq = xcalloc(1, sizeof *cmdq); 227 cmdq->type = CMD_Q_CALLBACK; 228 cmdq->group = 0; 229 cmdq->flags = 0; 230 231 cmdq->cb = cb; 232 cmdq->data = data; 233 234 return (cmdq); 235 } 236 237 /* Fire callback on callback queue. */ 238 static enum cmd_retval 239 cmdq_fire_callback(struct cmd_q *cmdq) 240 { 241 return (cmdq->cb(cmdq, cmdq->data)); 242 } 243 244 /* Process next item on command queue. */ 245 u_int 246 cmdq_next(struct client *c) 247 { 248 struct cmd_q_list *queue = cmdq_get(c); 249 const char *name = cmdq_name(c); 250 struct cmd_q *cmdq; 251 enum cmd_retval retval; 252 u_int items = 0; 253 static u_int number; 254 255 if (TAILQ_EMPTY(queue)) { 256 log_debug("%s %s: empty", __func__, name); 257 return (0); 258 } 259 if (TAILQ_FIRST(queue)->flags & CMD_Q_WAITING) { 260 log_debug("%s %s: waiting", __func__, name); 261 return (0); 262 } 263 264 log_debug("%s %s: enter", __func__, name); 265 for (;;) { 266 cmdq = TAILQ_FIRST(queue); 267 if (cmdq == NULL) 268 break; 269 log_debug("%s %s: type %d, flags %x", __func__, name, 270 cmdq->type, cmdq->flags); 271 272 /* 273 * Any item with the waiting flag set waits until an external 274 * event clears the flag (for example, a job - look at 275 * run-shell). 276 */ 277 if (cmdq->flags & CMD_Q_WAITING) 278 goto waiting; 279 280 /* 281 * Items are only fired once, once the fired flag is set, a 282 * waiting flag can only be cleared by an external event. 283 */ 284 if (~cmdq->flags & CMD_Q_FIRED) { 285 cmdq->time = time(NULL); 286 cmdq->number = ++number; 287 288 switch (cmdq->type) 289 { 290 case CMD_Q_COMMAND: 291 retval = cmdq_fire_command(cmdq); 292 293 /* 294 * If a command returns an error, remove any 295 * subsequent commands in the same group. 296 */ 297 if (retval == CMD_RETURN_ERROR) 298 cmdq_remove_group(cmdq); 299 break; 300 case CMD_Q_CALLBACK: 301 retval = cmdq_fire_callback(cmdq); 302 break; 303 default: 304 retval = CMD_RETURN_ERROR; 305 break; 306 } 307 cmdq->flags |= CMD_Q_FIRED; 308 309 if (retval == CMD_RETURN_WAIT) { 310 cmdq->flags |= CMD_Q_WAITING; 311 goto waiting; 312 } 313 items++; 314 } 315 cmdq_remove(cmdq); 316 } 317 318 log_debug("%s %s: exit (empty)", __func__, name); 319 return (items); 320 321 waiting: 322 log_debug("%s %s: exit (wait)", __func__, name); 323 return (items); 324 } 325 326 /* Print a guard line. */ 327 void 328 cmdq_guard(struct cmd_q *cmdq, const char *guard, int flags) 329 { 330 struct client *c = cmdq->client; 331 332 if (c == NULL || !(c->flags & CLIENT_CONTROL)) 333 return; 334 335 evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard, 336 (long)cmdq->time, cmdq->number, flags); 337 server_client_push_stdout(c); 338 } 339 340 /* Show message from command. */ 341 void 342 cmdq_print(struct cmd_q *cmdq, const char *fmt, ...) 343 { 344 struct client *c = cmdq->client; 345 struct window *w; 346 va_list ap; 347 char *tmp, *msg; 348 349 va_start(ap, fmt); 350 351 if (c == NULL) 352 /* nothing */; 353 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 354 if (~c->flags & CLIENT_UTF8) { 355 xvasprintf(&tmp, fmt, ap); 356 msg = utf8_sanitize(tmp); 357 free(tmp); 358 evbuffer_add(c->stdout_data, msg, strlen(msg)); 359 free(msg); 360 } else 361 evbuffer_add_vprintf(c->stdout_data, fmt, ap); 362 evbuffer_add(c->stdout_data, "\n", 1); 363 server_client_push_stdout(c); 364 } else { 365 w = c->session->curw->window; 366 if (w->active->mode != &window_copy_mode) { 367 window_pane_reset_mode(w->active); 368 window_pane_set_mode(w->active, &window_copy_mode); 369 window_copy_init_for_output(w->active); 370 } 371 window_copy_vadd(w->active, fmt, ap); 372 } 373 374 va_end(ap); 375 } 376 377 /* Show error from command. */ 378 void 379 cmdq_error(struct cmd_q *cmdq, const char *fmt, ...) 380 { 381 struct client *c = cmdq->client; 382 struct cmd *cmd = cmdq->cmd; 383 va_list ap; 384 char *msg; 385 size_t msglen; 386 char *tmp; 387 388 va_start(ap, fmt); 389 msglen = xvasprintf(&msg, fmt, ap); 390 va_end(ap); 391 392 if (c == NULL) 393 cfg_add_cause("%s:%u: %s", cmd->file, cmd->line, msg); 394 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 395 if (~c->flags & CLIENT_UTF8) { 396 tmp = msg; 397 msg = utf8_sanitize(tmp); 398 free(tmp); 399 msglen = strlen(msg); 400 } 401 evbuffer_add(c->stderr_data, msg, msglen); 402 evbuffer_add(c->stderr_data, "\n", 1); 403 server_client_push_stderr(c); 404 c->retval = 1; 405 } else { 406 *msg = toupper((u_char) *msg); 407 status_message_set(c, "%s", msg); 408 } 409 410 free(msg); 411 } 412