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