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