xref: /netbsd-src/external/bsd/tmux/dist/cmd-queue.c (revision 890b6d91a44b7fcb2dfbcbc1e93463086e462d2d)
15494e770Schristos /* $OpenBSD$ */
2928fc495Schristos 
3928fc495Schristos /*
4ed4e6cd4Schristos  * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
5928fc495Schristos  *
6928fc495Schristos  * Permission to use, copy, modify, and distribute this software for any
7928fc495Schristos  * purpose with or without fee is hereby granted, provided that the above
8928fc495Schristos  * copyright notice and this permission notice appear in all copies.
9928fc495Schristos  *
10928fc495Schristos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11928fc495Schristos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12928fc495Schristos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13928fc495Schristos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14928fc495Schristos  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15928fc495Schristos  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16928fc495Schristos  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17928fc495Schristos  */
18928fc495Schristos 
19928fc495Schristos #include <sys/types.h>
20928fc495Schristos 
21928fc495Schristos #include <ctype.h>
2246548964Swiz #include <pwd.h>
23928fc495Schristos #include <stdlib.h>
245494e770Schristos #include <string.h>
25928fc495Schristos #include <time.h>
2646548964Swiz #include <unistd.h>
27928fc495Schristos 
28928fc495Schristos #include "tmux.h"
29928fc495Schristos 
30e271dbb8Schristos /* Command queue flags. */
31e271dbb8Schristos #define CMDQ_FIRED 0x1
32e271dbb8Schristos #define CMDQ_WAITING 0x2
33e271dbb8Schristos 
34e271dbb8Schristos /* Command queue item type. */
35e271dbb8Schristos enum cmdq_type {
36e271dbb8Schristos 	CMDQ_COMMAND,
37e271dbb8Schristos 	CMDQ_CALLBACK,
38e271dbb8Schristos };
39e271dbb8Schristos 
40e271dbb8Schristos /* Command queue item. */
41e271dbb8Schristos struct cmdq_item {
42e271dbb8Schristos 	char			*name;
43e271dbb8Schristos 	struct cmdq_list	*queue;
44e271dbb8Schristos 	struct cmdq_item	*next;
45e271dbb8Schristos 
46e271dbb8Schristos 	struct client		*client;
47e271dbb8Schristos 	struct client		*target_client;
48e271dbb8Schristos 
49e271dbb8Schristos 	enum cmdq_type		 type;
50e271dbb8Schristos 	u_int			 group;
51e271dbb8Schristos 
52e271dbb8Schristos 	u_int			 number;
53e271dbb8Schristos 	time_t			 time;
54e271dbb8Schristos 
55e271dbb8Schristos 	int			 flags;
56e271dbb8Schristos 
57e271dbb8Schristos 	struct cmdq_state	*state;
58e271dbb8Schristos 	struct cmd_find_state	 source;
59e271dbb8Schristos 	struct cmd_find_state	 target;
60e271dbb8Schristos 
61e271dbb8Schristos 	struct cmd_list		*cmdlist;
62e271dbb8Schristos 	struct cmd		*cmd;
63e271dbb8Schristos 
64e271dbb8Schristos 	cmdq_cb			 cb;
65e271dbb8Schristos 	void			*data;
66e271dbb8Schristos 
67e271dbb8Schristos 	TAILQ_ENTRY(cmdq_item)	 entry;
68e271dbb8Schristos };
69e271dbb8Schristos TAILQ_HEAD(cmdq_item_list, cmdq_item);
70e271dbb8Schristos 
71e271dbb8Schristos /*
72e271dbb8Schristos  * Command queue state. This is the context for commands on the command queue.
73e271dbb8Schristos  * It holds information about how the commands were fired (the key and flags),
74e271dbb8Schristos  * any additional formats for the commands, and the current default target.
75e271dbb8Schristos  * Multiple commands can share the same state and a command may update the
76e271dbb8Schristos  * default target.
77e271dbb8Schristos  */
78e271dbb8Schristos struct cmdq_state {
79e271dbb8Schristos 	int			 references;
80e271dbb8Schristos 	int			 flags;
81e271dbb8Schristos 
82e271dbb8Schristos 	struct format_tree	*formats;
83e271dbb8Schristos 
84e271dbb8Schristos 	struct key_event	 event;
85e271dbb8Schristos 	struct cmd_find_state	 current;
86e271dbb8Schristos };
87e271dbb8Schristos 
88e271dbb8Schristos /* Command queue. */
89e271dbb8Schristos struct cmdq_list {
90e271dbb8Schristos 	struct cmdq_item	*item;
91e271dbb8Schristos 	struct cmdq_item_list	 list;
92e271dbb8Schristos };
935494e770Schristos 
944e179ddaSchristos /* Get command queue name. */
954e179ddaSchristos static const char *
964e179ddaSchristos cmdq_name(struct client *c)
97928fc495Schristos {
9830744affSchristos 	static char	s[256];
99928fc495Schristos 
1004e179ddaSchristos 	if (c == NULL)
1014e179ddaSchristos 		return ("<global>");
10230744affSchristos 	if (c->name != NULL)
10330744affSchristos 		xsnprintf(s, sizeof s, "<%s>", c->name);
10430744affSchristos 	else
1054e179ddaSchristos 		xsnprintf(s, sizeof s, "<%p>", c);
1064e179ddaSchristos 	return (s);
107928fc495Schristos }
108928fc495Schristos 
1094e179ddaSchristos /* Get command queue from client. */
1104e179ddaSchristos static struct cmdq_list *
1114e179ddaSchristos cmdq_get(struct client *c)
112928fc495Schristos {
113e271dbb8Schristos 	static struct cmdq_list *global_queue;
114e271dbb8Schristos 
115e271dbb8Schristos 	if (c == NULL) {
116e271dbb8Schristos 		if (global_queue == NULL)
117e271dbb8Schristos 			global_queue = cmdq_new();
118e271dbb8Schristos 		return (global_queue);
119e271dbb8Schristos 	}
120e271dbb8Schristos 	return (c->queue);
121e271dbb8Schristos }
122e271dbb8Schristos 
123e271dbb8Schristos /* Create a queue. */
124e271dbb8Schristos struct cmdq_list *
125e271dbb8Schristos cmdq_new(void)
126e271dbb8Schristos {
127e271dbb8Schristos 	struct cmdq_list	*queue;
128e271dbb8Schristos 
129e271dbb8Schristos 	queue = xcalloc(1, sizeof *queue);
130e271dbb8Schristos 	TAILQ_INIT (&queue->list);
131e271dbb8Schristos 	return (queue);
132e271dbb8Schristos }
133e271dbb8Schristos 
134e271dbb8Schristos /* Free a queue. */
135e271dbb8Schristos void
136e271dbb8Schristos cmdq_free(struct cmdq_list *queue)
137e271dbb8Schristos {
138e271dbb8Schristos 	if (!TAILQ_EMPTY(&queue->list))
139e271dbb8Schristos 		fatalx("queue not empty");
140e271dbb8Schristos 	free(queue);
141e271dbb8Schristos }
142e271dbb8Schristos 
143e271dbb8Schristos /* Get item name. */
144e271dbb8Schristos const char *
145e271dbb8Schristos cmdq_get_name(struct cmdq_item *item)
146e271dbb8Schristos {
147e271dbb8Schristos 	return (item->name);
148e271dbb8Schristos }
149e271dbb8Schristos 
150e271dbb8Schristos /* Get item client. */
151e271dbb8Schristos struct client *
152e271dbb8Schristos cmdq_get_client(struct cmdq_item *item)
153e271dbb8Schristos {
154e271dbb8Schristos 	return (item->client);
155e271dbb8Schristos }
156e271dbb8Schristos 
157e271dbb8Schristos /* Get item target client. */
158e271dbb8Schristos struct client *
159e271dbb8Schristos cmdq_get_target_client(struct cmdq_item *item)
160e271dbb8Schristos {
161e271dbb8Schristos 	return (item->target_client);
162e271dbb8Schristos }
163e271dbb8Schristos 
164e271dbb8Schristos /* Get item state. */
165e271dbb8Schristos struct cmdq_state *
166e271dbb8Schristos cmdq_get_state(struct cmdq_item *item)
167e271dbb8Schristos {
168e271dbb8Schristos 	return (item->state);
169e271dbb8Schristos }
170e271dbb8Schristos 
171e271dbb8Schristos /* Get item target. */
172e271dbb8Schristos struct cmd_find_state *
173e271dbb8Schristos cmdq_get_target(struct cmdq_item *item)
174e271dbb8Schristos {
175e271dbb8Schristos 	return (&item->target);
176e271dbb8Schristos }
177e271dbb8Schristos 
178e271dbb8Schristos /* Get item source. */
179e271dbb8Schristos struct cmd_find_state *
180e271dbb8Schristos cmdq_get_source(struct cmdq_item *item)
181e271dbb8Schristos {
182e271dbb8Schristos 	return (&item->source);
183e271dbb8Schristos }
184e271dbb8Schristos 
185e271dbb8Schristos /* Get state event. */
186e271dbb8Schristos struct key_event *
187e271dbb8Schristos cmdq_get_event(struct cmdq_item *item)
188e271dbb8Schristos {
189e271dbb8Schristos 	return (&item->state->event);
190e271dbb8Schristos }
191e271dbb8Schristos 
192e271dbb8Schristos /* Get state current target. */
193e271dbb8Schristos struct cmd_find_state *
194e271dbb8Schristos cmdq_get_current(struct cmdq_item *item)
195e271dbb8Schristos {
196e271dbb8Schristos 	return (&item->state->current);
197e271dbb8Schristos }
198e271dbb8Schristos 
199e271dbb8Schristos /* Get state flags. */
200e271dbb8Schristos int
201e271dbb8Schristos cmdq_get_flags(struct cmdq_item *item)
202e271dbb8Schristos {
203e271dbb8Schristos 	return (item->state->flags);
204e271dbb8Schristos }
205e271dbb8Schristos 
206e271dbb8Schristos /* Create a new state. */
207e271dbb8Schristos struct cmdq_state *
208e271dbb8Schristos cmdq_new_state(struct cmd_find_state *current, struct key_event *event,
209e271dbb8Schristos     int flags)
210e271dbb8Schristos {
211e271dbb8Schristos 	struct cmdq_state	*state;
212e271dbb8Schristos 
213e271dbb8Schristos 	state = xcalloc(1, sizeof *state);
214e271dbb8Schristos 	state->references = 1;
215e271dbb8Schristos 	state->flags = flags;
216e271dbb8Schristos 
217e271dbb8Schristos 	if (event != NULL)
218e271dbb8Schristos 		memcpy(&state->event, event, sizeof state->event);
219e271dbb8Schristos 	else
220e271dbb8Schristos 		state->event.key = KEYC_NONE;
221e271dbb8Schristos 	if (current != NULL && cmd_find_valid_state(current))
222e271dbb8Schristos 		cmd_find_copy_state(&state->current, current);
223e271dbb8Schristos 	else
224e271dbb8Schristos 		cmd_find_clear_state(&state->current, 0);
225e271dbb8Schristos 
226e271dbb8Schristos 	return (state);
227e271dbb8Schristos }
228e271dbb8Schristos 
229e271dbb8Schristos /* Add a reference to a state. */
230e271dbb8Schristos struct cmdq_state *
231e271dbb8Schristos cmdq_link_state(struct cmdq_state *state)
232e271dbb8Schristos {
233e271dbb8Schristos 	state->references++;
234e271dbb8Schristos 	return (state);
235e271dbb8Schristos }
236e271dbb8Schristos 
237e271dbb8Schristos /* Make a copy of a state. */
238e271dbb8Schristos struct cmdq_state *
239f844e94eSwiz cmdq_copy_state(struct cmdq_state *state, struct cmd_find_state *current)
240e271dbb8Schristos {
241f844e94eSwiz 	if (current != NULL)
242f844e94eSwiz 		return (cmdq_new_state(current, &state->event, state->flags));
243e271dbb8Schristos 	return (cmdq_new_state(&state->current, &state->event, state->flags));
244e271dbb8Schristos }
245e271dbb8Schristos 
246e271dbb8Schristos /* Free a state. */
247e271dbb8Schristos void
248e271dbb8Schristos cmdq_free_state(struct cmdq_state *state)
249e271dbb8Schristos {
250e271dbb8Schristos 	if (--state->references != 0)
251e271dbb8Schristos 		return;
252e271dbb8Schristos 
253e271dbb8Schristos 	if (state->formats != NULL)
254e271dbb8Schristos 		format_free(state->formats);
255e271dbb8Schristos 	free(state);
256e271dbb8Schristos }
257e271dbb8Schristos 
258e271dbb8Schristos /* Add a format to command queue. */
259e271dbb8Schristos void
260e271dbb8Schristos cmdq_add_format(struct cmdq_state *state, const char *key, const char *fmt, ...)
261e271dbb8Schristos {
262e271dbb8Schristos 	va_list	 ap;
263e271dbb8Schristos 	char	*value;
264e271dbb8Schristos 
265e271dbb8Schristos 	va_start(ap, fmt);
266e271dbb8Schristos 	xvasprintf(&value, fmt, ap);
267e271dbb8Schristos 	va_end(ap);
268e271dbb8Schristos 
269e271dbb8Schristos 	if (state->formats == NULL)
270e271dbb8Schristos 		state->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
271e271dbb8Schristos 	format_add(state->formats, key, "%s", value);
272e271dbb8Schristos 
273e271dbb8Schristos 	free(value);
274e271dbb8Schristos }
275e271dbb8Schristos 
27646548964Swiz /* Add formats to command queue. */
27746548964Swiz void
27846548964Swiz cmdq_add_formats(struct cmdq_state *state, struct format_tree *ft)
27946548964Swiz {
28046548964Swiz 	if (state->formats == NULL)
28146548964Swiz 		state->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
28246548964Swiz 	format_merge(state->formats, ft);
28346548964Swiz }
28446548964Swiz 
285e271dbb8Schristos /* Merge formats from item. */
286e271dbb8Schristos void
287e271dbb8Schristos cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft)
288e271dbb8Schristos {
289e271dbb8Schristos 	const struct cmd_entry	*entry;
290e271dbb8Schristos 
291e271dbb8Schristos 	if (item->cmd != NULL) {
292e271dbb8Schristos 		entry = cmd_get_entry(item->cmd);
293e271dbb8Schristos 		format_add(ft, "command", "%s", entry->name);
294e271dbb8Schristos 	}
295e271dbb8Schristos 	if (item->state->formats != NULL)
296e271dbb8Schristos 		format_merge(ft, item->state->formats);
2974e179ddaSchristos }
2984e179ddaSchristos 
2994e179ddaSchristos /* Append an item. */
30068e6ba84Schristos struct cmdq_item *
3014e179ddaSchristos cmdq_append(struct client *c, struct cmdq_item *item)
3024e179ddaSchristos {
3034e179ddaSchristos 	struct cmdq_list	*queue = cmdq_get(c);
3044e179ddaSchristos 	struct cmdq_item	*next;
3054e179ddaSchristos 
3064e179ddaSchristos 	do {
3074e179ddaSchristos 		next = item->next;
3084e179ddaSchristos 		item->next = NULL;
3094e179ddaSchristos 
3104e179ddaSchristos 		if (c != NULL)
3114e179ddaSchristos 			c->references++;
3124e179ddaSchristos 		item->client = c;
3134e179ddaSchristos 
3144e179ddaSchristos 		item->queue = queue;
315e271dbb8Schristos 		TAILQ_INSERT_TAIL(&queue->list, item, entry);
31630744affSchristos 		log_debug("%s %s: %s", __func__, cmdq_name(c), item->name);
3174e179ddaSchristos 
3184e179ddaSchristos 		item = next;
3194e179ddaSchristos 	} while (item != NULL);
320e271dbb8Schristos 	return (TAILQ_LAST(&queue->list, cmdq_item_list));
3214e179ddaSchristos }
3224e179ddaSchristos 
3234e179ddaSchristos /* Insert an item. */
32468e6ba84Schristos struct cmdq_item *
3254e179ddaSchristos cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item)
3264e179ddaSchristos {
3274e179ddaSchristos 	struct client		*c = after->client;
3284e179ddaSchristos 	struct cmdq_list	*queue = after->queue;
3294e179ddaSchristos 	struct cmdq_item	*next;
3304e179ddaSchristos 
3314e179ddaSchristos 	do {
3324e179ddaSchristos 		next = item->next;
33330744affSchristos 		item->next = after->next;
33430744affSchristos 		after->next = item;
3354e179ddaSchristos 
3364e179ddaSchristos 		if (c != NULL)
3374e179ddaSchristos 			c->references++;
3384e179ddaSchristos 		item->client = c;
3394e179ddaSchristos 
3404e179ddaSchristos 		item->queue = queue;
341e271dbb8Schristos 		TAILQ_INSERT_AFTER(&queue->list, after, item, entry);
34230744affSchristos 		log_debug("%s %s: %s after %s", __func__, cmdq_name(c),
34330744affSchristos 		    item->name, after->name);
3444e179ddaSchristos 
34530744affSchristos 		after = item;
3464e179ddaSchristos 		item = next;
3474e179ddaSchristos 	} while (item != NULL);
34868e6ba84Schristos 	return (after);
3494e179ddaSchristos }
3504e179ddaSchristos 
35130744affSchristos /* Insert a hook. */
35230744affSchristos void
35330744affSchristos cmdq_insert_hook(struct session *s, struct cmdq_item *item,
354e271dbb8Schristos     struct cmd_find_state *current, const char *fmt, ...)
35530744affSchristos {
356e271dbb8Schristos 	struct cmdq_state		*state = item->state;
357e271dbb8Schristos 	struct cmd			*cmd = item->cmd;
358e271dbb8Schristos 	struct args			*args = cmd_get_args(cmd);
35946548964Swiz 	struct args_entry		*ae;
36046548964Swiz 	struct args_value		*av;
36130744affSchristos 	struct options			*oo;
36230744affSchristos 	va_list				 ap;
363e271dbb8Schristos 	char				*name, tmp[32], flag, *arguments;
36446548964Swiz 	u_int				 i;
365e271dbb8Schristos 	const char			*value;
36630744affSchristos 	struct cmdq_item		*new_item;
367e271dbb8Schristos 	struct cmdq_state		*new_state;
36830744affSchristos 	struct options_entry		*o;
36930744affSchristos 	struct options_array_item	*a;
37030744affSchristos 	struct cmd_list			*cmdlist;
37130744affSchristos 
372e271dbb8Schristos 	if (item->state->flags & CMDQ_STATE_NOHOOKS)
37330744affSchristos 		return;
37430744affSchristos 	if (s == NULL)
37530744affSchristos 		oo = global_s_options;
37630744affSchristos 	else
37730744affSchristos 		oo = s->options;
37830744affSchristos 
37930744affSchristos 	va_start(ap, fmt);
38030744affSchristos 	xvasprintf(&name, fmt, ap);
38130744affSchristos 	va_end(ap);
38230744affSchristos 
38330744affSchristos 	o = options_get(oo, name);
38430744affSchristos 	if (o == NULL) {
38530744affSchristos 		free(name);
38630744affSchristos 		return;
38730744affSchristos 	}
38830744affSchristos 	log_debug("running hook %s (parent %p)", name, item);
38930744affSchristos 
390e271dbb8Schristos 	/*
391e271dbb8Schristos 	 * The hooks get a new state because they should not update the current
392e271dbb8Schristos 	 * target or formats for any subsequent commands.
393e271dbb8Schristos 	 */
394e271dbb8Schristos 	new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS);
395e271dbb8Schristos 	cmdq_add_format(new_state, "hook", "%s", name);
396e271dbb8Schristos 
397e271dbb8Schristos 	arguments = args_print(args);
398e271dbb8Schristos 	cmdq_add_format(new_state, "hook_arguments", "%s", arguments);
399e271dbb8Schristos 	free(arguments);
400e271dbb8Schristos 
40146548964Swiz 	for (i = 0; i < args_count(args); i++) {
402e271dbb8Schristos 		xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i);
40346548964Swiz 		cmdq_add_format(new_state, tmp, "%s", args_string(args, i));
404e271dbb8Schristos 	}
40546548964Swiz 	flag = args_first(args, &ae);
406e271dbb8Schristos 	while (flag != 0) {
407e271dbb8Schristos 		value = args_get(args, flag);
408e271dbb8Schristos 		if (value == NULL) {
409e271dbb8Schristos 			xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag);
410e271dbb8Schristos 			cmdq_add_format(new_state, tmp, "1");
411e271dbb8Schristos 		} else {
412e271dbb8Schristos 			xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag);
413e271dbb8Schristos 			cmdq_add_format(new_state, tmp, "%s", value);
414e271dbb8Schristos 		}
415e271dbb8Schristos 
416e271dbb8Schristos 		i = 0;
41746548964Swiz 		av = args_first_value(args, flag);
41846548964Swiz 		while (av != NULL) {
419e271dbb8Schristos 			xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i);
42046548964Swiz 			cmdq_add_format(new_state, tmp, "%s", av->string);
421e271dbb8Schristos 			i++;
42246548964Swiz 			av = args_next_value(av);
423e271dbb8Schristos 		}
424e271dbb8Schristos 
42546548964Swiz 		flag = args_next(&ae);
426e271dbb8Schristos 	}
427e271dbb8Schristos 
42830744affSchristos 	a = options_array_first(o);
42930744affSchristos 	while (a != NULL) {
43030744affSchristos 		cmdlist = options_array_item_value(a)->cmdlist;
431e271dbb8Schristos 		if (cmdlist != NULL) {
432e271dbb8Schristos 			new_item = cmdq_get_command(cmdlist, new_state);
43368e6ba84Schristos 			if (item != NULL)
43468e6ba84Schristos 				item = cmdq_insert_after(item, new_item);
43568e6ba84Schristos 			else
43668e6ba84Schristos 				item = cmdq_append(NULL, new_item);
437e271dbb8Schristos 		}
43830744affSchristos 		a = options_array_next(a);
43930744affSchristos 	}
44030744affSchristos 
441e271dbb8Schristos 	cmdq_free_state(new_state);
44230744affSchristos 	free(name);
44330744affSchristos }
44430744affSchristos 
44530744affSchristos /* Continue processing command queue. */
44630744affSchristos void
44730744affSchristos cmdq_continue(struct cmdq_item *item)
44830744affSchristos {
44930744affSchristos 	item->flags &= ~CMDQ_WAITING;
45030744affSchristos }
45130744affSchristos 
4524e179ddaSchristos /* Remove an item. */
4534e179ddaSchristos static void
4544e179ddaSchristos cmdq_remove(struct cmdq_item *item)
4554e179ddaSchristos {
4564e179ddaSchristos 	if (item->client != NULL)
4574e179ddaSchristos 		server_client_unref(item->client);
45830744affSchristos 	if (item->cmdlist != NULL)
4594e179ddaSchristos 		cmd_list_free(item->cmdlist);
460e271dbb8Schristos 	cmdq_free_state(item->state);
4614e179ddaSchristos 
462e271dbb8Schristos 	TAILQ_REMOVE(&item->queue->list, item, entry);
4634e179ddaSchristos 
464e9a2d6faSchristos 	free(__UNCONST(item->name));
4654e179ddaSchristos 	free(item);
4664e179ddaSchristos }
4674e179ddaSchristos 
4684e179ddaSchristos /* Remove all subsequent items that match this item's group. */
4694e179ddaSchristos static void
4704e179ddaSchristos cmdq_remove_group(struct cmdq_item *item)
4714e179ddaSchristos {
4724e179ddaSchristos 	struct cmdq_item	*this, *next;
4734e179ddaSchristos 
47430744affSchristos 	if (item->group == 0)
47530744affSchristos 		return;
4764e179ddaSchristos 	this = TAILQ_NEXT(item, entry);
4774e179ddaSchristos 	while (this != NULL) {
4784e179ddaSchristos 		next = TAILQ_NEXT(this, entry);
4794e179ddaSchristos 		if (this->group == item->group)
4804e179ddaSchristos 			cmdq_remove(this);
4814e179ddaSchristos 		this = next;
4824e179ddaSchristos 	}
4834e179ddaSchristos }
4844e179ddaSchristos 
48546548964Swiz /* Empty command callback. */
48646548964Swiz static enum cmd_retval
48746548964Swiz cmdq_empty_command(__unused struct cmdq_item *item, __unused void *data)
48846548964Swiz {
48946548964Swiz 	return (CMD_RETURN_NORMAL);
49046548964Swiz }
49146548964Swiz 
4924e179ddaSchristos /* Get a command for the command queue. */
4934e179ddaSchristos struct cmdq_item *
494e271dbb8Schristos cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state)
4954e179ddaSchristos {
4964e179ddaSchristos 	struct cmdq_item	*item, *first = NULL, *last = NULL;
4974e179ddaSchristos 	struct cmd		*cmd;
498e271dbb8Schristos 	const struct cmd_entry	*entry;
499e271dbb8Schristos 	int			 created = 0;
500fe99a117Schristos 
50146548964Swiz 	if ((cmd = cmd_list_first(cmdlist)) == NULL)
50246548964Swiz 		return (cmdq_get_callback(cmdq_empty_command, NULL));
50346548964Swiz 
504e271dbb8Schristos 	if (state == NULL) {
505e271dbb8Schristos 		state = cmdq_new_state(NULL, NULL, 0);
506e271dbb8Schristos 		created = 1;
50730744affSchristos 	}
5084e179ddaSchristos 
509e271dbb8Schristos 	while (cmd != NULL) {
510e271dbb8Schristos 		entry = cmd_get_entry(cmd);
511e271dbb8Schristos 
5124e179ddaSchristos 		item = xcalloc(1, sizeof *item);
513e271dbb8Schristos 		xasprintf(&item->name, "[%s/%p]", entry->name, item);
5144e179ddaSchristos 		item->type = CMDQ_COMMAND;
5154e179ddaSchristos 
516e271dbb8Schristos 		item->group = cmd_get_group(cmd);
517e271dbb8Schristos 		item->state = cmdq_link_state(state);
5184e179ddaSchristos 
5194e179ddaSchristos 		item->cmdlist = cmdlist;
5204e179ddaSchristos 		item->cmd = cmd;
5214e179ddaSchristos 
5224e179ddaSchristos 		cmdlist->references++;
523e271dbb8Schristos 		log_debug("%s: %s group %u", __func__, item->name, item->group);
5244e179ddaSchristos 
5254e179ddaSchristos 		if (first == NULL)
5264e179ddaSchristos 			first = item;
5274e179ddaSchristos 		if (last != NULL)
5284e179ddaSchristos 			last->next = item;
5294e179ddaSchristos 		last = item;
530e271dbb8Schristos 
531e271dbb8Schristos 		cmd = cmd_list_next(cmd);
5324e179ddaSchristos 	}
533e271dbb8Schristos 
534e271dbb8Schristos 	if (created)
535e271dbb8Schristos 		cmdq_free_state(state);
5364e179ddaSchristos 	return (first);
5374e179ddaSchristos }
5384e179ddaSchristos 
539fe99a117Schristos /* Fill in flag for a command. */
540fe99a117Schristos static enum cmd_retval
541fe99a117Schristos cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs,
542fe99a117Schristos     const struct cmd_entry_flag *flag)
543fe99a117Schristos {
544fe99a117Schristos 	const char	*value;
545fe99a117Schristos 
546fe99a117Schristos 	if (flag->flag == 0) {
547e271dbb8Schristos 		cmd_find_from_client(fs, item->target_client, 0);
548fe99a117Schristos 		return (CMD_RETURN_NORMAL);
549fe99a117Schristos 	}
550fe99a117Schristos 
551e271dbb8Schristos 	value = args_get(cmd_get_args(item->cmd), flag->flag);
552fe99a117Schristos 	if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) {
553fe99a117Schristos 		cmd_find_clear_state(fs, 0);
554fe99a117Schristos 		return (CMD_RETURN_ERROR);
555fe99a117Schristos 	}
556fe99a117Schristos 	return (CMD_RETURN_NORMAL);
557fe99a117Schristos }
558fe99a117Schristos 
559e271dbb8Schristos /* Add message with command. */
560e271dbb8Schristos static void
561e271dbb8Schristos cmdq_add_message(struct cmdq_item *item)
562e271dbb8Schristos {
563e271dbb8Schristos 	struct client		*c = item->client;
564e271dbb8Schristos 	struct cmdq_state	*state = item->state;
56546548964Swiz 	const char		*key;
566e271dbb8Schristos 	char			*tmp;
56746548964Swiz 	uid_t                    uid;
56846548964Swiz 	struct passwd		*pw;
56946548964Swiz 	char                    *user = NULL;
570e271dbb8Schristos 
571e271dbb8Schristos 	tmp = cmd_print(item->cmd);
572e271dbb8Schristos 	if (c != NULL) {
57346548964Swiz 		uid = proc_get_peer_uid(c->peer);
57446548964Swiz 		if (uid != (uid_t)-1 && uid != getuid()) {
57546548964Swiz 			if ((pw = getpwuid(uid)) != NULL)
57646548964Swiz 				xasprintf(&user, "[%s]", pw->pw_name);
57746548964Swiz 			else
57846548964Swiz 				user = xstrdup("[unknown]");
57946548964Swiz 		} else
58046548964Swiz 			user = xstrdup("");
581e271dbb8Schristos 		if (c->session != NULL && state->event.key != KEYC_NONE) {
582e271dbb8Schristos 			key = key_string_lookup_key(state->event.key, 0);
58346548964Swiz 			server_add_message("%s%s key %s: %s", c->name, user,
58446548964Swiz 			    key, tmp);
58546548964Swiz 		} else {
58646548964Swiz 			server_add_message("%s%s command: %s", c->name, user,
58746548964Swiz 			    tmp);
58846548964Swiz 		}
58946548964Swiz 		free(user);
590e271dbb8Schristos 	} else
591e271dbb8Schristos 		server_add_message("command: %s", tmp);
592e271dbb8Schristos 	free(tmp);
593e271dbb8Schristos }
594e271dbb8Schristos 
5954e179ddaSchristos /* Fire command on command queue. */
5964e179ddaSchristos static enum cmd_retval
5974e179ddaSchristos cmdq_fire_command(struct cmdq_item *item)
5984e179ddaSchristos {
599e271dbb8Schristos 	const char		*name = cmdq_name(item->client);
600e271dbb8Schristos 	struct cmdq_state	*state = item->state;
6014e179ddaSchristos 	struct cmd		*cmd = item->cmd;
602e271dbb8Schristos 	struct args		*args = cmd_get_args(cmd);
603e271dbb8Schristos 	const struct cmd_entry	*entry = cmd_get_entry(cmd);
604e271dbb8Schristos 	struct client		*tc, *saved = item->client;
6054e179ddaSchristos 	enum cmd_retval		 retval;
6064e179ddaSchristos 	struct cmd_find_state	*fsp, fs;
607e271dbb8Schristos 	int			 flags, quiet = 0;
60830744affSchristos 	char			*tmp;
6094e179ddaSchristos 
610e271dbb8Schristos 	if (cfg_finished)
611e271dbb8Schristos 		cmdq_add_message(item);
61230744affSchristos 	if (log_get_level() > 1) {
61330744affSchristos 		tmp = cmd_print(cmd);
61430744affSchristos 		log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp);
61530744affSchristos 		free(tmp);
61630744affSchristos 	}
61730744affSchristos 
618e271dbb8Schristos 	flags = !!(state->flags & CMDQ_STATE_CONTROL);
6194e179ddaSchristos 	cmdq_guard(item, "begin", flags);
6204e179ddaSchristos 
6214e179ddaSchristos 	if (item->client == NULL)
622fe99a117Schristos 		item->client = cmd_find_client(item, NULL, 1);
623e271dbb8Schristos 
624e271dbb8Schristos 	if (entry->flags & CMD_CLIENT_CANFAIL)
625e271dbb8Schristos 		quiet = 1;
626e271dbb8Schristos 	if (entry->flags & CMD_CLIENT_CFLAG) {
627e271dbb8Schristos 		tc = cmd_find_client(item, args_get(args, 'c'), quiet);
628e271dbb8Schristos 		if (tc == NULL && !quiet) {
629e271dbb8Schristos 			retval = CMD_RETURN_ERROR;
630e271dbb8Schristos 			goto out;
631e271dbb8Schristos 		}
632e271dbb8Schristos 	} else if (entry->flags & CMD_CLIENT_TFLAG) {
633e271dbb8Schristos 		tc = cmd_find_client(item, args_get(args, 't'), quiet);
634e271dbb8Schristos 		if (tc == NULL && !quiet) {
635e271dbb8Schristos 			retval = CMD_RETURN_ERROR;
636e271dbb8Schristos 			goto out;
637e271dbb8Schristos 		}
638e271dbb8Schristos 	} else
639e271dbb8Schristos 		tc = cmd_find_client(item, NULL, 1);
640e271dbb8Schristos 	item->target_client = tc;
641e271dbb8Schristos 
642fe99a117Schristos 	retval = cmdq_find_flag(item, &item->source, &entry->source);
643fe99a117Schristos 	if (retval == CMD_RETURN_ERROR)
644fe99a117Schristos 		goto out;
645fe99a117Schristos 	retval = cmdq_find_flag(item, &item->target, &entry->target);
6464e179ddaSchristos 	if (retval == CMD_RETURN_ERROR)
6474e179ddaSchristos 		goto out;
6484e179ddaSchristos 
649fe99a117Schristos 	retval = entry->exec(cmd, item);
650fe99a117Schristos 	if (retval == CMD_RETURN_ERROR)
6514e179ddaSchristos 		goto out;
652fe99a117Schristos 
653fe99a117Schristos 	if (entry->flags & CMD_AFTERHOOK) {
654fe99a117Schristos 		if (cmd_find_valid_state(&item->target))
655fe99a117Schristos 			fsp = &item->target;
656e271dbb8Schristos 		else if (cmd_find_valid_state(&item->state->current))
657e271dbb8Schristos 			fsp = &item->state->current;
658fe99a117Schristos 		else if (cmd_find_from_client(&fs, item->client, 0) == 0)
6594e179ddaSchristos 			fsp = &fs;
660fe99a117Schristos 		else
661fe99a117Schristos 			goto out;
66230744affSchristos 		cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name);
6634e179ddaSchristos 	}
6644e179ddaSchristos 
6654e179ddaSchristos out:
666e271dbb8Schristos 	item->client = saved;
667*890b6d91Swiz 	if (retval == CMD_RETURN_ERROR) {
668*890b6d91Swiz 		fsp = NULL;
669*890b6d91Swiz 		if (cmd_find_valid_state(&item->target))
670*890b6d91Swiz 			fsp = &item->target;
671*890b6d91Swiz 		else if (cmd_find_valid_state(&item->state->current))
672*890b6d91Swiz 			fsp = &item->state->current;
673*890b6d91Swiz 		else if (cmd_find_from_client(&fs, item->client, 0) == 0)
674*890b6d91Swiz 			fsp = &fs;
675*890b6d91Swiz 		cmdq_insert_hook(fsp != NULL ? fsp->s : NULL, item, fsp,
676*890b6d91Swiz 		    "command-error");
6774e179ddaSchristos 		cmdq_guard(item, "error", flags);
678*890b6d91Swiz 	} else
6794e179ddaSchristos 		cmdq_guard(item, "end", flags);
6804e179ddaSchristos 	return (retval);
6814e179ddaSchristos }
6824e179ddaSchristos 
6834e179ddaSchristos /* Get a callback for the command queue. */
6844e179ddaSchristos struct cmdq_item *
6854e179ddaSchristos cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
6864e179ddaSchristos {
6874e179ddaSchristos 	struct cmdq_item	*item;
6884e179ddaSchristos 
6894e179ddaSchristos 	item = xcalloc(1, sizeof *item);
69030744affSchristos 	xasprintf(&item->name, "[%s/%p]", name, item);
6914e179ddaSchristos 	item->type = CMDQ_CALLBACK;
6924e179ddaSchristos 
6934e179ddaSchristos 	item->group = 0;
694e271dbb8Schristos 	item->state = cmdq_new_state(NULL, NULL, 0);
6954e179ddaSchristos 
6964e179ddaSchristos 	item->cb = cb;
6974e179ddaSchristos 	item->data = data;
6984e179ddaSchristos 
6994e179ddaSchristos 	return (item);
7004e179ddaSchristos }
7014e179ddaSchristos 
70230744affSchristos /* Generic error callback. */
70330744affSchristos static enum cmd_retval
70430744affSchristos cmdq_error_callback(struct cmdq_item *item, void *data)
70530744affSchristos {
70630744affSchristos 	char	*error = data;
70730744affSchristos 
70830744affSchristos 	cmdq_error(item, "%s", error);
70930744affSchristos 	free(error);
71030744affSchristos 
71130744affSchristos 	return (CMD_RETURN_NORMAL);
71230744affSchristos }
71330744affSchristos 
71430744affSchristos /* Get an error callback for the command queue. */
71530744affSchristos struct cmdq_item *
71630744affSchristos cmdq_get_error(const char *error)
71730744affSchristos {
71830744affSchristos 	return (cmdq_get_callback(cmdq_error_callback, xstrdup(error)));
71930744affSchristos }
72030744affSchristos 
7214e179ddaSchristos /* Fire callback on callback queue. */
7224e179ddaSchristos static enum cmd_retval
7234e179ddaSchristos cmdq_fire_callback(struct cmdq_item *item)
7244e179ddaSchristos {
7254e179ddaSchristos 	return (item->cb(item, item->data));
7264e179ddaSchristos }
7274e179ddaSchristos 
7284e179ddaSchristos /* Process next item on command queue. */
7294e179ddaSchristos u_int
7304e179ddaSchristos cmdq_next(struct client *c)
7314e179ddaSchristos {
7324e179ddaSchristos 	struct cmdq_list	*queue = cmdq_get(c);
7334e179ddaSchristos 	const char		*name = cmdq_name(c);
7344e179ddaSchristos 	struct cmdq_item	*item;
7354e179ddaSchristos 	enum cmd_retval		 retval;
7364e179ddaSchristos 	u_int			 items = 0;
7374e179ddaSchristos 	static u_int		 number;
7384e179ddaSchristos 
739e271dbb8Schristos 	if (TAILQ_EMPTY(&queue->list)) {
7404e179ddaSchristos 		log_debug("%s %s: empty", __func__, name);
7414e179ddaSchristos 		return (0);
7424e179ddaSchristos 	}
743e271dbb8Schristos 	if (TAILQ_FIRST(&queue->list)->flags & CMDQ_WAITING) {
7444e179ddaSchristos 		log_debug("%s %s: waiting", __func__, name);
7455494e770Schristos 		return (0);
7465494e770Schristos 	}
747928fc495Schristos 
7484e179ddaSchristos 	log_debug("%s %s: enter", __func__, name);
7494e179ddaSchristos 	for (;;) {
750e271dbb8Schristos 		item = queue->item = TAILQ_FIRST(&queue->list);
7514e179ddaSchristos 		if (item == NULL)
7524e179ddaSchristos 			break;
7534e179ddaSchristos 		log_debug("%s %s: %s (%d), flags %x", __func__, name,
7544e179ddaSchristos 		    item->name, item->type, item->flags);
7554e179ddaSchristos 
7564e179ddaSchristos 		/*
7574e179ddaSchristos 		 * Any item with the waiting flag set waits until an external
7584e179ddaSchristos 		 * event clears the flag (for example, a job - look at
7594e179ddaSchristos 		 * run-shell).
7604e179ddaSchristos 		 */
7614e179ddaSchristos 		if (item->flags & CMDQ_WAITING)
7624e179ddaSchristos 			goto waiting;
7634e179ddaSchristos 
7644e179ddaSchristos 		/*
7654e179ddaSchristos 		 * Items are only fired once, once the fired flag is set, a
7664e179ddaSchristos 		 * waiting flag can only be cleared by an external event.
7674e179ddaSchristos 		 */
7684e179ddaSchristos 		if (~item->flags & CMDQ_FIRED) {
7694e179ddaSchristos 			item->time = time(NULL);
7704e179ddaSchristos 			item->number = ++number;
7714e179ddaSchristos 
772fe99a117Schristos 			switch (item->type) {
7734e179ddaSchristos 			case CMDQ_COMMAND:
7744e179ddaSchristos 				retval = cmdq_fire_command(item);
7754e179ddaSchristos 
7764e179ddaSchristos 				/*
7774e179ddaSchristos 				 * If a command returns an error, remove any
7784e179ddaSchristos 				 * subsequent commands in the same group.
7794e179ddaSchristos 				 */
7804e179ddaSchristos 				if (retval == CMD_RETURN_ERROR)
7814e179ddaSchristos 					cmdq_remove_group(item);
7824e179ddaSchristos 				break;
7834e179ddaSchristos 			case CMDQ_CALLBACK:
7844e179ddaSchristos 				retval = cmdq_fire_callback(item);
7854e179ddaSchristos 				break;
7864e179ddaSchristos 			default:
7874e179ddaSchristos 				retval = CMD_RETURN_ERROR;
7884e179ddaSchristos 				break;
7894e179ddaSchristos 			}
7904e179ddaSchristos 			item->flags |= CMDQ_FIRED;
7914e179ddaSchristos 
7924e179ddaSchristos 			if (retval == CMD_RETURN_WAIT) {
7934e179ddaSchristos 				item->flags |= CMDQ_WAITING;
7944e179ddaSchristos 				goto waiting;
7954e179ddaSchristos 			}
7964e179ddaSchristos 			items++;
7974e179ddaSchristos 		}
7984e179ddaSchristos 		cmdq_remove(item);
7994e179ddaSchristos 	}
800e271dbb8Schristos 	queue->item = NULL;
8014e179ddaSchristos 
8024e179ddaSchristos 	log_debug("%s %s: exit (empty)", __func__, name);
8034e179ddaSchristos 	return (items);
8044e179ddaSchristos 
8054e179ddaSchristos waiting:
8064e179ddaSchristos 	log_debug("%s %s: exit (wait)", __func__, name);
8074e179ddaSchristos 	return (items);
8084e179ddaSchristos }
8094e179ddaSchristos 
810e271dbb8Schristos /* Get running item if any. */
811e271dbb8Schristos struct cmdq_item *
812e271dbb8Schristos cmdq_running(struct client *c)
813e271dbb8Schristos {
814e271dbb8Schristos 	struct cmdq_list	*queue = cmdq_get(c);
815e271dbb8Schristos 
816e271dbb8Schristos 	if (queue->item == NULL)
817e271dbb8Schristos 		return (NULL);
818e271dbb8Schristos 	if (queue->item->flags & CMDQ_WAITING)
819e271dbb8Schristos 		return (NULL);
820e271dbb8Schristos 	return (queue->item);
821e271dbb8Schristos }
822e271dbb8Schristos 
8234e179ddaSchristos /* Print a guard line. */
8244e179ddaSchristos void
8254e179ddaSchristos cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
8264e179ddaSchristos {
8274e179ddaSchristos 	struct client	*c = item->client;
82868e6ba84Schristos 	long		 t = item->time;
82968e6ba84Schristos 	u_int		 number = item->number;
8304e179ddaSchristos 
83168e6ba84Schristos 	if (c != NULL && (c->flags & CLIENT_CONTROL))
832e271dbb8Schristos 		control_write(c, "%%%s %ld %u %d", guard, t, number, flags);
833928fc495Schristos }
834928fc495Schristos 
835928fc495Schristos /* Show message from command. */
8365494e770Schristos void
837f844e94eSwiz cmdq_print_data(struct cmdq_item *item, int parse, struct evbuffer *evb)
838f844e94eSwiz {
839f844e94eSwiz 	server_client_print(item->client, parse, evb);
840f844e94eSwiz }
841f844e94eSwiz 
842f844e94eSwiz /* Show message from command. */
843f844e94eSwiz void
8444e179ddaSchristos cmdq_print(struct cmdq_item *item, const char *fmt, ...)
845928fc495Schristos {
846928fc495Schristos 	va_list		 ap;
847f844e94eSwiz 	struct evbuffer	*evb;
848f844e94eSwiz 
849f844e94eSwiz 	evb = evbuffer_new();
850f844e94eSwiz 	if (evb == NULL)
851f844e94eSwiz 		fatalx("out of memory");
852928fc495Schristos 
853928fc495Schristos 	va_start(ap, fmt);
854f844e94eSwiz 	evbuffer_add_vprintf(evb, fmt, ap);
85568e6ba84Schristos 	va_end(ap);
85668e6ba84Schristos 
857f844e94eSwiz 	cmdq_print_data(item, 0, evb);
858f844e94eSwiz 	evbuffer_free(evb);
859928fc495Schristos }
860928fc495Schristos 
861928fc495Schristos /* Show error from command. */
8625494e770Schristos void
8634e179ddaSchristos cmdq_error(struct cmdq_item *item, const char *fmt, ...)
864928fc495Schristos {
8654e179ddaSchristos 	struct client	*c = item->client;
8664e179ddaSchristos 	struct cmd	*cmd = item->cmd;
867928fc495Schristos 	va_list		 ap;
868e271dbb8Schristos 	char		*msg, *tmp;
869e271dbb8Schristos 	const char	*file;
870e271dbb8Schristos 	u_int		 line;
871928fc495Schristos 
872928fc495Schristos 	va_start(ap, fmt);
87368e6ba84Schristos 	xvasprintf(&msg, fmt, ap);
874928fc495Schristos 	va_end(ap);
875928fc495Schristos 
876fe99a117Schristos 	log_debug("%s: %s", __func__, msg);
877fe99a117Schristos 
878e271dbb8Schristos 	if (c == NULL) {
879e271dbb8Schristos 		cmd_get_source(cmd, &file, &line);
880e271dbb8Schristos 		cfg_add_cause("%s:%u: %s", file, line, msg);
881e271dbb8Schristos 	} else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
882e271dbb8Schristos 		server_add_message("%s message: %s", c->name, msg);
883ed4e6cd4Schristos 		if (~c->flags & CLIENT_UTF8) {
884ed4e6cd4Schristos 			tmp = msg;
885ed4e6cd4Schristos 			msg = utf8_sanitize(tmp);
886ed4e6cd4Schristos 			free(tmp);
887ed4e6cd4Schristos 		}
88868e6ba84Schristos 		if (c->flags & CLIENT_CONTROL)
889e271dbb8Schristos 			control_write(c, "%s", msg);
89068e6ba84Schristos 		else
89168e6ba84Schristos 			file_error(c, "%s\n", msg);
892928fc495Schristos 		c->retval = 1;
893928fc495Schristos 	} else {
894928fc495Schristos 		*msg = toupper((u_char) *msg);
895e271dbb8Schristos 		status_message_set(c, -1, 1, 0, "%s", msg);
896928fc495Schristos 	}
897928fc495Schristos 
898928fc495Schristos 	free(msg);
899928fc495Schristos }
900