1 /* $OpenBSD: cmd-queue.c,v 1.36 2016/04/29 14:05:24 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 static enum cmd_retval cmdq_continue_one(struct cmd_q *); 29 30 /* Create new command queue. */ 31 struct cmd_q * 32 cmdq_new(struct client *c) 33 { 34 struct cmd_q *cmdq; 35 36 cmdq = xcalloc(1, sizeof *cmdq); 37 cmdq->references = 1; 38 cmdq->flags = 0; 39 40 cmdq->client = c; 41 cmdq->client_exit = -1; 42 43 TAILQ_INIT(&cmdq->queue); 44 cmdq->item = NULL; 45 cmdq->cmd = NULL; 46 47 cmd_find_clear_state(&cmdq->current, NULL, 0); 48 cmdq->parent = NULL; 49 50 return (cmdq); 51 } 52 53 /* Free command queue */ 54 int 55 cmdq_free(struct cmd_q *cmdq) 56 { 57 if (--cmdq->references != 0) { 58 if (cmdq->flags & CMD_Q_DEAD) 59 return (1); 60 return (0); 61 } 62 63 cmdq_flush(cmdq); 64 free(cmdq); 65 return (1); 66 } 67 68 /* Show message from command. */ 69 void 70 cmdq_print(struct cmd_q *cmdq, const char *fmt, ...) 71 { 72 struct client *c = cmdq->client; 73 struct window *w; 74 va_list ap; 75 char *tmp, *msg; 76 77 va_start(ap, fmt); 78 79 if (c == NULL) 80 /* nothing */; 81 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 82 if (~c->flags & CLIENT_UTF8) { 83 vasprintf(&tmp, fmt, ap); 84 msg = utf8_sanitize(tmp); 85 free(tmp); 86 evbuffer_add(c->stdout_data, msg, strlen(msg)); 87 free(msg); 88 } else 89 evbuffer_add_vprintf(c->stdout_data, fmt, ap); 90 evbuffer_add(c->stdout_data, "\n", 1); 91 server_client_push_stdout(c); 92 } else { 93 w = c->session->curw->window; 94 if (w->active->mode != &window_copy_mode) { 95 window_pane_reset_mode(w->active); 96 window_pane_set_mode(w->active, &window_copy_mode); 97 window_copy_init_for_output(w->active); 98 } 99 window_copy_vadd(w->active, fmt, ap); 100 } 101 102 va_end(ap); 103 } 104 105 /* Show error from command. */ 106 void 107 cmdq_error(struct cmd_q *cmdq, const char *fmt, ...) 108 { 109 struct client *c = cmdq->client; 110 struct cmd *cmd = cmdq->cmd; 111 va_list ap; 112 char *msg; 113 size_t msglen; 114 char *tmp; 115 116 va_start(ap, fmt); 117 msglen = xvasprintf(&msg, fmt, ap); 118 va_end(ap); 119 120 if (c == NULL) 121 cfg_add_cause("%s:%u: %s", cmd->file, cmd->line, msg); 122 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { 123 if (~c->flags & CLIENT_UTF8) { 124 tmp = msg; 125 msg = utf8_sanitize(tmp); 126 free(tmp); 127 msglen = strlen(msg); 128 } 129 evbuffer_add(c->stderr_data, msg, msglen); 130 evbuffer_add(c->stderr_data, "\n", 1); 131 server_client_push_stderr(c); 132 c->retval = 1; 133 } else { 134 *msg = toupper((u_char) *msg); 135 status_message_set(c, "%s", msg); 136 } 137 138 free(msg); 139 } 140 141 /* Print a guard line. */ 142 void 143 cmdq_guard(struct cmd_q *cmdq, const char *guard, int flags) 144 { 145 struct client *c = cmdq->client; 146 147 if (c == NULL || !(c->flags & CLIENT_CONTROL)) 148 return; 149 150 evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard, 151 (long) cmdq->time, cmdq->number, flags); 152 server_client_push_stdout(c); 153 } 154 155 /* Add command list to queue and begin processing if needed. */ 156 void 157 cmdq_run(struct cmd_q *cmdq, struct cmd_list *cmdlist, struct mouse_event *m) 158 { 159 cmdq_append(cmdq, cmdlist, m); 160 161 if (cmdq->item == NULL) { 162 cmdq->cmd = NULL; 163 cmdq_continue(cmdq); 164 } 165 } 166 167 /* Add command list to queue. */ 168 void 169 cmdq_append(struct cmd_q *cmdq, struct cmd_list *cmdlist, struct mouse_event *m) 170 { 171 struct cmd_q_item *item; 172 173 item = xcalloc(1, sizeof *item); 174 item->cmdlist = cmdlist; 175 TAILQ_INSERT_TAIL(&cmdq->queue, item, qentry); 176 cmdlist->references++; 177 178 if (m != NULL) 179 memcpy(&item->mouse, m, sizeof item->mouse); 180 else 181 item->mouse.valid = 0; 182 } 183 184 /* Process one command. */ 185 static enum cmd_retval 186 cmdq_continue_one(struct cmd_q *cmdq) 187 { 188 struct cmd *cmd = cmdq->cmd; 189 const char *name = cmd->entry->name; 190 struct session *s; 191 struct hooks *hooks; 192 enum cmd_retval retval; 193 char *tmp; 194 int flags = !!(cmd->flags & CMD_CONTROL); 195 196 tmp = cmd_print(cmd); 197 log_debug("cmdq %p: %s", cmdq, tmp); 198 free(tmp); 199 200 cmdq->time = time(NULL); 201 cmdq->number++; 202 203 if (~cmdq->flags & CMD_Q_REENTRY) 204 cmdq_guard(cmdq, "begin", flags); 205 206 if (cmd_prepare_state(cmd, cmdq, cmdq->parent) != 0) 207 goto error; 208 209 if (~cmdq->flags & CMD_Q_NOHOOKS) { 210 s = NULL; 211 if (cmdq->state.tflag.s != NULL) 212 s = cmdq->state.tflag.s; 213 else if (cmdq->state.sflag.s != NULL) 214 s = cmdq->state.sflag.s; 215 else if (cmdq->state.c != NULL) 216 s = cmdq->state.c->session; 217 if (s != NULL) 218 hooks = s->hooks; 219 else 220 hooks = global_hooks; 221 222 if (~cmdq->flags & CMD_Q_REENTRY) { 223 cmdq->flags |= CMD_Q_REENTRY; 224 if (hooks_wait(hooks, cmdq, NULL, 225 "before-%s", name) == 0) 226 return (CMD_RETURN_WAIT); 227 if (cmd_prepare_state(cmd, cmdq, cmdq->parent) != 0) 228 goto error; 229 } 230 } else 231 hooks = NULL; 232 cmdq->flags &= ~CMD_Q_REENTRY; 233 234 retval = cmd->entry->exec(cmd, cmdq); 235 if (retval == CMD_RETURN_ERROR) 236 goto error; 237 238 if (hooks != NULL && hooks_wait(hooks, cmdq, NULL, 239 "after-%s", name) == 0) 240 retval = CMD_RETURN_WAIT; 241 cmdq_guard(cmdq, "end", flags); 242 243 return (retval); 244 245 error: 246 cmdq_guard(cmdq, "error", flags); 247 cmdq->flags &= ~CMD_Q_REENTRY; 248 return (CMD_RETURN_ERROR); 249 } 250 251 /* Continue processing command queue. Returns 1 if finishes empty. */ 252 int 253 cmdq_continue(struct cmd_q *cmdq) 254 { 255 struct client *c = cmdq->client; 256 struct cmd_q_item *next; 257 enum cmd_retval retval; 258 int empty; 259 260 cmdq->references++; 261 notify_disable(); 262 263 log_debug("continuing cmdq %p: flags %#x, client %p", cmdq, cmdq->flags, 264 c); 265 266 empty = TAILQ_EMPTY(&cmdq->queue); 267 if (empty) 268 goto empty; 269 270 /* 271 * If the command isn't in the middle of running hooks (due to 272 * CMD_RETURN_WAIT), move onto the next command; otherwise, leave the 273 * state of the queue as it is. 274 */ 275 if (~cmdq->flags & CMD_Q_REENTRY) { 276 if (cmdq->item == NULL) { 277 cmdq->item = TAILQ_FIRST(&cmdq->queue); 278 cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list); 279 } else 280 cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry); 281 } 282 283 do { 284 while (cmdq->cmd != NULL) { 285 retval = cmdq_continue_one(cmdq); 286 if (retval == CMD_RETURN_ERROR) 287 break; 288 if (retval == CMD_RETURN_WAIT) 289 goto out; 290 if (retval == CMD_RETURN_STOP) { 291 cmdq_flush(cmdq); 292 goto empty; 293 } 294 cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry); 295 } 296 next = TAILQ_NEXT(cmdq->item, qentry); 297 298 TAILQ_REMOVE(&cmdq->queue, cmdq->item, qentry); 299 cmd_list_free(cmdq->item->cmdlist); 300 free(cmdq->item); 301 302 cmdq->item = next; 303 if (cmdq->item != NULL) 304 cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list); 305 } while (cmdq->item != NULL); 306 307 empty: 308 if (cmdq->client_exit > 0) 309 cmdq->client->flags |= CLIENT_EXIT; 310 if (cmdq->emptyfn != NULL) 311 cmdq->emptyfn(cmdq); 312 empty = 1; 313 314 out: 315 notify_enable(); 316 cmdq_free(cmdq); 317 318 return (empty); 319 } 320 321 /* Flush command queue. */ 322 void 323 cmdq_flush(struct cmd_q *cmdq) 324 { 325 struct cmd_q_item *item, *item1; 326 327 TAILQ_FOREACH_SAFE(item, &cmdq->queue, qentry, item1) { 328 TAILQ_REMOVE(&cmdq->queue, item, qentry); 329 cmd_list_free(item->cmdlist); 330 free(item); 331 } 332 cmdq->item = NULL; 333 } 334 335