xref: /openbsd-src/usr.bin/tmux/cmd-queue.c (revision 9b123ae5d0da92fdda7329db5578a2b9c2efeb3e)
1 /* $OpenBSD: cmd-queue.c,v 1.84 2020/04/13 13:32:09 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 				memcpy(&shared->mouse, m, sizeof shared->mouse);
338 			shared->flags = flags;
339 			last_group = group;
340 		}
341 		entry = cmd_get_entry(cmd);
342 
343 		item = xcalloc(1, sizeof *item);
344 		xasprintf(&item->name, "[%s/%p]", entry->name, item);
345 		item->type = CMDQ_COMMAND;
346 		item->group = group;
347 
348 		item->shared = shared;
349 		item->cmdlist = cmdlist;
350 		item->cmd = cmd;
351 
352 		log_debug("%s: %s group %u", __func__, item->name, item->group);
353 
354 		shared->references++;
355 		cmdlist->references++;
356 
357 		if (first == NULL)
358 			first = item;
359 		if (last != NULL)
360 			last->next = item;
361 		last = item;
362 
363 		cmd = cmd_list_next(cmd, &group);
364 	}
365 	return (first);
366 }
367 
368 /* Fill in flag for a command. */
369 static enum cmd_retval
370 cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs,
371     const struct cmd_entry_flag *flag)
372 {
373 	const char	*value;
374 
375 	if (flag->flag == 0) {
376 		cmd_find_clear_state(fs, 0);
377 		return (CMD_RETURN_NORMAL);
378 	}
379 
380 	value = args_get(cmd_get_args(item->cmd), flag->flag);
381 	if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) {
382 		cmd_find_clear_state(fs, 0);
383 		return (CMD_RETURN_ERROR);
384 	}
385 	return (CMD_RETURN_NORMAL);
386 }
387 
388 /* Fire command on command queue. */
389 static enum cmd_retval
390 cmdq_fire_command(struct cmdq_item *item)
391 {
392 	struct client		*c = item->client;
393 	const char		*name = cmdq_name(c);
394 	struct cmdq_shared	*shared = item->shared;
395 	struct cmd		*cmd = item->cmd;
396 	const struct cmd_entry	*entry = cmd_get_entry(cmd);
397 	enum cmd_retval		 retval;
398 	struct cmd_find_state	*fsp, fs;
399 	int			 flags;
400 	char			*tmp;
401 
402 	if (log_get_level() > 1) {
403 		tmp = cmd_print(cmd);
404 		log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp);
405 		free(tmp);
406 	}
407 
408 	flags = !!(shared->flags & CMDQ_SHARED_CONTROL);
409 	cmdq_guard(item, "begin", flags);
410 
411 	if (item->client == NULL)
412 		item->client = cmd_find_client(item, NULL, 1);
413 	retval = cmdq_find_flag(item, &item->source, &entry->source);
414 	if (retval == CMD_RETURN_ERROR)
415 		goto out;
416 	retval = cmdq_find_flag(item, &item->target, &entry->target);
417 	if (retval == CMD_RETURN_ERROR)
418 		goto out;
419 
420 	retval = entry->exec(cmd, item);
421 	if (retval == CMD_RETURN_ERROR)
422 		goto out;
423 
424 	if (entry->flags & CMD_AFTERHOOK) {
425 		if (cmd_find_valid_state(&item->target))
426 			fsp = &item->target;
427 		else if (cmd_find_valid_state(&item->shared->current))
428 			fsp = &item->shared->current;
429 		else if (cmd_find_from_client(&fs, item->client, 0) == 0)
430 			fsp = &fs;
431 		else
432 			goto out;
433 		cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name);
434 	}
435 
436 out:
437 	item->client = c;
438 	if (retval == CMD_RETURN_ERROR)
439 		cmdq_guard(item, "error", flags);
440 	else
441 		cmdq_guard(item, "end", flags);
442 	return (retval);
443 }
444 
445 /* Get a callback for the command queue. */
446 struct cmdq_item *
447 cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
448 {
449 	struct cmdq_item	*item;
450 
451 	item = xcalloc(1, sizeof *item);
452 	xasprintf(&item->name, "[%s/%p]", name, item);
453 	item->type = CMDQ_CALLBACK;
454 	item->group = 0;
455 
456 	item->cb = cb;
457 	item->data = data;
458 
459 	return (item);
460 }
461 
462 /* Generic error callback. */
463 static enum cmd_retval
464 cmdq_error_callback(struct cmdq_item *item, void *data)
465 {
466 	char	*error = data;
467 
468 	cmdq_error(item, "%s", error);
469 	free(error);
470 
471 	return (CMD_RETURN_NORMAL);
472 }
473 
474 /* Get an error callback for the command queue. */
475 struct cmdq_item *
476 cmdq_get_error(const char *error)
477 {
478 	return (cmdq_get_callback(cmdq_error_callback, xstrdup(error)));
479 }
480 
481 /* Fire callback on callback queue. */
482 static enum cmd_retval
483 cmdq_fire_callback(struct cmdq_item *item)
484 {
485 	return (item->cb(item, item->data));
486 }
487 
488 /* Add a format to command queue. */
489 void
490 cmdq_format(struct cmdq_item *item, const char *key, const char *fmt, ...)
491 {
492 	struct cmdq_shared	*shared = item->shared;
493 	va_list			 ap;
494 	char			*value;
495 
496 	va_start(ap, fmt);
497 	xvasprintf(&value, fmt, ap);
498 	va_end(ap);
499 
500 	if (shared->formats == NULL)
501 		shared->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
502 	format_add(shared->formats, key, "%s", value);
503 
504 	free(value);
505 }
506 
507 /* Process next item on command queue. */
508 u_int
509 cmdq_next(struct client *c)
510 {
511 	struct cmdq_list	*queue = cmdq_get(c);
512 	const char		*name = cmdq_name(c);
513 	struct cmdq_item	*item;
514 	enum cmd_retval		 retval;
515 	u_int			 items = 0;
516 	static u_int		 number;
517 
518 	if (TAILQ_EMPTY(queue)) {
519 		log_debug("%s %s: empty", __func__, name);
520 		return (0);
521 	}
522 	if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) {
523 		log_debug("%s %s: waiting", __func__, name);
524 		return (0);
525 	}
526 
527 	log_debug("%s %s: enter", __func__, name);
528 	for (;;) {
529 		item = TAILQ_FIRST(queue);
530 		if (item == NULL)
531 			break;
532 		log_debug("%s %s: %s (%d), flags %x", __func__, name,
533 		    item->name, item->type, item->flags);
534 
535 		/*
536 		 * Any item with the waiting flag set waits until an external
537 		 * event clears the flag (for example, a job - look at
538 		 * run-shell).
539 		 */
540 		if (item->flags & CMDQ_WAITING)
541 			goto waiting;
542 
543 		/*
544 		 * Items are only fired once, once the fired flag is set, a
545 		 * waiting flag can only be cleared by an external event.
546 		 */
547 		if (~item->flags & CMDQ_FIRED) {
548 			item->time = time(NULL);
549 			item->number = ++number;
550 
551 			switch (item->type) {
552 			case CMDQ_COMMAND:
553 				retval = cmdq_fire_command(item);
554 
555 				/*
556 				 * If a command returns an error, remove any
557 				 * subsequent commands in the same group.
558 				 */
559 				if (retval == CMD_RETURN_ERROR)
560 					cmdq_remove_group(item);
561 				break;
562 			case CMDQ_CALLBACK:
563 				retval = cmdq_fire_callback(item);
564 				break;
565 			default:
566 				retval = CMD_RETURN_ERROR;
567 				break;
568 			}
569 			item->flags |= CMDQ_FIRED;
570 
571 			if (retval == CMD_RETURN_WAIT) {
572 				item->flags |= CMDQ_WAITING;
573 				goto waiting;
574 			}
575 			items++;
576 		}
577 		cmdq_remove(item);
578 	}
579 
580 	log_debug("%s %s: exit (empty)", __func__, name);
581 	return (items);
582 
583 waiting:
584 	log_debug("%s %s: exit (wait)", __func__, name);
585 	return (items);
586 }
587 
588 /* Print a guard line. */
589 void
590 cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
591 {
592 	struct client	*c = item->client;
593 	long		 t = item->time;
594 	u_int		 number = item->number;
595 
596 	if (c != NULL && (c->flags & CLIENT_CONTROL))
597 		file_print(c, "%%%s %ld %u %d\n", guard, t, number, flags);
598 }
599 
600 /* Show message from command. */
601 void
602 cmdq_print(struct cmdq_item *item, const char *fmt, ...)
603 {
604 	struct client			*c = item->client;
605 	struct window_pane		*wp;
606 	struct window_mode_entry	*wme;
607 	va_list				 ap;
608 	char				*tmp, *msg;
609 
610 	va_start(ap, fmt);
611 	xvasprintf(&msg, fmt, ap);
612 	va_end(ap);
613 
614 	log_debug("%s: %s", __func__, msg);
615 
616 	if (c == NULL)
617 		/* nothing */;
618 	else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
619 		if (~c->flags & CLIENT_UTF8) {
620 			tmp = msg;
621 			msg = utf8_sanitize(tmp);
622 			free(tmp);
623 		}
624 		file_print(c, "%s\n", msg);
625 	} else {
626 		wp = c->session->curw->window->active;
627 		wme = TAILQ_FIRST(&wp->modes);
628 		if (wme == NULL || wme->mode != &window_view_mode) {
629 			window_pane_set_mode(wp, NULL, &window_view_mode, NULL,
630 			    NULL);
631 		}
632 		window_copy_add(wp, "%s", msg);
633 	}
634 
635 	free(msg);
636 }
637 
638 /* Show error from command. */
639 void
640 cmdq_error(struct cmdq_item *item, const char *fmt, ...)
641 {
642 	struct client	*c = item->client;
643 	struct cmd	*cmd = item->cmd;
644 	va_list		 ap;
645 	char		*msg, *tmp;
646 	const char	*file;
647 	u_int		 line;
648 
649 	va_start(ap, fmt);
650 	xvasprintf(&msg, fmt, ap);
651 	va_end(ap);
652 
653 	log_debug("%s: %s", __func__, msg);
654 
655 	if (c == NULL) {
656 		cmd_get_source(cmd, &file, &line);
657 		cfg_add_cause("%s:%u: %s", file, line, msg);
658 	} else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
659 		if (~c->flags & CLIENT_UTF8) {
660 			tmp = msg;
661 			msg = utf8_sanitize(tmp);
662 			free(tmp);
663 		}
664 		if (c->flags & CLIENT_CONTROL)
665 			file_print(c, "%s\n", msg);
666 		else
667 			file_error(c, "%s\n", msg);
668 		c->retval = 1;
669 	} else {
670 		*msg = toupper((u_char) *msg);
671 		status_message_set(c, "%s", msg);
672 	}
673 
674 	free(msg);
675 }
676