xref: /openbsd-src/usr.bin/tmux/cmd-queue.c (revision 035dc73d1aa2b06694f1c8fe08a74fa3650b6b4b)
1 /* $OpenBSD: cmd-queue.c,v 1.89 2020/04/13 20:51:57 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 /* Command queue flags. */
29 #define CMDQ_FIRED 0x1
30 #define CMDQ_WAITING 0x2
31 
32 /* Command queue item type. */
33 enum cmdq_type {
34 	CMDQ_COMMAND,
35 	CMDQ_CALLBACK,
36 };
37 
38 /* Command queue item. */
39 struct cmdq_item {
40 	char			*name;
41 	struct cmdq_list	*queue;
42 	struct cmdq_item	*next;
43 
44 	struct client		*client;
45 	struct client		*target_client;
46 
47 	enum cmdq_type		 type;
48 	u_int			 group;
49 
50 	u_int			 number;
51 	time_t			 time;
52 
53 	int			 flags;
54 
55 	struct cmdq_state	*state;
56 	struct cmd_find_state	 source;
57 	struct cmd_find_state	 target;
58 
59 	struct cmd_list		*cmdlist;
60 	struct cmd		*cmd;
61 
62 	cmdq_cb			 cb;
63 	void			*data;
64 
65 	TAILQ_ENTRY(cmdq_item)	 entry;
66 };
67 TAILQ_HEAD(cmdq_list, cmdq_item);
68 
69 /*
70  * Command queue state. This is the context for commands on the command queue.
71  * It holds information about how the commands were fired (the key and flags),
72  * any additional formats for the commands, and the current default target.
73  * Multiple commands can share the same state and a command may update the
74  * default target.
75  */
76 struct cmdq_state {
77 	int			 references;
78 	int			 flags;
79 
80 	struct format_tree	*formats;
81 
82 	struct key_event	 event;
83 	struct cmd_find_state	 current;
84 };
85 
86 /* Get command queue name. */
87 static const char *
88 cmdq_name(struct client *c)
89 {
90 	static char	s[256];
91 
92 	if (c == NULL)
93 		return ("<global>");
94 	if (c->name != NULL)
95 		xsnprintf(s, sizeof s, "<%s>", c->name);
96 	else
97 		xsnprintf(s, sizeof s, "<%p>", c);
98 	return (s);
99 }
100 
101 /* Get command queue from client. */
102 static struct cmdq_list *
103 cmdq_get(struct client *c)
104 {
105 	static struct cmdq_list *global_queue;
106 
107 	if (c == NULL) {
108 		if (global_queue == NULL)
109 			global_queue = cmdq_new();
110 		return (global_queue);
111 	}
112 	return (c->queue);
113 }
114 
115 /* Create a queue. */
116 struct cmdq_list *
117 cmdq_new(void)
118 {
119 	struct cmdq_list	*queue;
120 
121 	queue = xcalloc (1, sizeof *queue);
122 	TAILQ_INIT (queue);
123 	return (queue);
124 }
125 
126 /* Free a queue. */
127 void
128 cmdq_free(struct cmdq_list *queue)
129 {
130 	if (!TAILQ_EMPTY(queue))
131 		fatalx("queue not empty");
132 	free(queue);
133 }
134 
135 /* Get item name. */
136 const char *
137 cmdq_get_name(struct cmdq_item *item)
138 {
139 	return (item->name);
140 }
141 
142 /* Get item client. */
143 struct client *
144 cmdq_get_client(struct cmdq_item *item)
145 {
146 	return (item->client);
147 }
148 
149 /* Get item target client. */
150 struct client *
151 cmdq_get_target_client(struct cmdq_item *item)
152 {
153 	return (item->target_client);
154 }
155 
156 /* Get item state. */
157 struct cmdq_state *
158 cmdq_get_state(struct cmdq_item *item)
159 {
160 	return (item->state);
161 }
162 
163 /* Get item target. */
164 struct cmd_find_state *
165 cmdq_get_target(struct cmdq_item *item)
166 {
167 	return (&item->target);
168 }
169 
170 /* Get item source. */
171 struct cmd_find_state *
172 cmdq_get_source(struct cmdq_item *item)
173 {
174 	return (&item->source);
175 }
176 
177 /* Get state event. */
178 struct key_event *
179 cmdq_get_event(struct cmdq_item *item)
180 {
181 	return (&item->state->event);
182 }
183 
184 /* Get state current target. */
185 struct cmd_find_state *
186 cmdq_get_current(struct cmdq_item *item)
187 {
188 	return (&item->state->current);
189 }
190 
191 /* Get state flags. */
192 int
193 cmdq_get_flags(struct cmdq_item *item)
194 {
195 	return (item->state->flags);
196 }
197 
198 /* Create a new state. */
199 struct cmdq_state *
200 cmdq_new_state(struct cmd_find_state *current, struct key_event *event,
201     int flags)
202 {
203 	struct cmdq_state	*state;
204 
205 	state = xcalloc(1, sizeof *state);
206 	state->references = 1;
207 	state->flags = flags;
208 
209 	if (event != NULL)
210 		memcpy(&state->event, event, sizeof state->event);
211 	else
212 		state->event.key = KEYC_NONE;
213 	if (current != NULL && cmd_find_valid_state(current))
214 		cmd_find_copy_state(&state->current, current);
215 	else
216 		cmd_find_clear_state(&state->current, 0);
217 
218 	return (state);
219 }
220 
221 /* Add a reference to a state. */
222 struct cmdq_state *
223 cmdq_link_state(struct cmdq_state *state)
224 {
225 	state->references++;
226 	return (state);
227 }
228 
229 /* Make a copy of a state. */
230 struct cmdq_state *
231 cmdq_copy_state(struct cmdq_state *state)
232 {
233 	return (cmdq_new_state(&state->current, &state->event, state->flags));
234 }
235 
236 /* Free a state. */
237 void
238 cmdq_free_state(struct cmdq_state *state)
239 {
240 	if (--state->references == 0)
241 		free(state);
242 }
243 
244 /* Add a format to command queue. */
245 void
246 cmdq_add_format(struct cmdq_state *state, const char *key, const char *fmt, ...)
247 {
248 	va_list	 ap;
249 	char	*value;
250 
251 	va_start(ap, fmt);
252 	xvasprintf(&value, fmt, ap);
253 	va_end(ap);
254 
255 	if (state->formats == NULL)
256 		state->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
257 	format_add(state->formats, key, "%s", value);
258 
259 	free(value);
260 }
261 
262 /* Merge formats from item. */
263 void
264 cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft)
265 {
266 	const struct cmd_entry	*entry;
267 
268 	if (item->cmd != NULL) {
269 		entry = cmd_get_entry (item->cmd);
270 		format_add(ft, "command", "%s", entry->name);
271 	}
272 	if (item->state->formats != NULL)
273 		format_merge(ft, item->state->formats);
274 }
275 
276 /* Append an item. */
277 struct cmdq_item *
278 cmdq_append(struct client *c, struct cmdq_item *item)
279 {
280 	struct cmdq_list	*queue = cmdq_get(c);
281 	struct cmdq_item	*next;
282 
283 	do {
284 		next = item->next;
285 		item->next = NULL;
286 
287 		if (c != NULL)
288 			c->references++;
289 		item->client = c;
290 
291 		item->queue = queue;
292 		TAILQ_INSERT_TAIL(queue, item, entry);
293 		log_debug("%s %s: %s", __func__, cmdq_name(c), item->name);
294 
295 		item = next;
296 	} while (item != NULL);
297 	return (TAILQ_LAST(queue, cmdq_list));
298 }
299 
300 /* Insert an item. */
301 struct cmdq_item *
302 cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item)
303 {
304 	struct client		*c = after->client;
305 	struct cmdq_list	*queue = after->queue;
306 	struct cmdq_item	*next;
307 
308 	do {
309 		next = item->next;
310 		item->next = after->next;
311 		after->next = item;
312 
313 		if (c != NULL)
314 			c->references++;
315 		item->client = c;
316 
317 		item->queue = queue;
318 		TAILQ_INSERT_AFTER(queue, after, item, entry);
319 		log_debug("%s %s: %s after %s", __func__, cmdq_name(c),
320 		    item->name, after->name);
321 
322 		after = item;
323 		item = next;
324 	} while (item != NULL);
325 	return (after);
326 }
327 
328 /* Insert a hook. */
329 void
330 cmdq_insert_hook(struct session *s, struct cmdq_item *item,
331     struct cmd_find_state *current, const char *fmt, ...)
332 {
333 	struct cmdq_state		*state = item->state;
334 	struct options			*oo;
335 	va_list				 ap;
336 	char				*name;
337 	struct cmdq_item		*new_item;
338 	struct cmdq_state		*new_state;
339 	struct options_entry		*o;
340 	struct options_array_item	*a;
341 	struct cmd_list			*cmdlist;
342 
343 	if (item->state->flags & CMDQ_STATE_NOHOOKS)
344 		return;
345 	if (s == NULL)
346 		oo = global_s_options;
347 	else
348 		oo = s->options;
349 
350 	va_start(ap, fmt);
351 	xvasprintf(&name, fmt, ap);
352 	va_end(ap);
353 
354 	o = options_get(oo, name);
355 	if (o == NULL) {
356 		free(name);
357 		return;
358 	}
359 	log_debug("running hook %s (parent %p)", name, item);
360 
361 	/*
362 	 * The hooks get a new state because they should not update the current
363 	 * target or formats for any subsequent commands.
364 	 */
365 	new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS);
366 	cmdq_add_format(new_state, "hook", "%s", name);
367 
368 	a = options_array_first(o);
369 	while (a != NULL) {
370 		cmdlist = options_array_item_value(a)->cmdlist;
371 		if (cmdlist != NULL) {
372 			new_item = cmdq_get_command(cmdlist, new_state);
373 			if (item != NULL)
374 				item = cmdq_insert_after(item, new_item);
375 			else
376 				item = cmdq_append(NULL, new_item);
377 		}
378 		a = options_array_next(a);
379 	}
380 
381 	cmdq_free_state(new_state);
382 	free(name);
383 }
384 
385 /* Continue processing command queue. */
386 void
387 cmdq_continue(struct cmdq_item *item)
388 {
389 	item->flags &= ~CMDQ_WAITING;
390 }
391 
392 /* Remove an item. */
393 static void
394 cmdq_remove(struct cmdq_item *item)
395 {
396 	if (item->client != NULL)
397 		server_client_unref(item->client);
398 	if (item->cmdlist != NULL)
399 		cmd_list_free(item->cmdlist);
400 	cmdq_free_state(item->state);
401 
402 	TAILQ_REMOVE(item->queue, item, entry);
403 
404 	free(item->name);
405 	free(item);
406 }
407 
408 /* Remove all subsequent items that match this item's group. */
409 static void
410 cmdq_remove_group(struct cmdq_item *item)
411 {
412 	struct cmdq_item	*this, *next;
413 
414 	if (item->group == 0)
415 		return;
416 	this = TAILQ_NEXT(item, entry);
417 	while (this != NULL) {
418 		next = TAILQ_NEXT(this, entry);
419 		if (this->group == item->group)
420 			cmdq_remove(this);
421 		this = next;
422 	}
423 }
424 
425 /* Get a command for the command queue. */
426 struct cmdq_item *
427 cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state)
428 {
429 	struct cmdq_item	*item, *first = NULL, *last = NULL;
430 	struct cmd		*cmd;
431 	const struct cmd_entry	*entry;
432 	int			 created = 0;
433 
434 	if (state == NULL) {
435 		state = cmdq_new_state(NULL, NULL, 0);
436 		created = 1;
437 	}
438 
439 	cmd = cmd_list_first(cmdlist);
440 	while (cmd != NULL) {
441 		entry = cmd_get_entry(cmd);
442 
443 		item = xcalloc(1, sizeof *item);
444 		xasprintf(&item->name, "[%s/%p]", entry->name, item);
445 		item->type = CMDQ_COMMAND;
446 
447 		item->group = cmd_get_group(cmd);
448 		item->state = cmdq_link_state(state);
449 
450 		item->cmdlist = cmdlist;
451 		item->cmd = cmd;
452 
453 		cmdlist->references++;
454 		log_debug("%s: %s group %u", __func__, item->name, item->group);
455 
456 		if (first == NULL)
457 			first = item;
458 		if (last != NULL)
459 			last->next = item;
460 		last = item;
461 
462 		cmd = cmd_list_next(cmd);
463 	}
464 
465 	if (created)
466 		cmdq_free_state(state);
467 	return (first);
468 }
469 
470 /* Fill in flag for a command. */
471 static enum cmd_retval
472 cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs,
473     const struct cmd_entry_flag *flag)
474 {
475 	const char	*value;
476 
477 	if (flag->flag == 0) {
478 		cmd_find_clear_state(fs, 0);
479 		return (CMD_RETURN_NORMAL);
480 	}
481 
482 	value = args_get(cmd_get_args(item->cmd), flag->flag);
483 	if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) {
484 		cmd_find_clear_state(fs, 0);
485 		return (CMD_RETURN_ERROR);
486 	}
487 	return (CMD_RETURN_NORMAL);
488 }
489 
490 /* Fire command on command queue. */
491 static enum cmd_retval
492 cmdq_fire_command(struct cmdq_item *item)
493 {
494 	const char		*name = cmdq_name(item->client);
495 	struct cmdq_state	*state = item->state;
496 	struct cmd		*cmd = item->cmd;
497 	struct args		*args = cmd_get_args(cmd);
498 	const struct cmd_entry	*entry = cmd_get_entry(cmd);
499 	struct client		*tc, *saved = item->client;
500 	enum cmd_retval		 retval;
501 	struct cmd_find_state	*fsp, fs;
502 	int			 flags, quiet = 0;
503 	char			*tmp;
504 
505 	if (log_get_level() > 1) {
506 		tmp = cmd_print(cmd);
507 		log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp);
508 		free(tmp);
509 	}
510 
511 	flags = !!(state->flags & CMDQ_STATE_CONTROL);
512 	cmdq_guard(item, "begin", flags);
513 
514 	if (item->client == NULL)
515 		item->client = cmd_find_client(item, NULL, 1);
516 
517 	if (entry->flags & CMD_CLIENT_CANFAIL)
518 		quiet = 1;
519 	if (entry->flags & CMD_CLIENT_CFLAG) {
520 		tc = cmd_find_client(item, args_get(args, 'c'), quiet);
521 		if (tc == NULL && !quiet) {
522 			retval = CMD_RETURN_ERROR;
523 			goto out;
524 		}
525 	} else if (entry->flags & CMD_CLIENT_TFLAG) {
526 		tc = cmd_find_client(item, args_get(args, 't'), quiet);
527 		if (tc == NULL && !quiet) {
528 			retval = CMD_RETURN_ERROR;
529 			goto out;
530 		}
531 	} else
532 		tc = cmd_find_client(item, NULL, 1);
533 	item->target_client = tc;
534 
535 	retval = cmdq_find_flag(item, &item->source, &entry->source);
536 	if (retval == CMD_RETURN_ERROR)
537 		goto out;
538 	retval = cmdq_find_flag(item, &item->target, &entry->target);
539 	if (retval == CMD_RETURN_ERROR)
540 		goto out;
541 
542 
543 	retval = entry->exec(cmd, item);
544 	if (retval == CMD_RETURN_ERROR)
545 		goto out;
546 
547 	if (entry->flags & CMD_AFTERHOOK) {
548 		if (cmd_find_valid_state(&item->target))
549 			fsp = &item->target;
550 		else if (cmd_find_valid_state(&item->state->current))
551 			fsp = &item->state->current;
552 		else if (cmd_find_from_client(&fs, item->client, 0) == 0)
553 			fsp = &fs;
554 		else
555 			goto out;
556 		cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name);
557 	}
558 
559 out:
560 	item->client = saved;
561 	if (retval == CMD_RETURN_ERROR)
562 		cmdq_guard(item, "error", flags);
563 	else
564 		cmdq_guard(item, "end", flags);
565 	return (retval);
566 }
567 
568 /* Get a callback for the command queue. */
569 struct cmdq_item *
570 cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
571 {
572 	struct cmdq_item	*item;
573 
574 	item = xcalloc(1, sizeof *item);
575 	xasprintf(&item->name, "[%s/%p]", name, item);
576 	item->type = CMDQ_CALLBACK;
577 
578 	item->group = 0;
579 	item->state = cmdq_new_state(NULL, NULL, 0);
580 
581 	item->cb = cb;
582 	item->data = data;
583 
584 	return (item);
585 }
586 
587 /* Generic error callback. */
588 static enum cmd_retval
589 cmdq_error_callback(struct cmdq_item *item, void *data)
590 {
591 	char	*error = data;
592 
593 	cmdq_error(item, "%s", error);
594 	free(error);
595 
596 	return (CMD_RETURN_NORMAL);
597 }
598 
599 /* Get an error callback for the command queue. */
600 struct cmdq_item *
601 cmdq_get_error(const char *error)
602 {
603 	return (cmdq_get_callback(cmdq_error_callback, xstrdup(error)));
604 }
605 
606 /* Fire callback on callback queue. */
607 static enum cmd_retval
608 cmdq_fire_callback(struct cmdq_item *item)
609 {
610 	return (item->cb(item, item->data));
611 }
612 
613 /* Process next item on command queue. */
614 u_int
615 cmdq_next(struct client *c)
616 {
617 	struct cmdq_list	*queue = cmdq_get(c);
618 	const char		*name = cmdq_name(c);
619 	struct cmdq_item	*item;
620 	enum cmd_retval		 retval;
621 	u_int			 items = 0;
622 	static u_int		 number;
623 
624 	if (TAILQ_EMPTY(queue)) {
625 		log_debug("%s %s: empty", __func__, name);
626 		return (0);
627 	}
628 	if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) {
629 		log_debug("%s %s: waiting", __func__, name);
630 		return (0);
631 	}
632 
633 	log_debug("%s %s: enter", __func__, name);
634 	for (;;) {
635 		item = TAILQ_FIRST(queue);
636 		if (item == NULL)
637 			break;
638 		log_debug("%s %s: %s (%d), flags %x", __func__, name,
639 		    item->name, item->type, item->flags);
640 
641 		/*
642 		 * Any item with the waiting flag set waits until an external
643 		 * event clears the flag (for example, a job - look at
644 		 * run-shell).
645 		 */
646 		if (item->flags & CMDQ_WAITING)
647 			goto waiting;
648 
649 		/*
650 		 * Items are only fired once, once the fired flag is set, a
651 		 * waiting flag can only be cleared by an external event.
652 		 */
653 		if (~item->flags & CMDQ_FIRED) {
654 			item->time = time(NULL);
655 			item->number = ++number;
656 
657 			switch (item->type) {
658 			case CMDQ_COMMAND:
659 				retval = cmdq_fire_command(item);
660 
661 				/*
662 				 * If a command returns an error, remove any
663 				 * subsequent commands in the same group.
664 				 */
665 				if (retval == CMD_RETURN_ERROR)
666 					cmdq_remove_group(item);
667 				break;
668 			case CMDQ_CALLBACK:
669 				retval = cmdq_fire_callback(item);
670 				break;
671 			default:
672 				retval = CMD_RETURN_ERROR;
673 				break;
674 			}
675 			item->flags |= CMDQ_FIRED;
676 
677 			if (retval == CMD_RETURN_WAIT) {
678 				item->flags |= CMDQ_WAITING;
679 				goto waiting;
680 			}
681 			items++;
682 		}
683 		cmdq_remove(item);
684 	}
685 
686 	log_debug("%s %s: exit (empty)", __func__, name);
687 	return (items);
688 
689 waiting:
690 	log_debug("%s %s: exit (wait)", __func__, name);
691 	return (items);
692 }
693 
694 /* Print a guard line. */
695 void
696 cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
697 {
698 	struct client	*c = item->client;
699 	long		 t = item->time;
700 	u_int		 number = item->number;
701 
702 	if (c != NULL && (c->flags & CLIENT_CONTROL))
703 		file_print(c, "%%%s %ld %u %d\n", guard, t, number, flags);
704 }
705 
706 /* Show message from command. */
707 void
708 cmdq_print(struct cmdq_item *item, const char *fmt, ...)
709 {
710 	struct client			*c = item->client;
711 	struct window_pane		*wp;
712 	struct window_mode_entry	*wme;
713 	va_list				 ap;
714 	char				*tmp, *msg;
715 
716 	va_start(ap, fmt);
717 	xvasprintf(&msg, fmt, ap);
718 	va_end(ap);
719 
720 	log_debug("%s: %s", __func__, msg);
721 
722 	if (c == NULL)
723 		/* nothing */;
724 	else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
725 		if (~c->flags & CLIENT_UTF8) {
726 			tmp = msg;
727 			msg = utf8_sanitize(tmp);
728 			free(tmp);
729 		}
730 		file_print(c, "%s\n", msg);
731 	} else {
732 		wp = c->session->curw->window->active;
733 		wme = TAILQ_FIRST(&wp->modes);
734 		if (wme == NULL || wme->mode != &window_view_mode) {
735 			window_pane_set_mode(wp, NULL, &window_view_mode, NULL,
736 			    NULL);
737 		}
738 		window_copy_add(wp, "%s", msg);
739 	}
740 
741 	free(msg);
742 }
743 
744 /* Show error from command. */
745 void
746 cmdq_error(struct cmdq_item *item, const char *fmt, ...)
747 {
748 	struct client	*c = item->client;
749 	struct cmd	*cmd = item->cmd;
750 	va_list		 ap;
751 	char		*msg, *tmp;
752 	const char	*file;
753 	u_int		 line;
754 
755 	va_start(ap, fmt);
756 	xvasprintf(&msg, fmt, ap);
757 	va_end(ap);
758 
759 	log_debug("%s: %s", __func__, msg);
760 
761 	if (c == NULL) {
762 		cmd_get_source(cmd, &file, &line);
763 		cfg_add_cause("%s:%u: %s", file, line, msg);
764 	} else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
765 		if (~c->flags & CLIENT_UTF8) {
766 			tmp = msg;
767 			msg = utf8_sanitize(tmp);
768 			free(tmp);
769 		}
770 		if (c->flags & CLIENT_CONTROL)
771 			file_print(c, "%s\n", msg);
772 		else
773 			file_error(c, "%s\n", msg);
774 		c->retval = 1;
775 	} else {
776 		*msg = toupper((u_char) *msg);
777 		status_message_set(c, "%s", msg);
778 	}
779 
780 	free(msg);
781 }
782