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