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