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