xref: /openbsd-src/usr.bin/tmux/cmd-queue.c (revision 3a77592715e189ad5c4e8389b714923246392f2c)
1*3a775927Snicm /* $OpenBSD: cmd-queue.c,v 1.118 2024/11/22 12:58:05 nicm Exp $ */
2175d36ccSnicm 
3175d36ccSnicm /*
498ca8272Snicm  * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
5175d36ccSnicm  *
6175d36ccSnicm  * Permission to use, copy, modify, and distribute this software for any
7175d36ccSnicm  * purpose with or without fee is hereby granted, provided that the above
8175d36ccSnicm  * copyright notice and this permission notice appear in all copies.
9175d36ccSnicm  *
10175d36ccSnicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11175d36ccSnicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12175d36ccSnicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13175d36ccSnicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14175d36ccSnicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15175d36ccSnicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16175d36ccSnicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17175d36ccSnicm  */
18175d36ccSnicm 
19175d36ccSnicm #include <sys/types.h>
20175d36ccSnicm 
21175d36ccSnicm #include <ctype.h>
228ab000fcSnicm #include <pwd.h>
23175d36ccSnicm #include <stdlib.h>
24e048bb79Snicm #include <string.h>
256ba5a684Snicm #include <time.h>
268ab000fcSnicm #include <unistd.h>
27f260bbaaSnicm #include <vis.h>
28175d36ccSnicm 
29175d36ccSnicm #include "tmux.h"
30175d36ccSnicm 
319b123ae5Snicm /* Command queue flags. */
329b123ae5Snicm #define CMDQ_FIRED 0x1
339b123ae5Snicm #define CMDQ_WAITING 0x2
349b123ae5Snicm 
35040343aeSnicm /* Command queue item type. */
36040343aeSnicm enum cmdq_type {
37040343aeSnicm 	CMDQ_COMMAND,
38040343aeSnicm 	CMDQ_CALLBACK,
39040343aeSnicm };
40040343aeSnicm 
41040343aeSnicm /* Command queue item. */
42040343aeSnicm struct cmdq_item {
43040343aeSnicm 	char			*name;
44040343aeSnicm 	struct cmdq_list	*queue;
45040343aeSnicm 	struct cmdq_item	*next;
46040343aeSnicm 
47040343aeSnicm 	struct client		*client;
48035dc73dSnicm 	struct client		*target_client;
49040343aeSnicm 
50040343aeSnicm 	enum cmdq_type		 type;
51040343aeSnicm 	u_int			 group;
52040343aeSnicm 
53040343aeSnicm 	u_int			 number;
54040343aeSnicm 	time_t			 time;
55040343aeSnicm 
56040343aeSnicm 	int			 flags;
57040343aeSnicm 
58054f42acSnicm 	struct cmdq_state	*state;
59040343aeSnicm 	struct cmd_find_state	 source;
60040343aeSnicm 	struct cmd_find_state	 target;
61040343aeSnicm 
62040343aeSnicm 	struct cmd_list		*cmdlist;
63040343aeSnicm 	struct cmd		*cmd;
64040343aeSnicm 
65040343aeSnicm 	cmdq_cb			 cb;
66040343aeSnicm 	void			*data;
67040343aeSnicm 
68040343aeSnicm 	TAILQ_ENTRY(cmdq_item)	 entry;
69040343aeSnicm };
705c72fbecSnicm TAILQ_HEAD(cmdq_item_list, cmdq_item);
7176851227Snicm 
72823b6d6dSnicm /*
73823b6d6dSnicm  * Command queue state. This is the context for commands on the command queue.
74823b6d6dSnicm  * It holds information about how the commands were fired (the key and flags),
75823b6d6dSnicm  * any additional formats for the commands, and the current default target.
76823b6d6dSnicm  * Multiple commands can share the same state and a command may update the
77823b6d6dSnicm  * default target.
78823b6d6dSnicm  */
79823b6d6dSnicm struct cmdq_state {
80823b6d6dSnicm 	int			 references;
81823b6d6dSnicm 	int			 flags;
82823b6d6dSnicm 
83823b6d6dSnicm 	struct format_tree	*formats;
84823b6d6dSnicm 
85823b6d6dSnicm 	struct key_event	 event;
86823b6d6dSnicm 	struct cmd_find_state	 current;
87823b6d6dSnicm };
88823b6d6dSnicm 
895c72fbecSnicm /* Command queue. */
905c72fbecSnicm struct cmdq_list {
915c72fbecSnicm 	struct cmdq_item	*item;
925c72fbecSnicm 	struct cmdq_item_list	 list;
935c72fbecSnicm };
945c72fbecSnicm 
95765b9a58Snicm /* Get command queue name. */
96765b9a58Snicm static const char *
97765b9a58Snicm cmdq_name(struct client *c)
98765b9a58Snicm {
996b2831afSnicm 	static char	s[256];
100765b9a58Snicm 
101765b9a58Snicm 	if (c == NULL)
102765b9a58Snicm 		return ("<global>");
1039a3a0de7Snicm 	if (c->name != NULL)
1046b2831afSnicm 		xsnprintf(s, sizeof s, "<%s>", c->name);
1059a3a0de7Snicm 	else
1069a3a0de7Snicm 		xsnprintf(s, sizeof s, "<%p>", c);
107765b9a58Snicm 	return (s);
108765b9a58Snicm }
109765b9a58Snicm 
110765b9a58Snicm /* Get command queue from client. */
11168e0a7f2Snicm static struct cmdq_list *
112765b9a58Snicm cmdq_get(struct client *c)
113765b9a58Snicm {
114040343aeSnicm 	static struct cmdq_list *global_queue;
115040343aeSnicm 
116040343aeSnicm 	if (c == NULL) {
117040343aeSnicm 		if (global_queue == NULL)
118040343aeSnicm 			global_queue = cmdq_new();
119040343aeSnicm 		return (global_queue);
120040343aeSnicm 	}
121040343aeSnicm 	return (c->queue);
122040343aeSnicm }
123040343aeSnicm 
124040343aeSnicm /* Create a queue. */
125040343aeSnicm struct cmdq_list *
126040343aeSnicm cmdq_new(void)
127040343aeSnicm {
128040343aeSnicm 	struct cmdq_list	*queue;
129040343aeSnicm 
130040343aeSnicm 	queue = xcalloc(1, sizeof *queue);
1315c72fbecSnicm 	TAILQ_INIT (&queue->list);
132040343aeSnicm 	return (queue);
133040343aeSnicm }
134040343aeSnicm 
135040343aeSnicm /* Free a queue. */
136040343aeSnicm void
137040343aeSnicm cmdq_free(struct cmdq_list *queue)
138040343aeSnicm {
1395c72fbecSnicm 	if (!TAILQ_EMPTY(&queue->list))
140040343aeSnicm 		fatalx("queue not empty");
141040343aeSnicm 	free(queue);
142040343aeSnicm }
143040343aeSnicm 
144040343aeSnicm /* Get item name. */
145040343aeSnicm const char *
146040343aeSnicm cmdq_get_name(struct cmdq_item *item)
147040343aeSnicm {
148040343aeSnicm 	return (item->name);
149040343aeSnicm }
150040343aeSnicm 
151040343aeSnicm /* Get item client. */
152040343aeSnicm struct client *
153040343aeSnicm cmdq_get_client(struct cmdq_item *item)
154040343aeSnicm {
155040343aeSnicm 	return (item->client);
156040343aeSnicm }
157040343aeSnicm 
158035dc73dSnicm /* Get item target client. */
159035dc73dSnicm struct client *
160035dc73dSnicm cmdq_get_target_client(struct cmdq_item *item)
161035dc73dSnicm {
162035dc73dSnicm 	return (item->target_client);
163035dc73dSnicm }
164035dc73dSnicm 
165c1e0bdabSnicm /* Get item state. */
166c1e0bdabSnicm struct cmdq_state *
167c1e0bdabSnicm cmdq_get_state(struct cmdq_item *item)
168c1e0bdabSnicm {
169c1e0bdabSnicm 	return (item->state);
170c1e0bdabSnicm }
171c1e0bdabSnicm 
172040343aeSnicm /* Get item target. */
173040343aeSnicm struct cmd_find_state *
174040343aeSnicm cmdq_get_target(struct cmdq_item *item)
175040343aeSnicm {
176040343aeSnicm 	return (&item->target);
177040343aeSnicm }
178040343aeSnicm 
179040343aeSnicm /* Get item source. */
180040343aeSnicm struct cmd_find_state *
181040343aeSnicm cmdq_get_source(struct cmdq_item *item)
182040343aeSnicm {
183040343aeSnicm 	return (&item->source);
184040343aeSnicm }
185040343aeSnicm 
186823b6d6dSnicm /* Get state event. */
187823b6d6dSnicm struct key_event *
188823b6d6dSnicm cmdq_get_event(struct cmdq_item *item)
189040343aeSnicm {
190823b6d6dSnicm 	return (&item->state->event);
191823b6d6dSnicm }
192823b6d6dSnicm 
193823b6d6dSnicm /* Get state current target. */
194823b6d6dSnicm struct cmd_find_state *
195823b6d6dSnicm cmdq_get_current(struct cmdq_item *item)
196823b6d6dSnicm {
197823b6d6dSnicm 	return (&item->state->current);
198823b6d6dSnicm }
199823b6d6dSnicm 
200823b6d6dSnicm /* Get state flags. */
201823b6d6dSnicm int
202823b6d6dSnicm cmdq_get_flags(struct cmdq_item *item)
203823b6d6dSnicm {
204823b6d6dSnicm 	return (item->state->flags);
205040343aeSnicm }
206040343aeSnicm 
207c1e0bdabSnicm /* Create a new state. */
208c1e0bdabSnicm struct cmdq_state *
209c1e0bdabSnicm cmdq_new_state(struct cmd_find_state *current, struct key_event *event,
210c1e0bdabSnicm     int flags)
211c1e0bdabSnicm {
212c1e0bdabSnicm 	struct cmdq_state	*state;
213c1e0bdabSnicm 
214c1e0bdabSnicm 	state = xcalloc(1, sizeof *state);
215c1e0bdabSnicm 	state->references = 1;
216c1e0bdabSnicm 	state->flags = flags;
217c1e0bdabSnicm 
218c1e0bdabSnicm 	if (event != NULL)
219c1e0bdabSnicm 		memcpy(&state->event, event, sizeof state->event);
220c1e0bdabSnicm 	else
221c1e0bdabSnicm 		state->event.key = KEYC_NONE;
222c1e0bdabSnicm 	if (current != NULL && cmd_find_valid_state(current))
223c1e0bdabSnicm 		cmd_find_copy_state(&state->current, current);
224c1e0bdabSnicm 	else
225c1e0bdabSnicm 		cmd_find_clear_state(&state->current, 0);
226c1e0bdabSnicm 
227c1e0bdabSnicm 	return (state);
228c1e0bdabSnicm }
229c1e0bdabSnicm 
230c1e0bdabSnicm /* Add a reference to a state. */
231c1e0bdabSnicm struct cmdq_state *
232c1e0bdabSnicm cmdq_link_state(struct cmdq_state *state)
233c1e0bdabSnicm {
234c1e0bdabSnicm 	state->references++;
235c1e0bdabSnicm 	return (state);
236c1e0bdabSnicm }
237c1e0bdabSnicm 
238c1e0bdabSnicm /* Make a copy of a state. */
239c1e0bdabSnicm struct cmdq_state *
24004d313c5Snicm cmdq_copy_state(struct cmdq_state *state, struct cmd_find_state *current)
241c1e0bdabSnicm {
24204d313c5Snicm 	if (current != NULL)
24304d313c5Snicm 		return (cmdq_new_state(current, &state->event, state->flags));
244c1e0bdabSnicm 	return (cmdq_new_state(&state->current, &state->event, state->flags));
245c1e0bdabSnicm }
246c1e0bdabSnicm 
247c1e0bdabSnicm /* Free a state. */
248c1e0bdabSnicm void
249c1e0bdabSnicm cmdq_free_state(struct cmdq_state *state)
250c1e0bdabSnicm {
251928e56dcSnicm 	if (--state->references != 0)
252928e56dcSnicm 		return;
253928e56dcSnicm 
254928e56dcSnicm 	if (state->formats != NULL)
255928e56dcSnicm 		format_free(state->formats);
256c1e0bdabSnicm 	free(state);
257c1e0bdabSnicm }
258c1e0bdabSnicm 
259c1e0bdabSnicm /* Add a format to command queue. */
260c1e0bdabSnicm void
261c1e0bdabSnicm cmdq_add_format(struct cmdq_state *state, const char *key, const char *fmt, ...)
262c1e0bdabSnicm {
263c1e0bdabSnicm 	va_list	 ap;
264c1e0bdabSnicm 	char	*value;
265c1e0bdabSnicm 
266c1e0bdabSnicm 	va_start(ap, fmt);
267c1e0bdabSnicm 	xvasprintf(&value, fmt, ap);
268c1e0bdabSnicm 	va_end(ap);
269c1e0bdabSnicm 
270c1e0bdabSnicm 	if (state->formats == NULL)
271c1e0bdabSnicm 		state->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
272c1e0bdabSnicm 	format_add(state->formats, key, "%s", value);
273c1e0bdabSnicm 
274c1e0bdabSnicm 	free(value);
275c1e0bdabSnicm }
276c1e0bdabSnicm 
2779eee7735Snicm /* Add formats to command queue. */
2789eee7735Snicm void
2799eee7735Snicm cmdq_add_formats(struct cmdq_state *state, struct format_tree *ft)
2809eee7735Snicm {
2819eee7735Snicm 	if (state->formats == NULL)
2829eee7735Snicm 		state->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
2839eee7735Snicm 	format_merge(state->formats, ft);
2849eee7735Snicm }
2859eee7735Snicm 
286040343aeSnicm /* Merge formats from item. */
287040343aeSnicm void
288040343aeSnicm cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft)
289040343aeSnicm {
290040343aeSnicm 	const struct cmd_entry	*entry;
291040343aeSnicm 
292040343aeSnicm 	if (item->cmd != NULL) {
293040343aeSnicm 		entry = cmd_get_entry(item->cmd);
294040343aeSnicm 		format_add(ft, "command", "%s", entry->name);
295040343aeSnicm 	}
296054f42acSnicm 	if (item->state->formats != NULL)
297054f42acSnicm 		format_merge(ft, item->state->formats);
298765b9a58Snicm }
299765b9a58Snicm 
300765b9a58Snicm /* Append an item. */
30195cd24f8Snicm struct cmdq_item *
30268e0a7f2Snicm cmdq_append(struct client *c, struct cmdq_item *item)
303765b9a58Snicm {
30468e0a7f2Snicm 	struct cmdq_list	*queue = cmdq_get(c);
30568e0a7f2Snicm 	struct cmdq_item	*next;
306765b9a58Snicm 
307765b9a58Snicm 	do {
30868e0a7f2Snicm 		next = item->next;
30968e0a7f2Snicm 		item->next = NULL;
310765b9a58Snicm 
311765b9a58Snicm 		if (c != NULL)
312765b9a58Snicm 			c->references++;
31368e0a7f2Snicm 		item->client = c;
314765b9a58Snicm 
31568e0a7f2Snicm 		item->queue = queue;
3165c72fbecSnicm 		TAILQ_INSERT_TAIL(&queue->list, item, entry);
3175edaef27Snicm 		log_debug("%s %s: %s", __func__, cmdq_name(c), item->name);
318765b9a58Snicm 
31968e0a7f2Snicm 		item = next;
32068e0a7f2Snicm 	} while (item != NULL);
3215c72fbecSnicm 	return (TAILQ_LAST(&queue->list, cmdq_item_list));
322765b9a58Snicm }
323765b9a58Snicm 
324765b9a58Snicm /* Insert an item. */
32595cd24f8Snicm struct cmdq_item *
32668e0a7f2Snicm cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item)
327765b9a58Snicm {
328765b9a58Snicm 	struct client		*c = after->client;
32968e0a7f2Snicm 	struct cmdq_list	*queue = after->queue;
33068e0a7f2Snicm 	struct cmdq_item	*next;
331765b9a58Snicm 
332765b9a58Snicm 	do {
33368e0a7f2Snicm 		next = item->next;
3345edaef27Snicm 		item->next = after->next;
3355edaef27Snicm 		after->next = item;
336765b9a58Snicm 
337765b9a58Snicm 		if (c != NULL)
338765b9a58Snicm 			c->references++;
33968e0a7f2Snicm 		item->client = c;
340765b9a58Snicm 
34168e0a7f2Snicm 		item->queue = queue;
3425c72fbecSnicm 		TAILQ_INSERT_AFTER(&queue->list, after, item, entry);
3435edaef27Snicm 		log_debug("%s %s: %s after %s", __func__, cmdq_name(c),
3445edaef27Snicm 		    item->name, after->name);
345765b9a58Snicm 
346ef3f1b12Snicm 		after = item;
34768e0a7f2Snicm 		item = next;
34868e0a7f2Snicm 	} while (item != NULL);
34995cd24f8Snicm 	return (after);
350765b9a58Snicm }
351765b9a58Snicm 
352844b9093Snicm /* Insert a hook. */
353844b9093Snicm void
354844b9093Snicm cmdq_insert_hook(struct session *s, struct cmdq_item *item,
355c1e0bdabSnicm     struct cmd_find_state *current, const char *fmt, ...)
356844b9093Snicm {
357c1e0bdabSnicm 	struct cmdq_state		*state = item->state;
358ef07bfa2Snicm 	struct cmd			*cmd = item->cmd;
359ef07bfa2Snicm 	struct args			*args = cmd_get_args(cmd);
36005b80794Snicm 	struct args_entry		*ae;
36105b80794Snicm 	struct args_value		*av;
362844b9093Snicm 	struct options			*oo;
363844b9093Snicm 	va_list				 ap;
364ef07bfa2Snicm 	char				*name, tmp[32], flag, *arguments;
3651693b10bSnicm 	u_int				 i;
366ef07bfa2Snicm 	const char			*value;
367844b9093Snicm 	struct cmdq_item		*new_item;
368c1e0bdabSnicm 	struct cmdq_state		*new_state;
369844b9093Snicm 	struct options_entry		*o;
370844b9093Snicm 	struct options_array_item	*a;
371844b9093Snicm 	struct cmd_list			*cmdlist;
372844b9093Snicm 
373054f42acSnicm 	if (item->state->flags & CMDQ_STATE_NOHOOKS)
374844b9093Snicm 		return;
375844b9093Snicm 	if (s == NULL)
376844b9093Snicm 		oo = global_s_options;
377844b9093Snicm 	else
378844b9093Snicm 		oo = s->options;
379844b9093Snicm 
380844b9093Snicm 	va_start(ap, fmt);
381844b9093Snicm 	xvasprintf(&name, fmt, ap);
382844b9093Snicm 	va_end(ap);
383844b9093Snicm 
384844b9093Snicm 	o = options_get(oo, name);
385844b9093Snicm 	if (o == NULL) {
386844b9093Snicm 		free(name);
387844b9093Snicm 		return;
388844b9093Snicm 	}
389844b9093Snicm 	log_debug("running hook %s (parent %p)", name, item);
390844b9093Snicm 
391c1e0bdabSnicm 	/*
392c1e0bdabSnicm 	 * The hooks get a new state because they should not update the current
393c1e0bdabSnicm 	 * target or formats for any subsequent commands.
394c1e0bdabSnicm 	 */
395c1e0bdabSnicm 	new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS);
396c1e0bdabSnicm 	cmdq_add_format(new_state, "hook", "%s", name);
397c1e0bdabSnicm 
398ef07bfa2Snicm 	arguments = args_print(args);
399ef07bfa2Snicm 	cmdq_add_format(new_state, "hook_arguments", "%s", arguments);
400ef07bfa2Snicm 	free(arguments);
401ef07bfa2Snicm 
4021693b10bSnicm 	for (i = 0; i < args_count(args); i++) {
403ef07bfa2Snicm 		xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i);
4041693b10bSnicm 		cmdq_add_format(new_state, tmp, "%s", args_string(args, i));
405ef07bfa2Snicm 	}
40605b80794Snicm 	flag = args_first(args, &ae);
407ef07bfa2Snicm 	while (flag != 0) {
408ef07bfa2Snicm 		value = args_get(args, flag);
409ef07bfa2Snicm 		if (value == NULL) {
410ef07bfa2Snicm 			xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag);
411ef07bfa2Snicm 			cmdq_add_format(new_state, tmp, "1");
412ef07bfa2Snicm 		} else {
413ef07bfa2Snicm 			xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag);
414ef07bfa2Snicm 			cmdq_add_format(new_state, tmp, "%s", value);
415ef07bfa2Snicm 		}
416ef07bfa2Snicm 
417ef07bfa2Snicm 		i = 0;
41805b80794Snicm 		av = args_first_value(args, flag);
41905b80794Snicm 		while (av != NULL) {
420ef07bfa2Snicm 			xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i);
421825f884aSnicm 			cmdq_add_format(new_state, tmp, "%s", av->string);
422ef07bfa2Snicm 			i++;
42305b80794Snicm 			av = args_next_value(av);
424ef07bfa2Snicm 		}
425ef07bfa2Snicm 
42605b80794Snicm 		flag = args_next(&ae);
427ef07bfa2Snicm 	}
428ef07bfa2Snicm 
429844b9093Snicm 	a = options_array_first(o);
430844b9093Snicm 	while (a != NULL) {
431844b9093Snicm 		cmdlist = options_array_item_value(a)->cmdlist;
432c1e0bdabSnicm 		if (cmdlist != NULL) {
433c1e0bdabSnicm 			new_item = cmdq_get_command(cmdlist, new_state);
43495cd24f8Snicm 			if (item != NULL)
43595cd24f8Snicm 				item = cmdq_insert_after(item, new_item);
43695cd24f8Snicm 			else
43795cd24f8Snicm 				item = cmdq_append(NULL, new_item);
438c1e0bdabSnicm 		}
439844b9093Snicm 		a = options_array_next(a);
440844b9093Snicm 	}
441844b9093Snicm 
442c1e0bdabSnicm 	cmdq_free_state(new_state);
443844b9093Snicm 	free(name);
444844b9093Snicm }
445844b9093Snicm 
446780ca620Snicm /* Continue processing command queue. */
447780ca620Snicm void
448780ca620Snicm cmdq_continue(struct cmdq_item *item)
449780ca620Snicm {
450780ca620Snicm 	item->flags &= ~CMDQ_WAITING;
451780ca620Snicm }
452780ca620Snicm 
453765b9a58Snicm /* Remove an item. */
454765b9a58Snicm static void
45568e0a7f2Snicm cmdq_remove(struct cmdq_item *item)
456765b9a58Snicm {
45768e0a7f2Snicm 	if (item->client != NULL)
45868e0a7f2Snicm 		server_client_unref(item->client);
459c26c4f79Snicm 	if (item->cmdlist != NULL)
46068e0a7f2Snicm 		cmd_list_free(item->cmdlist);
461c1e0bdabSnicm 	cmdq_free_state(item->state);
462765b9a58Snicm 
4635c72fbecSnicm 	TAILQ_REMOVE(&item->queue->list, item, entry);
464d6aa0047Snicm 
4655edaef27Snicm 	free(item->name);
46668e0a7f2Snicm 	free(item);
467765b9a58Snicm }
468765b9a58Snicm 
469765b9a58Snicm /* Remove all subsequent items that match this item's group. */
470765b9a58Snicm static void
47168e0a7f2Snicm cmdq_remove_group(struct cmdq_item *item)
472765b9a58Snicm {
47368e0a7f2Snicm 	struct cmdq_item	*this, *next;
474765b9a58Snicm 
475d8804b0bSnicm 	if (item->group == 0)
476d8804b0bSnicm 		return;
47768e0a7f2Snicm 	this = TAILQ_NEXT(item, entry);
478765b9a58Snicm 	while (this != NULL) {
479765b9a58Snicm 		next = TAILQ_NEXT(this, entry);
48068e0a7f2Snicm 		if (this->group == item->group)
481765b9a58Snicm 			cmdq_remove(this);
482765b9a58Snicm 		this = next;
483765b9a58Snicm 	}
484765b9a58Snicm }
485765b9a58Snicm 
486afdf680fSnicm /* Empty command callback. */
487afdf680fSnicm static enum cmd_retval
488afdf680fSnicm cmdq_empty_command(__unused struct cmdq_item *item, __unused void *data)
489afdf680fSnicm {
490afdf680fSnicm 	return (CMD_RETURN_NORMAL);
491afdf680fSnicm }
492afdf680fSnicm 
493765b9a58Snicm /* Get a command for the command queue. */
49468e0a7f2Snicm struct cmdq_item *
495c1e0bdabSnicm cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state)
496765b9a58Snicm {
49768e0a7f2Snicm 	struct cmdq_item	*item, *first = NULL, *last = NULL;
498765b9a58Snicm 	struct cmd		*cmd;
49990d7ba38Snicm 	const struct cmd_entry	*entry;
500c1e0bdabSnicm 	int			 created = 0;
501bebc73f1Snicm 
502afdf680fSnicm 	if ((cmd = cmd_list_first(cmdlist)) == NULL)
503afdf680fSnicm 		return (cmdq_get_callback(cmdq_empty_command, NULL));
504afdf680fSnicm 
505c1e0bdabSnicm 	if (state == NULL) {
506c1e0bdabSnicm 		state = cmdq_new_state(NULL, NULL, 0);
507c1e0bdabSnicm 		created = 1;
508c1e0bdabSnicm 	}
509c1e0bdabSnicm 
51090d7ba38Snicm 	while (cmd != NULL) {
51190d7ba38Snicm 		entry = cmd_get_entry(cmd);
512765b9a58Snicm 
51368e0a7f2Snicm 		item = xcalloc(1, sizeof *item);
51490d7ba38Snicm 		xasprintf(&item->name, "[%s/%p]", entry->name, item);
51568e0a7f2Snicm 		item->type = CMDQ_COMMAND;
516765b9a58Snicm 
517c1e0bdabSnicm 		item->group = cmd_get_group(cmd);
518c1e0bdabSnicm 		item->state = cmdq_link_state(state);
519c1e0bdabSnicm 
52068e0a7f2Snicm 		item->cmdlist = cmdlist;
52168e0a7f2Snicm 		item->cmd = cmd;
522765b9a58Snicm 
523765b9a58Snicm 		cmdlist->references++;
524c1e0bdabSnicm 		log_debug("%s: %s group %u", __func__, item->name, item->group);
525765b9a58Snicm 
526765b9a58Snicm 		if (first == NULL)
52768e0a7f2Snicm 			first = item;
528765b9a58Snicm 		if (last != NULL)
52968e0a7f2Snicm 			last->next = item;
53068e0a7f2Snicm 		last = item;
53190d7ba38Snicm 
532c1e0bdabSnicm 		cmd = cmd_list_next(cmd);
533765b9a58Snicm 	}
534c1e0bdabSnicm 
535c1e0bdabSnicm 	if (created)
536c1e0bdabSnicm 		cmdq_free_state(state);
537765b9a58Snicm 	return (first);
538765b9a58Snicm }
539765b9a58Snicm 
540bf0d297eSnicm /* Fill in flag for a command. */
541bf0d297eSnicm static enum cmd_retval
542bf0d297eSnicm cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs,
543bf0d297eSnicm     const struct cmd_entry_flag *flag)
544bf0d297eSnicm {
545bf0d297eSnicm 	const char	*value;
546bf0d297eSnicm 
547bf0d297eSnicm 	if (flag->flag == 0) {
54894adf770Snicm 		cmd_find_from_client(fs, item->target_client, 0);
549bf0d297eSnicm 		return (CMD_RETURN_NORMAL);
550bf0d297eSnicm 	}
551bf0d297eSnicm 
55290d7ba38Snicm 	value = args_get(cmd_get_args(item->cmd), flag->flag);
553bf0d297eSnicm 	if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) {
554bf0d297eSnicm 		cmd_find_clear_state(fs, 0);
555bf0d297eSnicm 		return (CMD_RETURN_ERROR);
556bf0d297eSnicm 	}
557bf0d297eSnicm 	return (CMD_RETURN_NORMAL);
558bf0d297eSnicm }
559bf0d297eSnicm 
5600687354cSnicm /* Add message with command. */
5610687354cSnicm static void
5620687354cSnicm cmdq_add_message(struct cmdq_item *item)
5630687354cSnicm {
5640687354cSnicm 	struct client		*c = item->client;
5650687354cSnicm 	struct cmdq_state	*state = item->state;
5668ab000fcSnicm 	const char		*key;
5670687354cSnicm 	char			*tmp;
5688ab000fcSnicm 	uid_t                    uid;
5698ab000fcSnicm 	struct passwd		*pw;
5708ab000fcSnicm 	char                    *user = NULL;
5710687354cSnicm 
5720687354cSnicm 	tmp = cmd_print(item->cmd);
5730687354cSnicm 	if (c != NULL) {
5748ab000fcSnicm 		uid = proc_get_peer_uid(c->peer);
5758ab000fcSnicm 		if (uid != (uid_t)-1 && uid != getuid()) {
5768ab000fcSnicm 			if ((pw = getpwuid(uid)) != NULL)
5778ab000fcSnicm 				xasprintf(&user, "[%s]", pw->pw_name);
5788ab000fcSnicm 			else
5798ab000fcSnicm 				user = xstrdup("[unknown]");
5808ab000fcSnicm 		} else
5818ab000fcSnicm 			user = xstrdup("");
5820687354cSnicm 		if (c->session != NULL && state->event.key != KEYC_NONE) {
5835416581eSnicm 			key = key_string_lookup_key(state->event.key, 0);
5848ab000fcSnicm 			server_add_message("%s%s key %s: %s", c->name, user,
5858ab000fcSnicm 			    key, tmp);
5868ab000fcSnicm 		} else {
5878ab000fcSnicm 			server_add_message("%s%s command: %s", c->name, user,
5888ab000fcSnicm 			    tmp);
5898ab000fcSnicm 		}
5908ab000fcSnicm 		free(user);
5910687354cSnicm 	} else
5920687354cSnicm 		server_add_message("command: %s", tmp);
5930687354cSnicm 	free(tmp);
5940687354cSnicm }
5950687354cSnicm 
596765b9a58Snicm /* Fire command on command queue. */
597765b9a58Snicm static enum cmd_retval
59868e0a7f2Snicm cmdq_fire_command(struct cmdq_item *item)
599765b9a58Snicm {
600035dc73dSnicm 	const char		*name = cmdq_name(item->client);
601054f42acSnicm 	struct cmdq_state	*state = item->state;
60268e0a7f2Snicm 	struct cmd		*cmd = item->cmd;
603035dc73dSnicm 	struct args		*args = cmd_get_args(cmd);
60490d7ba38Snicm 	const struct cmd_entry	*entry = cmd_get_entry(cmd);
605035dc73dSnicm 	struct client		*tc, *saved = item->client;
606765b9a58Snicm 	enum cmd_retval		 retval;
607765b9a58Snicm 	struct cmd_find_state	*fsp, fs;
608035dc73dSnicm 	int			 flags, quiet = 0;
60947394861Snicm 	char			*tmp;
61047394861Snicm 
6110687354cSnicm 	if (cfg_finished)
6120687354cSnicm 		cmdq_add_message(item);
61347394861Snicm 	if (log_get_level() > 1) {
61447394861Snicm 		tmp = cmd_print(cmd);
61547394861Snicm 		log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp);
61647394861Snicm 		free(tmp);
61747394861Snicm 	}
618765b9a58Snicm 
619054f42acSnicm 	flags = !!(state->flags & CMDQ_STATE_CONTROL);
62068e0a7f2Snicm 	cmdq_guard(item, "begin", flags);
621765b9a58Snicm 
622d655f083Snicm 	if (item->client == NULL)
623d655f083Snicm 		item->client = cmd_find_client(item, NULL, 1);
624035dc73dSnicm 
625035dc73dSnicm 	if (entry->flags & CMD_CLIENT_CANFAIL)
626035dc73dSnicm 		quiet = 1;
627035dc73dSnicm 	if (entry->flags & CMD_CLIENT_CFLAG) {
628035dc73dSnicm 		tc = cmd_find_client(item, args_get(args, 'c'), quiet);
629035dc73dSnicm 		if (tc == NULL && !quiet) {
630035dc73dSnicm 			retval = CMD_RETURN_ERROR;
631035dc73dSnicm 			goto out;
632035dc73dSnicm 		}
633035dc73dSnicm 	} else if (entry->flags & CMD_CLIENT_TFLAG) {
634035dc73dSnicm 		tc = cmd_find_client(item, args_get(args, 't'), quiet);
635035dc73dSnicm 		if (tc == NULL && !quiet) {
636035dc73dSnicm 			retval = CMD_RETURN_ERROR;
637035dc73dSnicm 			goto out;
638035dc73dSnicm 		}
639035dc73dSnicm 	} else
640035dc73dSnicm 		tc = cmd_find_client(item, NULL, 1);
641035dc73dSnicm 	item->target_client = tc;
642035dc73dSnicm 
643bf0d297eSnicm 	retval = cmdq_find_flag(item, &item->source, &entry->source);
644bf0d297eSnicm 	if (retval == CMD_RETURN_ERROR)
645765b9a58Snicm 		goto out;
646bf0d297eSnicm 	retval = cmdq_find_flag(item, &item->target, &entry->target);
647765b9a58Snicm 	if (retval == CMD_RETURN_ERROR)
648765b9a58Snicm 		goto out;
649765b9a58Snicm 
650bf0d297eSnicm 	retval = entry->exec(cmd, item);
651bf0d297eSnicm 	if (retval == CMD_RETURN_ERROR)
652bf0d297eSnicm 		goto out;
653bf0d297eSnicm 
654bf0d297eSnicm 	if (entry->flags & CMD_AFTERHOOK) {
655bf0d297eSnicm 		if (cmd_find_valid_state(&item->target))
656bf0d297eSnicm 			fsp = &item->target;
657054f42acSnicm 		else if (cmd_find_valid_state(&item->state->current))
658054f42acSnicm 			fsp = &item->state->current;
6590772530eSnicm 		else if (cmd_find_from_client(&fs, item->client, 0) == 0)
660765b9a58Snicm 			fsp = &fs;
66193e732aaSnicm 		else
66293e732aaSnicm 			goto out;
663844b9093Snicm 		cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name);
664765b9a58Snicm 	}
665765b9a58Snicm 
666765b9a58Snicm out:
667035dc73dSnicm 	item->client = saved;
6681adaafb7Snicm 	if (retval == CMD_RETURN_ERROR) {
6691adaafb7Snicm 		fsp = NULL;
6701adaafb7Snicm 		if (cmd_find_valid_state(&item->target))
6711adaafb7Snicm 			fsp = &item->target;
6721adaafb7Snicm 		else if (cmd_find_valid_state(&item->state->current))
6731adaafb7Snicm 			fsp = &item->state->current;
6741adaafb7Snicm 		else if (cmd_find_from_client(&fs, item->client, 0) == 0)
6751adaafb7Snicm 			fsp = &fs;
6761adaafb7Snicm 		cmdq_insert_hook(fsp != NULL ? fsp->s : NULL, item, fsp,
6771adaafb7Snicm 		    "command-error");
67868e0a7f2Snicm 		cmdq_guard(item, "error", flags);
6791adaafb7Snicm 	} else
68068e0a7f2Snicm 		cmdq_guard(item, "end", flags);
681765b9a58Snicm 	return (retval);
682765b9a58Snicm }
683765b9a58Snicm 
684765b9a58Snicm /* Get a callback for the command queue. */
68568e0a7f2Snicm struct cmdq_item *
686d6aa0047Snicm cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
687175d36ccSnicm {
68868e0a7f2Snicm 	struct cmdq_item	*item;
689175d36ccSnicm 
69068e0a7f2Snicm 	item = xcalloc(1, sizeof *item);
6915edaef27Snicm 	xasprintf(&item->name, "[%s/%p]", name, item);
69268e0a7f2Snicm 	item->type = CMDQ_CALLBACK;
693c1e0bdabSnicm 
69468e0a7f2Snicm 	item->group = 0;
695c1e0bdabSnicm 	item->state = cmdq_new_state(NULL, NULL, 0);
696175d36ccSnicm 
69768e0a7f2Snicm 	item->cb = cb;
69868e0a7f2Snicm 	item->data = data;
6994fc586aaSnicm 
70068e0a7f2Snicm 	return (item);
701175d36ccSnicm }
702175d36ccSnicm 
7030f3d1a91Snicm /* Generic error callback. */
7040f3d1a91Snicm static enum cmd_retval
7050f3d1a91Snicm cmdq_error_callback(struct cmdq_item *item, void *data)
7060f3d1a91Snicm {
7070f3d1a91Snicm 	char	*error = data;
7080f3d1a91Snicm 
7090f3d1a91Snicm 	cmdq_error(item, "%s", error);
7100f3d1a91Snicm 	free(error);
7110f3d1a91Snicm 
7120f3d1a91Snicm 	return (CMD_RETURN_NORMAL);
7130f3d1a91Snicm }
7140f3d1a91Snicm 
7150f3d1a91Snicm /* Get an error callback for the command queue. */
7160f3d1a91Snicm struct cmdq_item *
7170f3d1a91Snicm cmdq_get_error(const char *error)
7180f3d1a91Snicm {
7190f3d1a91Snicm 	return (cmdq_get_callback(cmdq_error_callback, xstrdup(error)));
7200f3d1a91Snicm }
7210f3d1a91Snicm 
722765b9a58Snicm /* Fire callback on callback queue. */
723765b9a58Snicm static enum cmd_retval
72468e0a7f2Snicm cmdq_fire_callback(struct cmdq_item *item)
725175d36ccSnicm {
72668e0a7f2Snicm 	return (item->cb(item, item->data));
727765b9a58Snicm }
7289a67a5acSnicm 
729765b9a58Snicm /* Process next item on command queue. */
730765b9a58Snicm u_int
731765b9a58Snicm cmdq_next(struct client *c)
732765b9a58Snicm {
73368e0a7f2Snicm 	struct cmdq_list	*queue = cmdq_get(c);
734765b9a58Snicm 	const char		*name = cmdq_name(c);
73568e0a7f2Snicm 	struct cmdq_item	*item;
736765b9a58Snicm 	enum cmd_retval		 retval;
737765b9a58Snicm 	u_int			 items = 0;
738765b9a58Snicm 	static u_int		 number;
739765b9a58Snicm 
7405c72fbecSnicm 	if (TAILQ_EMPTY(&queue->list)) {
741765b9a58Snicm 		log_debug("%s %s: empty", __func__, name);
742765b9a58Snicm 		return (0);
743765b9a58Snicm 	}
7445c72fbecSnicm 	if (TAILQ_FIRST(&queue->list)->flags & CMDQ_WAITING) {
745765b9a58Snicm 		log_debug("%s %s: waiting", __func__, name);
746722808c8Snicm 		return (0);
747722808c8Snicm 	}
748175d36ccSnicm 
749765b9a58Snicm 	log_debug("%s %s: enter", __func__, name);
750765b9a58Snicm 	for (;;) {
7515c72fbecSnicm 		item = queue->item = TAILQ_FIRST(&queue->list);
75268e0a7f2Snicm 		if (item == NULL)
753765b9a58Snicm 			break;
754d6aa0047Snicm 		log_debug("%s %s: %s (%d), flags %x", __func__, name,
755d6aa0047Snicm 		    item->name, item->type, item->flags);
756765b9a58Snicm 
757765b9a58Snicm 		/*
758765b9a58Snicm 		 * Any item with the waiting flag set waits until an external
759765b9a58Snicm 		 * event clears the flag (for example, a job - look at
760765b9a58Snicm 		 * run-shell).
761765b9a58Snicm 		 */
76268e0a7f2Snicm 		if (item->flags & CMDQ_WAITING)
763765b9a58Snicm 			goto waiting;
764765b9a58Snicm 
765765b9a58Snicm 		/*
766765b9a58Snicm 		 * Items are only fired once, once the fired flag is set, a
767765b9a58Snicm 		 * waiting flag can only be cleared by an external event.
768765b9a58Snicm 		 */
76968e0a7f2Snicm 		if (~item->flags & CMDQ_FIRED) {
77068e0a7f2Snicm 			item->time = time(NULL);
77168e0a7f2Snicm 			item->number = ++number;
772765b9a58Snicm 
77333a044c7Snicm 			switch (item->type) {
77468e0a7f2Snicm 			case CMDQ_COMMAND:
77568e0a7f2Snicm 				retval = cmdq_fire_command(item);
776765b9a58Snicm 
777765b9a58Snicm 				/*
778765b9a58Snicm 				 * If a command returns an error, remove any
779765b9a58Snicm 				 * subsequent commands in the same group.
780765b9a58Snicm 				 */
781765b9a58Snicm 				if (retval == CMD_RETURN_ERROR)
78268e0a7f2Snicm 					cmdq_remove_group(item);
783765b9a58Snicm 				break;
78468e0a7f2Snicm 			case CMDQ_CALLBACK:
78568e0a7f2Snicm 				retval = cmdq_fire_callback(item);
786765b9a58Snicm 				break;
787765b9a58Snicm 			default:
788765b9a58Snicm 				retval = CMD_RETURN_ERROR;
789765b9a58Snicm 				break;
790765b9a58Snicm 			}
79168e0a7f2Snicm 			item->flags |= CMDQ_FIRED;
792765b9a58Snicm 
793765b9a58Snicm 			if (retval == CMD_RETURN_WAIT) {
79468e0a7f2Snicm 				item->flags |= CMDQ_WAITING;
795765b9a58Snicm 				goto waiting;
796765b9a58Snicm 			}
797765b9a58Snicm 			items++;
798765b9a58Snicm 		}
79968e0a7f2Snicm 		cmdq_remove(item);
800765b9a58Snicm 	}
8015c72fbecSnicm 	queue->item = NULL;
802765b9a58Snicm 
803765b9a58Snicm 	log_debug("%s %s: exit (empty)", __func__, name);
804765b9a58Snicm 	return (items);
805765b9a58Snicm 
806765b9a58Snicm waiting:
807765b9a58Snicm 	log_debug("%s %s: exit (wait)", __func__, name);
808765b9a58Snicm 	return (items);
809765b9a58Snicm }
810765b9a58Snicm 
8115c72fbecSnicm /* Get running item if any. */
8125c72fbecSnicm struct cmdq_item *
8135c72fbecSnicm cmdq_running(struct client *c)
8145c72fbecSnicm {
8155c72fbecSnicm 	struct cmdq_list	*queue = cmdq_get(c);
8165c72fbecSnicm 
81770449ab2Snicm 	if (queue->item == NULL)
81870449ab2Snicm 		return (NULL);
81970449ab2Snicm 	if (queue->item->flags & CMDQ_WAITING)
82070449ab2Snicm 		return (NULL);
8215c72fbecSnicm 	return (queue->item);
8225c72fbecSnicm }
8235c72fbecSnicm 
824765b9a58Snicm /* Print a guard line. */
825765b9a58Snicm void
82668e0a7f2Snicm cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
827765b9a58Snicm {
82868e0a7f2Snicm 	struct client	*c = item->client;
829f4bc7c7aSnicm 	long		 t = item->time;
830f4bc7c7aSnicm 	u_int		 number = item->number;
831765b9a58Snicm 
832f4bc7c7aSnicm 	if (c != NULL && (c->flags & CLIENT_CONTROL))
833a34cf9c8Snicm 		control_write(c, "%%%s %ld %u %d", guard, t, number, flags);
834175d36ccSnicm }
835175d36ccSnicm 
836175d36ccSnicm /* Show message from command. */
83774d4b937Snicm void
838*3a775927Snicm cmdq_print_data(struct cmdq_item *item, struct evbuffer *evb)
839175d36ccSnicm {
840*3a775927Snicm 	server_client_print(item->client, 1, evb);
841f260bbaaSnicm }
842f260bbaaSnicm 
843f260bbaaSnicm /* Show message from command. */
844f260bbaaSnicm void
845f260bbaaSnicm cmdq_print(struct cmdq_item *item, const char *fmt, ...)
846f260bbaaSnicm {
847f260bbaaSnicm 	va_list		 ap;
848f260bbaaSnicm 	struct evbuffer	*evb;
849f260bbaaSnicm 
850f260bbaaSnicm 	evb = evbuffer_new();
851f260bbaaSnicm 	if (evb == NULL)
852f260bbaaSnicm 		fatalx("out of memory");
853f260bbaaSnicm 
854f260bbaaSnicm 	va_start(ap, fmt);
855f260bbaaSnicm 	evbuffer_add_vprintf(evb, fmt, ap);
856f260bbaaSnicm 	va_end(ap);
857f260bbaaSnicm 
858*3a775927Snicm 	cmdq_print_data(item, evb);
859f260bbaaSnicm 	evbuffer_free(evb);
860175d36ccSnicm }
861175d36ccSnicm 
862175d36ccSnicm /* Show error from command. */
86374d4b937Snicm void
86468e0a7f2Snicm cmdq_error(struct cmdq_item *item, const char *fmt, ...)
865175d36ccSnicm {
86668e0a7f2Snicm 	struct client	*c = item->client;
86768e0a7f2Snicm 	struct cmd	*cmd = item->cmd;
868175d36ccSnicm 	va_list		 ap;
86990d7ba38Snicm 	char		*msg, *tmp;
87090d7ba38Snicm 	const char	*file;
87190d7ba38Snicm 	u_int		 line;
872175d36ccSnicm 
873175d36ccSnicm 	va_start(ap, fmt);
874f4bc7c7aSnicm 	xvasprintf(&msg, fmt, ap);
875175d36ccSnicm 	va_end(ap);
876175d36ccSnicm 
8776e8d04c2Snicm 	log_debug("%s: %s", __func__, msg);
8786e8d04c2Snicm 
87990d7ba38Snicm 	if (c == NULL) {
88090d7ba38Snicm 		cmd_get_source(cmd, &file, &line);
88190d7ba38Snicm 		cfg_add_cause("%s:%u: %s", file, line, msg);
88290d7ba38Snicm 	} else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
8830687354cSnicm 		server_add_message("%s message: %s", c->name, msg);
88462f1fdfdSnicm 		if (~c->flags & CLIENT_UTF8) {
88562f1fdfdSnicm 			tmp = msg;
88662f1fdfdSnicm 			msg = utf8_sanitize(tmp);
88762f1fdfdSnicm 			free(tmp);
88862f1fdfdSnicm 		}
88937b4450bSnicm 		if (c->flags & CLIENT_CONTROL)
890a34cf9c8Snicm 			control_write(c, "%s", msg);
89137b4450bSnicm 		else
892f4bc7c7aSnicm 			file_error(c, "%s\n", msg);
893f6a3ada0Snicm 		c->retval = 1;
894175d36ccSnicm 	} else {
895175d36ccSnicm 		*msg = toupper((u_char) *msg);
896e7e79d0aSnicm 		status_message_set(c, -1, 1, 0, "%s", msg);
897175d36ccSnicm 	}
898175d36ccSnicm 
899175d36ccSnicm 	free(msg);
900175d36ccSnicm }
901