xref: /openbsd-src/usr.bin/tmux/cmd-queue.c (revision d4741794dd2f512d997014f8bd85fbb24d935059)
1 /* $OpenBSD: cmd-queue.c,v 1.47 2017/01/05 09:07:15 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 cmdq_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 cmdq_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 cmdq_item *item)
55 {
56 	struct cmdq_list	*queue = cmdq_get(c);
57 	struct cmdq_item	*next;
58 
59 	do {
60 		next = item->next;
61 		item->next = NULL;
62 
63 		if (c != NULL)
64 			c->references++;
65 		item->client = c;
66 
67 		item->queue = queue;
68 		TAILQ_INSERT_TAIL(queue, item, entry);
69 
70 		item = next;
71 	} while (item != NULL);
72 }
73 
74 /* Insert an item. */
75 void
76 cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item)
77 {
78 	struct client		*c = after->client;
79 	struct cmdq_list	*queue = after->queue;
80 	struct cmdq_item	*next;
81 
82 	do {
83 		next = item->next;
84 		item->next = NULL;
85 
86 		if (c != NULL)
87 			c->references++;
88 		item->client = c;
89 
90 		item->queue = queue;
91 		if (after->next != NULL)
92 			TAILQ_INSERT_AFTER(queue, after->next, item, entry);
93 		else
94 			TAILQ_INSERT_AFTER(queue, after, item, entry);
95 		after->next = item;
96 
97 		item = next;
98 	} while (item != NULL);
99 }
100 
101 /* Remove an item. */
102 static void
103 cmdq_remove(struct cmdq_item *item)
104 {
105 	if (item->formats != NULL)
106 		format_free(item->formats);
107 
108 	if (item->client != NULL)
109 		server_client_unref(item->client);
110 
111 	if (item->type == CMDQ_COMMAND)
112 		cmd_list_free(item->cmdlist);
113 
114 	TAILQ_REMOVE(item->queue, item, entry);
115 
116 	free((void *)item->name);
117 	free(item);
118 }
119 
120 /* Set command group. */
121 static u_int
122 cmdq_next_group(void)
123 {
124 	static u_int	group;
125 
126 	return (++group);
127 }
128 
129 /* Remove all subsequent items that match this item's group. */
130 static void
131 cmdq_remove_group(struct cmdq_item *item)
132 {
133 	struct cmdq_item	*this, *next;
134 
135 	this = TAILQ_NEXT(item, entry);
136 	while (this != NULL) {
137 		next = TAILQ_NEXT(this, entry);
138 		if (this->group == item->group)
139 			cmdq_remove(this);
140 		this = next;
141 	}
142 }
143 
144 /* Get a command for the command queue. */
145 struct cmdq_item *
146 cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current,
147     struct mouse_event *m, int flags)
148 {
149 	struct cmdq_item	*item, *first = NULL, *last = NULL;
150 	struct cmd		*cmd;
151 	u_int			 group = cmdq_next_group();
152 	char			*tmp;
153 
154 	TAILQ_FOREACH(cmd, &cmdlist->list, qentry) {
155 		xasprintf(&tmp, "command[%s]", cmd->entry->name);
156 
157 		item = xcalloc(1, sizeof *item);
158 		item->name = tmp;
159 		item->type = CMDQ_COMMAND;
160 
161 		item->group = group;
162 		item->flags = flags;
163 
164 		item->cmdlist = cmdlist;
165 		item->cmd = cmd;
166 
167 		if (current != NULL)
168 			cmd_find_copy_state(&item->current, current);
169 		if (m != NULL)
170 			memcpy(&item->mouse, m, sizeof item->mouse);
171 		cmdlist->references++;
172 
173 		if (first == NULL)
174 			first = item;
175 		if (last != NULL)
176 			last->next = item;
177 		last = item;
178 	}
179 	return (first);
180 }
181 
182 /* Fire command on command queue. */
183 static enum cmd_retval
184 cmdq_fire_command(struct cmdq_item *item)
185 {
186 	struct client		*c = item->client;
187 	struct cmd		*cmd = item->cmd;
188 	enum cmd_retval		 retval;
189 	const char		*name;
190 	struct cmd_find_state	*fsp, fs;
191 	int			 flags;
192 
193 	flags = !!(cmd->flags & CMD_CONTROL);
194 	cmdq_guard(item, "begin", flags);
195 
196 	if (cmd_prepare_state(cmd, item) != 0) {
197 		retval = CMD_RETURN_ERROR;
198 		goto out;
199 	}
200 	if (item->client == NULL)
201 		item->client = cmd_find_client(item, NULL, CMD_FIND_QUIET);
202 
203 	retval = cmd->entry->exec(cmd, item);
204 	if (retval == CMD_RETURN_ERROR)
205 		goto out;
206 
207 	if (cmd->entry->flags & CMD_AFTERHOOK) {
208 		name = cmd->entry->name;
209 		if (cmd_find_valid_state(&item->state.tflag))
210 			fsp = &item->state.tflag;
211 		else {
212 			if (cmd_find_current(&fs, item, CMD_FIND_QUIET) != 0)
213 				goto out;
214 			fsp = &fs;
215 		}
216 		hooks_insert(fsp->s->hooks, item, fsp, "after-%s", name);
217 	}
218 
219 out:
220 	item->client = c;
221 	if (retval == CMD_RETURN_ERROR)
222 		cmdq_guard(item, "error", flags);
223 	else
224 		cmdq_guard(item, "end", flags);
225 	return (retval);
226 }
227 
228 /* Get a callback for the command queue. */
229 struct cmdq_item *
230 cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
231 {
232 	struct cmdq_item	*item;
233 	char			*tmp;
234 
235 	xasprintf(&tmp, "callback[%s]", name);
236 
237 	item = xcalloc(1, sizeof *item);
238 	item->name = tmp;
239 	item->type = CMDQ_CALLBACK;
240 
241 	item->group = 0;
242 	item->flags = 0;
243 
244 	item->cb = cb;
245 	item->data = data;
246 
247 	return (item);
248 }
249 
250 /* Fire callback on callback queue. */
251 static enum cmd_retval
252 cmdq_fire_callback(struct cmdq_item *item)
253 {
254 	return (item->cb(item, item->data));
255 }
256 
257 /* Add a format to command queue. */
258 void
259 cmdq_format(struct cmdq_item *item, const char *key, const char *fmt, ...)
260 {
261 	va_list			 ap;
262 	struct cmdq_item	*loop;
263 	char			*value;
264 
265 	va_start(ap, fmt);
266 	xvasprintf(&value, fmt, ap);
267 	va_end(ap);
268 
269 	for (loop = item; loop != NULL; loop = item->next) {
270 		if (loop->formats == NULL)
271 			loop->formats = format_create(NULL, 0);
272 		format_add(loop->formats, key, "%s", value);
273 	}
274 
275 	free(value);
276 }
277 
278 /* Process next item on command queue. */
279 u_int
280 cmdq_next(struct client *c)
281 {
282 	struct cmdq_list	*queue = cmdq_get(c);
283 	const char		*name = cmdq_name(c);
284 	struct cmdq_item	*item;
285 	enum cmd_retval		 retval;
286 	u_int			 items = 0;
287 	static u_int		 number;
288 
289 	if (TAILQ_EMPTY(queue)) {
290 		log_debug("%s %s: empty", __func__, name);
291 		return (0);
292 	}
293 	if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) {
294 		log_debug("%s %s: waiting", __func__, name);
295 		return (0);
296 	}
297 
298 	log_debug("%s %s: enter", __func__, name);
299 	for (;;) {
300 		item = TAILQ_FIRST(queue);
301 		if (item == NULL)
302 			break;
303 		log_debug("%s %s: %s (%d), flags %x", __func__, name,
304 		    item->name, item->type, item->flags);
305 
306 		/*
307 		 * Any item with the waiting flag set waits until an external
308 		 * event clears the flag (for example, a job - look at
309 		 * run-shell).
310 		 */
311 		if (item->flags & CMDQ_WAITING)
312 			goto waiting;
313 
314 		/*
315 		 * Items are only fired once, once the fired flag is set, a
316 		 * waiting flag can only be cleared by an external event.
317 		 */
318 		if (~item->flags & CMDQ_FIRED) {
319 			item->time = time(NULL);
320 			item->number = ++number;
321 
322 			switch (item->type)
323 			{
324 			case CMDQ_COMMAND:
325 				retval = cmdq_fire_command(item);
326 
327 				/*
328 				 * If a command returns an error, remove any
329 				 * subsequent commands in the same group.
330 				 */
331 				if (retval == CMD_RETURN_ERROR)
332 					cmdq_remove_group(item);
333 				break;
334 			case CMDQ_CALLBACK:
335 				retval = cmdq_fire_callback(item);
336 				break;
337 			default:
338 				retval = CMD_RETURN_ERROR;
339 				break;
340 			}
341 			item->flags |= CMDQ_FIRED;
342 
343 			if (retval == CMD_RETURN_WAIT) {
344 				item->flags |= CMDQ_WAITING;
345 				goto waiting;
346 			}
347 			items++;
348 		}
349 		cmdq_remove(item);
350 	}
351 
352 	log_debug("%s %s: exit (empty)", __func__, name);
353 	return (items);
354 
355 waiting:
356 	log_debug("%s %s: exit (wait)", __func__, name);
357 	return (items);
358 }
359 
360 /* Print a guard line. */
361 void
362 cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
363 {
364 	struct client	*c = item->client;
365 
366 	if (c == NULL || !(c->flags & CLIENT_CONTROL))
367 		return;
368 
369 	evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard,
370 	    (long)item->time, item->number, flags);
371 	server_client_push_stdout(c);
372 }
373 
374 /* Show message from command. */
375 void
376 cmdq_print(struct cmdq_item *item, const char *fmt, ...)
377 {
378 	struct client	*c = item->client;
379 	struct window	*w;
380 	va_list		 ap;
381 	char		*tmp, *msg;
382 
383 	va_start(ap, fmt);
384 
385 	if (c == NULL)
386 		/* nothing */;
387 	else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
388 		if (~c->flags & CLIENT_UTF8) {
389 			xvasprintf(&tmp, fmt, ap);
390 			msg = utf8_sanitize(tmp);
391 			free(tmp);
392 			evbuffer_add(c->stdout_data, msg, strlen(msg));
393 			free(msg);
394 		} else
395 			evbuffer_add_vprintf(c->stdout_data, fmt, ap);
396 		evbuffer_add(c->stdout_data, "\n", 1);
397 		server_client_push_stdout(c);
398 	} else {
399 		w = c->session->curw->window;
400 		if (w->active->mode != &window_copy_mode) {
401 			window_pane_reset_mode(w->active);
402 			window_pane_set_mode(w->active, &window_copy_mode);
403 			window_copy_init_for_output(w->active);
404 		}
405 		window_copy_vadd(w->active, fmt, ap);
406 	}
407 
408 	va_end(ap);
409 }
410 
411 /* Show error from command. */
412 void
413 cmdq_error(struct cmdq_item *item, const char *fmt, ...)
414 {
415 	struct client	*c = item->client;
416 	struct cmd	*cmd = item->cmd;
417 	va_list		 ap;
418 	char		*msg;
419 	size_t		 msglen;
420 	char		*tmp;
421 
422 	va_start(ap, fmt);
423 	msglen = xvasprintf(&msg, fmt, ap);
424 	va_end(ap);
425 
426 	if (c == NULL)
427 		cfg_add_cause("%s:%u: %s", cmd->file, cmd->line, msg);
428 	else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
429 		if (~c->flags & CLIENT_UTF8) {
430 			tmp = msg;
431 			msg = utf8_sanitize(tmp);
432 			free(tmp);
433 			msglen = strlen(msg);
434 		}
435 		evbuffer_add(c->stderr_data, msg, msglen);
436 		evbuffer_add(c->stderr_data, "\n", 1);
437 		server_client_push_stderr(c);
438 		c->retval = 1;
439 	} else {
440 		*msg = toupper((u_char) *msg);
441 		status_message_set(c, "%s", msg);
442 	}
443 
444 	free(msg);
445 }
446