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