xref: /openbsd-src/usr.bin/tmux/cmd-queue.c (revision 9a67a5aca97a93c3bc65bc72704b221b7a5a7660)
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