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