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