xref: /openbsd-src/usr.bin/tmux/cmd-queue.c (revision 9a3a0de7a74c0eb28cfb859c956f268a6525de6e)
1 /* $OpenBSD: cmd-queue.c,v 1.71 2019/05/25 10:46:55 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;
206 
207 	shared = xcalloc(1, sizeof *shared);
208 	if (current != NULL)
209 		cmd_find_copy_state(&shared->current, current);
210 	else
211 		cmd_find_clear_state(&shared->current, 0);
212 	if (m != NULL)
213 		memcpy(&shared->mouse, m, sizeof shared->mouse);
214 
215 	TAILQ_FOREACH(cmd, &cmdlist->list, qentry) {
216 		item = xcalloc(1, sizeof *item);
217 		xasprintf(&item->name, "[%s/%p]", cmd->entry->name, item);
218 		item->type = CMDQ_COMMAND;
219 
220 		item->group = cmd->group;
221 		item->flags = flags;
222 
223 		item->shared = shared;
224 		item->cmdlist = cmdlist;
225 		item->cmd = cmd;
226 
227 		log_debug("%s: %s group %u", __func__, item->name, item->group);
228 
229 		shared->references++;
230 		cmdlist->references++;
231 
232 		if (first == NULL)
233 			first = item;
234 		if (last != NULL)
235 			last->next = item;
236 		last = item;
237 	}
238 	return (first);
239 }
240 
241 /* Fill in flag for a command. */
242 static enum cmd_retval
243 cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs,
244     const struct cmd_entry_flag *flag)
245 {
246 	const char	*value;
247 
248 	if (flag->flag == 0) {
249 		cmd_find_clear_state(fs, 0);
250 		return (CMD_RETURN_NORMAL);
251 	}
252 
253 	value = args_get(item->cmd->args, flag->flag);
254 	if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) {
255 		cmd_find_clear_state(fs, 0);
256 		return (CMD_RETURN_ERROR);
257 	}
258 	return (CMD_RETURN_NORMAL);
259 }
260 
261 /* Fire command on command queue. */
262 static enum cmd_retval
263 cmdq_fire_command(struct cmdq_item *item)
264 {
265 	struct client		*c = item->client;
266 	struct cmdq_shared	*shared = item->shared;
267 	struct cmd		*cmd = item->cmd;
268 	const struct cmd_entry	*entry = cmd->entry;
269 	enum cmd_retval		 retval;
270 	struct cmd_find_state	*fsp, fs;
271 	int			 flags;
272 
273 	flags = !!(shared->flags & CMDQ_SHARED_CONTROL);
274 	cmdq_guard(item, "begin", flags);
275 
276 	if (item->client == NULL)
277 		item->client = cmd_find_client(item, NULL, 1);
278 	retval = cmdq_find_flag(item, &item->source, &entry->source);
279 	if (retval == CMD_RETURN_ERROR)
280 		goto out;
281 	retval = cmdq_find_flag(item, &item->target, &entry->target);
282 	if (retval == CMD_RETURN_ERROR)
283 		goto out;
284 
285 	retval = entry->exec(cmd, item);
286 	if (retval == CMD_RETURN_ERROR)
287 		goto out;
288 
289 	if (entry->flags & CMD_AFTERHOOK) {
290 		if (cmd_find_valid_state(&item->target))
291 			fsp = &item->target;
292 		else if (cmd_find_valid_state(&item->shared->current))
293 			fsp = &item->shared->current;
294 		else if (cmd_find_from_client(&fs, item->client, 0) == 0)
295 			fsp = &fs;
296 		else
297 			goto out;
298 		cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name);
299 	}
300 
301 out:
302 	item->client = c;
303 	if (retval == CMD_RETURN_ERROR)
304 		cmdq_guard(item, "error", flags);
305 	else
306 		cmdq_guard(item, "end", flags);
307 	return (retval);
308 }
309 
310 /* Get a callback for the command queue. */
311 struct cmdq_item *
312 cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
313 {
314 	struct cmdq_item	*item;
315 
316 	item = xcalloc(1, sizeof *item);
317 	xasprintf(&item->name, "[%s/%p]", name, item);
318 	item->type = CMDQ_CALLBACK;
319 
320 	item->group = 0;
321 	item->flags = 0;
322 
323 	item->cb = cb;
324 	item->data = data;
325 
326 	return (item);
327 }
328 
329 /* Generic error callback. */
330 static enum cmd_retval
331 cmdq_error_callback(struct cmdq_item *item, void *data)
332 {
333 	char	*error = data;
334 
335 	cmdq_error(item, "%s", error);
336 	free(error);
337 
338 	return (CMD_RETURN_NORMAL);
339 }
340 
341 /* Get an error callback for the command queue. */
342 struct cmdq_item *
343 cmdq_get_error(const char *error)
344 {
345 	return (cmdq_get_callback(cmdq_error_callback, xstrdup(error)));
346 }
347 
348 /* Fire callback on callback queue. */
349 static enum cmd_retval
350 cmdq_fire_callback(struct cmdq_item *item)
351 {
352 	return (item->cb(item, item->data));
353 }
354 
355 /* Add a format to command queue. */
356 void
357 cmdq_format(struct cmdq_item *item, const char *key, const char *fmt, ...)
358 {
359 	struct cmdq_shared	*shared = item->shared;
360 	va_list			 ap;
361 	char			*value;
362 
363 	va_start(ap, fmt);
364 	xvasprintf(&value, fmt, ap);
365 	va_end(ap);
366 
367 	if (shared->formats == NULL)
368 		shared->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
369 	format_add(shared->formats, key, "%s", value);
370 
371 	free(value);
372 }
373 
374 /* Process next item on command queue. */
375 u_int
376 cmdq_next(struct client *c)
377 {
378 	struct cmdq_list	*queue = cmdq_get(c);
379 	const char		*name = cmdq_name(c);
380 	struct cmdq_item	*item;
381 	enum cmd_retval		 retval;
382 	u_int			 items = 0;
383 	static u_int		 number;
384 
385 	if (TAILQ_EMPTY(queue)) {
386 		log_debug("%s %s: empty", __func__, name);
387 		return (0);
388 	}
389 	if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) {
390 		log_debug("%s %s: waiting", __func__, name);
391 		return (0);
392 	}
393 
394 	log_debug("%s %s: enter", __func__, name);
395 	for (;;) {
396 		item = TAILQ_FIRST(queue);
397 		if (item == NULL)
398 			break;
399 		log_debug("%s %s: %s (%d), flags %x", __func__, name,
400 		    item->name, item->type, item->flags);
401 
402 		/*
403 		 * Any item with the waiting flag set waits until an external
404 		 * event clears the flag (for example, a job - look at
405 		 * run-shell).
406 		 */
407 		if (item->flags & CMDQ_WAITING)
408 			goto waiting;
409 
410 		/*
411 		 * Items are only fired once, once the fired flag is set, a
412 		 * waiting flag can only be cleared by an external event.
413 		 */
414 		if (~item->flags & CMDQ_FIRED) {
415 			item->time = time(NULL);
416 			item->number = ++number;
417 
418 			switch (item->type) {
419 			case CMDQ_COMMAND:
420 				retval = cmdq_fire_command(item);
421 
422 				/*
423 				 * If a command returns an error, remove any
424 				 * subsequent commands in the same group.
425 				 */
426 				if (retval == CMD_RETURN_ERROR)
427 					cmdq_remove_group(item);
428 				break;
429 			case CMDQ_CALLBACK:
430 				retval = cmdq_fire_callback(item);
431 				break;
432 			default:
433 				retval = CMD_RETURN_ERROR;
434 				break;
435 			}
436 			item->flags |= CMDQ_FIRED;
437 
438 			if (retval == CMD_RETURN_WAIT) {
439 				item->flags |= CMDQ_WAITING;
440 				goto waiting;
441 			}
442 			items++;
443 		}
444 		cmdq_remove(item);
445 	}
446 
447 	log_debug("%s %s: exit (empty)", __func__, name);
448 	return (items);
449 
450 waiting:
451 	log_debug("%s %s: exit (wait)", __func__, name);
452 	return (items);
453 }
454 
455 /* Print a guard line. */
456 void
457 cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
458 {
459 	struct client	*c = item->client;
460 
461 	if (c == NULL || !(c->flags & CLIENT_CONTROL))
462 		return;
463 
464 	evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard,
465 	    (long)item->time, item->number, flags);
466 	server_client_push_stdout(c);
467 }
468 
469 /* Show message from command. */
470 void
471 cmdq_print(struct cmdq_item *item, const char *fmt, ...)
472 {
473 	struct client			*c = item->client;
474 	struct window_pane		*wp;
475 	struct window_mode_entry	*wme;
476 	va_list				 ap;
477 	char				*tmp, *msg;
478 
479 	va_start(ap, fmt);
480 
481 	if (c == NULL)
482 		/* nothing */;
483 	else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
484 		if (~c->flags & CLIENT_UTF8) {
485 			xvasprintf(&tmp, fmt, ap);
486 			msg = utf8_sanitize(tmp);
487 			free(tmp);
488 			evbuffer_add(c->stdout_data, msg, strlen(msg));
489 			free(msg);
490 		} else
491 			evbuffer_add_vprintf(c->stdout_data, fmt, ap);
492 		evbuffer_add(c->stdout_data, "\n", 1);
493 		server_client_push_stdout(c);
494 	} else {
495 		wp = c->session->curw->window->active;
496 		wme = TAILQ_FIRST(&wp->modes);
497 		if (wme == NULL || wme->mode != &window_view_mode)
498 			window_pane_set_mode(wp, &window_view_mode, NULL, NULL);
499 		window_copy_vadd(wp, fmt, ap);
500 	}
501 
502 	va_end(ap);
503 }
504 
505 /* Show error from command. */
506 void
507 cmdq_error(struct cmdq_item *item, const char *fmt, ...)
508 {
509 	struct client	*c = item->client;
510 	struct cmd	*cmd = item->cmd;
511 	va_list		 ap;
512 	char		*msg;
513 	size_t		 msglen;
514 	char		*tmp;
515 
516 	va_start(ap, fmt);
517 	msglen = xvasprintf(&msg, fmt, ap);
518 	va_end(ap);
519 
520 	log_debug("%s: %s", __func__, msg);
521 
522 	if (c == NULL)
523 		cfg_add_cause("%s:%u: %s", cmd->file, cmd->line, msg);
524 	else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
525 		if (~c->flags & CLIENT_UTF8) {
526 			tmp = msg;
527 			msg = utf8_sanitize(tmp);
528 			free(tmp);
529 			msglen = strlen(msg);
530 		}
531 		evbuffer_add(c->stderr_data, msg, msglen);
532 		evbuffer_add(c->stderr_data, "\n", 1);
533 		server_client_push_stderr(c);
534 		c->retval = 1;
535 	} else {
536 		*msg = toupper((u_char) *msg);
537 		status_message_set(c, "%s", msg);
538 	}
539 
540 	free(msg);
541 }
542