xref: /openbsd-src/usr.bin/tmux/cmd-queue.c (revision ba06422051ccbb677d8784759ac8e114b0db67e3)
1 /* $OpenBSD: cmd-queue.c,v 1.46 2016/10/18 08:46:43 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 
279 /* Process next item on command queue. */
280 u_int
281 cmdq_next(struct client *c)
282 {
283 	struct cmdq_list	*queue = cmdq_get(c);
284 	const char		*name = cmdq_name(c);
285 	struct cmdq_item	*item;
286 	enum cmd_retval		 retval;
287 	u_int			 items = 0;
288 	static u_int		 number;
289 
290 	if (TAILQ_EMPTY(queue)) {
291 		log_debug("%s %s: empty", __func__, name);
292 		return (0);
293 	}
294 	if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) {
295 		log_debug("%s %s: waiting", __func__, name);
296 		return (0);
297 	}
298 
299 	log_debug("%s %s: enter", __func__, name);
300 	for (;;) {
301 		item = TAILQ_FIRST(queue);
302 		if (item == NULL)
303 			break;
304 		log_debug("%s %s: %s (%d), flags %x", __func__, name,
305 		    item->name, item->type, item->flags);
306 
307 		/*
308 		 * Any item with the waiting flag set waits until an external
309 		 * event clears the flag (for example, a job - look at
310 		 * run-shell).
311 		 */
312 		if (item->flags & CMDQ_WAITING)
313 			goto waiting;
314 
315 		/*
316 		 * Items are only fired once, once the fired flag is set, a
317 		 * waiting flag can only be cleared by an external event.
318 		 */
319 		if (~item->flags & CMDQ_FIRED) {
320 			item->time = time(NULL);
321 			item->number = ++number;
322 
323 			switch (item->type)
324 			{
325 			case CMDQ_COMMAND:
326 				retval = cmdq_fire_command(item);
327 
328 				/*
329 				 * If a command returns an error, remove any
330 				 * subsequent commands in the same group.
331 				 */
332 				if (retval == CMD_RETURN_ERROR)
333 					cmdq_remove_group(item);
334 				break;
335 			case CMDQ_CALLBACK:
336 				retval = cmdq_fire_callback(item);
337 				break;
338 			default:
339 				retval = CMD_RETURN_ERROR;
340 				break;
341 			}
342 			item->flags |= CMDQ_FIRED;
343 
344 			if (retval == CMD_RETURN_WAIT) {
345 				item->flags |= CMDQ_WAITING;
346 				goto waiting;
347 			}
348 			items++;
349 		}
350 		cmdq_remove(item);
351 	}
352 
353 	log_debug("%s %s: exit (empty)", __func__, name);
354 	return (items);
355 
356 waiting:
357 	log_debug("%s %s: exit (wait)", __func__, name);
358 	return (items);
359 }
360 
361 /* Print a guard line. */
362 void
363 cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
364 {
365 	struct client	*c = item->client;
366 
367 	if (c == NULL || !(c->flags & CLIENT_CONTROL))
368 		return;
369 
370 	evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard,
371 	    (long)item->time, item->number, flags);
372 	server_client_push_stdout(c);
373 }
374 
375 /* Show message from command. */
376 void
377 cmdq_print(struct cmdq_item *item, const char *fmt, ...)
378 {
379 	struct client	*c = item->client;
380 	struct window	*w;
381 	va_list		 ap;
382 	char		*tmp, *msg;
383 
384 	va_start(ap, fmt);
385 
386 	if (c == NULL)
387 		/* nothing */;
388 	else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
389 		if (~c->flags & CLIENT_UTF8) {
390 			xvasprintf(&tmp, fmt, ap);
391 			msg = utf8_sanitize(tmp);
392 			free(tmp);
393 			evbuffer_add(c->stdout_data, msg, strlen(msg));
394 			free(msg);
395 		} else
396 			evbuffer_add_vprintf(c->stdout_data, fmt, ap);
397 		evbuffer_add(c->stdout_data, "\n", 1);
398 		server_client_push_stdout(c);
399 	} else {
400 		w = c->session->curw->window;
401 		if (w->active->mode != &window_copy_mode) {
402 			window_pane_reset_mode(w->active);
403 			window_pane_set_mode(w->active, &window_copy_mode);
404 			window_copy_init_for_output(w->active);
405 		}
406 		window_copy_vadd(w->active, fmt, ap);
407 	}
408 
409 	va_end(ap);
410 }
411 
412 /* Show error from command. */
413 void
414 cmdq_error(struct cmdq_item *item, const char *fmt, ...)
415 {
416 	struct client	*c = item->client;
417 	struct cmd	*cmd = item->cmd;
418 	va_list		 ap;
419 	char		*msg;
420 	size_t		 msglen;
421 	char		*tmp;
422 
423 	va_start(ap, fmt);
424 	msglen = xvasprintf(&msg, fmt, ap);
425 	va_end(ap);
426 
427 	if (c == NULL)
428 		cfg_add_cause("%s:%u: %s", cmd->file, cmd->line, msg);
429 	else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
430 		if (~c->flags & CLIENT_UTF8) {
431 			tmp = msg;
432 			msg = utf8_sanitize(tmp);
433 			free(tmp);
434 			msglen = strlen(msg);
435 		}
436 		evbuffer_add(c->stderr_data, msg, msglen);
437 		evbuffer_add(c->stderr_data, "\n", 1);
438 		server_client_push_stderr(c);
439 		c->retval = 1;
440 	} else {
441 		*msg = toupper((u_char) *msg);
442 		status_message_set(c, "%s", msg);
443 	}
444 
445 	free(msg);
446 }
447