File: | src/usr.bin/tmux/cmd-queue.c |
Warning: | line 746, column 7 Use of memory after it is freed |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* $OpenBSD: cmd-queue.c,v 1.115 2023/09/15 06:31:49 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 <pwd.h> | ||||
23 | #include <stdlib.h> | ||||
24 | #include <string.h> | ||||
25 | #include <time.h> | ||||
26 | #include <unistd.h> | ||||
27 | #include <vis.h> | ||||
28 | |||||
29 | #include "tmux.h" | ||||
30 | |||||
31 | /* Command queue flags. */ | ||||
32 | #define CMDQ_FIRED0x1 0x1 | ||||
33 | #define CMDQ_WAITING0x2 0x2 | ||||
34 | |||||
35 | /* Command queue item type. */ | ||||
36 | enum cmdq_type { | ||||
37 | CMDQ_COMMAND, | ||||
38 | CMDQ_CALLBACK, | ||||
39 | }; | ||||
40 | |||||
41 | /* Command queue item. */ | ||||
42 | struct cmdq_item { | ||||
43 | char *name; | ||||
44 | struct cmdq_list *queue; | ||||
45 | struct cmdq_item *next; | ||||
46 | |||||
47 | struct client *client; | ||||
48 | struct client *target_client; | ||||
49 | |||||
50 | enum cmdq_type type; | ||||
51 | u_int group; | ||||
52 | |||||
53 | u_int number; | ||||
54 | time_t time; | ||||
55 | |||||
56 | int flags; | ||||
57 | |||||
58 | struct cmdq_state *state; | ||||
59 | struct cmd_find_state source; | ||||
60 | struct cmd_find_state target; | ||||
61 | |||||
62 | struct cmd_list *cmdlist; | ||||
63 | struct cmd *cmd; | ||||
64 | |||||
65 | cmdq_cb cb; | ||||
66 | void *data; | ||||
67 | |||||
68 | TAILQ_ENTRY(cmdq_item)struct { struct cmdq_item *tqe_next; struct cmdq_item **tqe_prev ; } entry; | ||||
69 | }; | ||||
70 | TAILQ_HEAD(cmdq_item_list, cmdq_item)struct cmdq_item_list { struct cmdq_item *tqh_first; struct cmdq_item **tqh_last; }; | ||||
71 | |||||
72 | /* | ||||
73 | * Command queue state. This is the context for commands on the command queue. | ||||
74 | * It holds information about how the commands were fired (the key and flags), | ||||
75 | * any additional formats for the commands, and the current default target. | ||||
76 | * Multiple commands can share the same state and a command may update the | ||||
77 | * default target. | ||||
78 | */ | ||||
79 | struct cmdq_state { | ||||
80 | int references; | ||||
81 | int flags; | ||||
82 | |||||
83 | struct format_tree *formats; | ||||
84 | |||||
85 | struct key_event event; | ||||
86 | struct cmd_find_state current; | ||||
87 | }; | ||||
88 | |||||
89 | /* Command queue. */ | ||||
90 | struct cmdq_list { | ||||
91 | struct cmdq_item *item; | ||||
92 | struct cmdq_item_list list; | ||||
93 | }; | ||||
94 | |||||
95 | /* Get command queue name. */ | ||||
96 | static const char * | ||||
97 | cmdq_name(struct client *c) | ||||
98 | { | ||||
99 | static char s[256]; | ||||
100 | |||||
101 | if (c == NULL((void *)0)) | ||||
102 | return ("<global>"); | ||||
103 | if (c->name != NULL((void *)0)) | ||||
104 | xsnprintf(s, sizeof s, "<%s>", c->name); | ||||
105 | else | ||||
106 | xsnprintf(s, sizeof s, "<%p>", c); | ||||
107 | return (s); | ||||
108 | } | ||||
109 | |||||
110 | /* Get command queue from client. */ | ||||
111 | static struct cmdq_list * | ||||
112 | cmdq_get(struct client *c) | ||||
113 | { | ||||
114 | static struct cmdq_list *global_queue; | ||||
115 | |||||
116 | if (c == NULL((void *)0)) { | ||||
117 | if (global_queue == NULL((void *)0)) | ||||
118 | global_queue = cmdq_new(); | ||||
119 | return (global_queue); | ||||
120 | } | ||||
121 | return (c->queue); | ||||
122 | } | ||||
123 | |||||
124 | /* Create a queue. */ | ||||
125 | struct cmdq_list * | ||||
126 | cmdq_new(void) | ||||
127 | { | ||||
128 | struct cmdq_list *queue; | ||||
129 | |||||
130 | queue = xcalloc(1, sizeof *queue); | ||||
131 | TAILQ_INIT (&queue->list)do { (&queue->list)->tqh_first = ((void *)0); (& queue->list)->tqh_last = &(&queue->list)-> tqh_first; } while (0); | ||||
132 | return (queue); | ||||
133 | } | ||||
134 | |||||
135 | /* Free a queue. */ | ||||
136 | void | ||||
137 | cmdq_free(struct cmdq_list *queue) | ||||
138 | { | ||||
139 | if (!TAILQ_EMPTY(&queue->list)(((&queue->list)->tqh_first) == ((void *)0))) | ||||
140 | fatalx("queue not empty"); | ||||
141 | free(queue); | ||||
142 | } | ||||
143 | |||||
144 | /* Get item name. */ | ||||
145 | const char * | ||||
146 | cmdq_get_name(struct cmdq_item *item) | ||||
147 | { | ||||
148 | return (item->name); | ||||
149 | } | ||||
150 | |||||
151 | /* Get item client. */ | ||||
152 | struct client * | ||||
153 | cmdq_get_client(struct cmdq_item *item) | ||||
154 | { | ||||
155 | return (item->client); | ||||
156 | } | ||||
157 | |||||
158 | /* Get item target client. */ | ||||
159 | struct client * | ||||
160 | cmdq_get_target_client(struct cmdq_item *item) | ||||
161 | { | ||||
162 | return (item->target_client); | ||||
163 | } | ||||
164 | |||||
165 | /* Get item state. */ | ||||
166 | struct cmdq_state * | ||||
167 | cmdq_get_state(struct cmdq_item *item) | ||||
168 | { | ||||
169 | return (item->state); | ||||
170 | } | ||||
171 | |||||
172 | /* Get item target. */ | ||||
173 | struct cmd_find_state * | ||||
174 | cmdq_get_target(struct cmdq_item *item) | ||||
175 | { | ||||
176 | return (&item->target); | ||||
177 | } | ||||
178 | |||||
179 | /* Get item source. */ | ||||
180 | struct cmd_find_state * | ||||
181 | cmdq_get_source(struct cmdq_item *item) | ||||
182 | { | ||||
183 | return (&item->source); | ||||
184 | } | ||||
185 | |||||
186 | /* Get state event. */ | ||||
187 | struct key_event * | ||||
188 | cmdq_get_event(struct cmdq_item *item) | ||||
189 | { | ||||
190 | return (&item->state->event); | ||||
191 | } | ||||
192 | |||||
193 | /* Get state current target. */ | ||||
194 | struct cmd_find_state * | ||||
195 | cmdq_get_current(struct cmdq_item *item) | ||||
196 | { | ||||
197 | return (&item->state->current); | ||||
198 | } | ||||
199 | |||||
200 | /* Get state flags. */ | ||||
201 | int | ||||
202 | cmdq_get_flags(struct cmdq_item *item) | ||||
203 | { | ||||
204 | return (item->state->flags); | ||||
205 | } | ||||
206 | |||||
207 | /* Create a new state. */ | ||||
208 | struct cmdq_state * | ||||
209 | cmdq_new_state(struct cmd_find_state *current, struct key_event *event, | ||||
210 | int flags) | ||||
211 | { | ||||
212 | struct cmdq_state *state; | ||||
213 | |||||
214 | state = xcalloc(1, sizeof *state); | ||||
215 | state->references = 1; | ||||
216 | state->flags = flags; | ||||
217 | |||||
218 | if (event != NULL((void *)0)) | ||||
219 | memcpy(&state->event, event, sizeof state->event); | ||||
220 | else | ||||
221 | state->event.key = KEYC_NONE0x000ff000000000ULL; | ||||
222 | if (current != NULL((void *)0) && cmd_find_valid_state(current)) | ||||
223 | cmd_find_copy_state(&state->current, current); | ||||
224 | else | ||||
225 | cmd_find_clear_state(&state->current, 0); | ||||
226 | |||||
227 | return (state); | ||||
228 | } | ||||
229 | |||||
230 | /* Add a reference to a state. */ | ||||
231 | struct cmdq_state * | ||||
232 | cmdq_link_state(struct cmdq_state *state) | ||||
233 | { | ||||
234 | state->references++; | ||||
235 | return (state); | ||||
236 | } | ||||
237 | |||||
238 | /* Make a copy of a state. */ | ||||
239 | struct cmdq_state * | ||||
240 | cmdq_copy_state(struct cmdq_state *state, struct cmd_find_state *current) | ||||
241 | { | ||||
242 | if (current != NULL((void *)0)) | ||||
243 | return (cmdq_new_state(current, &state->event, state->flags)); | ||||
244 | return (cmdq_new_state(&state->current, &state->event, state->flags)); | ||||
245 | } | ||||
246 | |||||
247 | /* Free a state. */ | ||||
248 | void | ||||
249 | cmdq_free_state(struct cmdq_state *state) | ||||
250 | { | ||||
251 | if (--state->references != 0) | ||||
252 | return; | ||||
253 | |||||
254 | if (state->formats != NULL((void *)0)) | ||||
255 | format_free(state->formats); | ||||
256 | free(state); | ||||
257 | } | ||||
258 | |||||
259 | /* Add a format to command queue. */ | ||||
260 | void | ||||
261 | cmdq_add_format(struct cmdq_state *state, const char *key, const char *fmt, ...) | ||||
262 | { | ||||
263 | va_list ap; | ||||
264 | char *value; | ||||
265 | |||||
266 | va_start(ap, fmt)__builtin_va_start((ap), fmt); | ||||
267 | xvasprintf(&value, fmt, ap); | ||||
268 | va_end(ap)__builtin_va_end((ap)); | ||||
269 | |||||
270 | if (state->formats == NULL((void *)0)) | ||||
271 | state->formats = format_create(NULL((void *)0), NULL((void *)0), FORMAT_NONE0, 0); | ||||
272 | format_add(state->formats, key, "%s", value); | ||||
273 | |||||
274 | free(value); | ||||
275 | } | ||||
276 | |||||
277 | /* Add formats to command queue. */ | ||||
278 | void | ||||
279 | cmdq_add_formats(struct cmdq_state *state, struct format_tree *ft) | ||||
280 | { | ||||
281 | if (state->formats == NULL((void *)0)) | ||||
282 | state->formats = format_create(NULL((void *)0), NULL((void *)0), FORMAT_NONE0, 0); | ||||
283 | format_merge(state->formats, ft); | ||||
284 | } | ||||
285 | |||||
286 | /* Merge formats from item. */ | ||||
287 | void | ||||
288 | cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft) | ||||
289 | { | ||||
290 | const struct cmd_entry *entry; | ||||
291 | |||||
292 | if (item->cmd != NULL((void *)0)) { | ||||
293 | entry = cmd_get_entry(item->cmd); | ||||
294 | format_add(ft, "command", "%s", entry->name); | ||||
295 | } | ||||
296 | if (item->state->formats != NULL((void *)0)) | ||||
297 | format_merge(ft, item->state->formats); | ||||
298 | } | ||||
299 | |||||
300 | /* Append an item. */ | ||||
301 | struct cmdq_item * | ||||
302 | cmdq_append(struct client *c, struct cmdq_item *item) | ||||
303 | { | ||||
304 | struct cmdq_list *queue = cmdq_get(c); | ||||
305 | struct cmdq_item *next; | ||||
306 | |||||
307 | do { | ||||
308 | next = item->next; | ||||
309 | item->next = NULL((void *)0); | ||||
310 | |||||
311 | if (c != NULL((void *)0)) | ||||
312 | c->references++; | ||||
313 | item->client = c; | ||||
314 | |||||
315 | item->queue = queue; | ||||
316 | TAILQ_INSERT_TAIL(&queue->list, item, entry)do { (item)->entry.tqe_next = ((void *)0); (item)->entry .tqe_prev = (&queue->list)->tqh_last; *(&queue-> list)->tqh_last = (item); (&queue->list)->tqh_last = &(item)->entry.tqe_next; } while (0); | ||||
317 | log_debug("%s %s: %s", __func__, cmdq_name(c), item->name); | ||||
318 | |||||
319 | item = next; | ||||
320 | } while (item != NULL((void *)0)); | ||||
321 | return (TAILQ_LAST(&queue->list, cmdq_item_list)(*(((struct cmdq_item_list *)((&queue->list)->tqh_last ))->tqh_last))); | ||||
322 | } | ||||
323 | |||||
324 | /* Insert an item. */ | ||||
325 | struct cmdq_item * | ||||
326 | cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) | ||||
327 | { | ||||
328 | struct client *c = after->client; | ||||
329 | struct cmdq_list *queue = after->queue; | ||||
330 | struct cmdq_item *next; | ||||
331 | |||||
332 | do { | ||||
333 | next = item->next; | ||||
334 | item->next = after->next; | ||||
335 | after->next = item; | ||||
336 | |||||
337 | if (c != NULL((void *)0)) | ||||
338 | c->references++; | ||||
339 | item->client = c; | ||||
340 | |||||
341 | item->queue = queue; | ||||
342 | TAILQ_INSERT_AFTER(&queue->list, after, item, entry)do { if (((item)->entry.tqe_next = (after)->entry.tqe_next ) != ((void *)0)) (item)->entry.tqe_next->entry.tqe_prev = &(item)->entry.tqe_next; else (&queue->list) ->tqh_last = &(item)->entry.tqe_next; (after)->entry .tqe_next = (item); (item)->entry.tqe_prev = &(after)-> entry.tqe_next; } while (0); | ||||
343 | log_debug("%s %s: %s after %s", __func__, cmdq_name(c), | ||||
344 | item->name, after->name); | ||||
345 | |||||
346 | after = item; | ||||
347 | item = next; | ||||
348 | } while (item != NULL((void *)0)); | ||||
349 | return (after); | ||||
350 | } | ||||
351 | |||||
352 | /* Insert a hook. */ | ||||
353 | void | ||||
354 | cmdq_insert_hook(struct session *s, struct cmdq_item *item, | ||||
355 | struct cmd_find_state *current, const char *fmt, ...) | ||||
356 | { | ||||
357 | struct cmdq_state *state = item->state; | ||||
358 | struct cmd *cmd = item->cmd; | ||||
359 | struct args *args = cmd_get_args(cmd); | ||||
360 | struct args_entry *ae; | ||||
361 | struct args_value *av; | ||||
362 | struct options *oo; | ||||
363 | va_list ap; | ||||
364 | char *name, tmp[32], flag, *arguments; | ||||
365 | u_int i; | ||||
366 | const char *value; | ||||
367 | struct cmdq_item *new_item; | ||||
368 | struct cmdq_state *new_state; | ||||
369 | struct options_entry *o; | ||||
370 | struct options_array_item *a; | ||||
371 | struct cmd_list *cmdlist; | ||||
372 | |||||
373 | if (item->state->flags & CMDQ_STATE_NOHOOKS0x4) | ||||
374 | return; | ||||
375 | if (s == NULL((void *)0)) | ||||
376 | oo = global_s_options; | ||||
377 | else | ||||
378 | oo = s->options; | ||||
379 | |||||
380 | va_start(ap, fmt)__builtin_va_start((ap), fmt); | ||||
381 | xvasprintf(&name, fmt, ap); | ||||
382 | va_end(ap)__builtin_va_end((ap)); | ||||
383 | |||||
384 | o = options_get(oo, name); | ||||
385 | if (o == NULL((void *)0)) { | ||||
386 | free(name); | ||||
387 | return; | ||||
388 | } | ||||
389 | log_debug("running hook %s (parent %p)", name, item); | ||||
390 | |||||
391 | /* | ||||
392 | * The hooks get a new state because they should not update the current | ||||
393 | * target or formats for any subsequent commands. | ||||
394 | */ | ||||
395 | new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS0x4); | ||||
396 | cmdq_add_format(new_state, "hook", "%s", name); | ||||
397 | |||||
398 | arguments = args_print(args); | ||||
399 | cmdq_add_format(new_state, "hook_arguments", "%s", arguments); | ||||
400 | free(arguments); | ||||
401 | |||||
402 | for (i = 0; i < args_count(args); i++) { | ||||
403 | xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i); | ||||
404 | cmdq_add_format(new_state, tmp, "%s", args_string(args, i)); | ||||
405 | } | ||||
406 | flag = args_first(args, &ae); | ||||
407 | while (flag != 0) { | ||||
408 | value = args_get(args, flag); | ||||
409 | if (value == NULL((void *)0)) { | ||||
410 | xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); | ||||
411 | cmdq_add_format(new_state, tmp, "1"); | ||||
412 | } else { | ||||
413 | xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); | ||||
414 | cmdq_add_format(new_state, tmp, "%s", value); | ||||
415 | } | ||||
416 | |||||
417 | i = 0; | ||||
418 | av = args_first_value(args, flag); | ||||
419 | while (av != NULL((void *)0)) { | ||||
420 | xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i); | ||||
421 | cmdq_add_format(new_state, tmp, "%s", av->string); | ||||
422 | i++; | ||||
423 | av = args_next_value(av); | ||||
424 | } | ||||
425 | |||||
426 | flag = args_next(&ae); | ||||
427 | } | ||||
428 | |||||
429 | a = options_array_first(o); | ||||
430 | while (a != NULL((void *)0)) { | ||||
431 | cmdlist = options_array_item_value(a)->cmdlist; | ||||
432 | if (cmdlist != NULL((void *)0)) { | ||||
433 | new_item = cmdq_get_command(cmdlist, new_state); | ||||
434 | if (item != NULL((void *)0)) | ||||
435 | item = cmdq_insert_after(item, new_item); | ||||
436 | else | ||||
437 | item = cmdq_append(NULL((void *)0), new_item); | ||||
438 | } | ||||
439 | a = options_array_next(a); | ||||
440 | } | ||||
441 | |||||
442 | cmdq_free_state(new_state); | ||||
443 | free(name); | ||||
444 | } | ||||
445 | |||||
446 | /* Continue processing command queue. */ | ||||
447 | void | ||||
448 | cmdq_continue(struct cmdq_item *item) | ||||
449 | { | ||||
450 | item->flags &= ~CMDQ_WAITING0x2; | ||||
451 | } | ||||
452 | |||||
453 | /* Remove an item. */ | ||||
454 | static void | ||||
455 | cmdq_remove(struct cmdq_item *item) | ||||
456 | { | ||||
457 | if (item->client != NULL((void *)0)) | ||||
458 | server_client_unref(item->client); | ||||
459 | if (item->cmdlist != NULL((void *)0)) | ||||
460 | cmd_list_free(item->cmdlist); | ||||
461 | cmdq_free_state(item->state); | ||||
462 | |||||
463 | TAILQ_REMOVE(&item->queue->list, item, entry)do { if (((item)->entry.tqe_next) != ((void *)0)) (item)-> entry.tqe_next->entry.tqe_prev = (item)->entry.tqe_prev ; else (&item->queue->list)->tqh_last = (item)-> entry.tqe_prev; *(item)->entry.tqe_prev = (item)->entry .tqe_next; ; ; } while (0); | ||||
464 | |||||
465 | free(item->name); | ||||
466 | free(item); | ||||
467 | } | ||||
468 | |||||
469 | /* Remove all subsequent items that match this item's group. */ | ||||
470 | static void | ||||
471 | cmdq_remove_group(struct cmdq_item *item) | ||||
472 | { | ||||
473 | struct cmdq_item *this, *next; | ||||
474 | |||||
475 | if (item->group == 0) | ||||
476 | return; | ||||
477 | this = TAILQ_NEXT(item, entry)((item)->entry.tqe_next); | ||||
478 | while (this != NULL((void *)0)) { | ||||
479 | next = TAILQ_NEXT(this, entry)((this)->entry.tqe_next); | ||||
480 | if (this->group == item->group) | ||||
481 | cmdq_remove(this); | ||||
482 | this = next; | ||||
483 | } | ||||
484 | } | ||||
485 | |||||
486 | /* Empty command callback. */ | ||||
487 | static enum cmd_retval | ||||
488 | cmdq_empty_command(__unused__attribute__((__unused__)) struct cmdq_item *item, __unused__attribute__((__unused__)) void *data) | ||||
489 | { | ||||
490 | return (CMD_RETURN_NORMAL); | ||||
491 | } | ||||
492 | |||||
493 | /* Get a command for the command queue. */ | ||||
494 | struct cmdq_item * | ||||
495 | cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state) | ||||
496 | { | ||||
497 | struct cmdq_item *item, *first = NULL((void *)0), *last = NULL((void *)0); | ||||
498 | struct cmd *cmd; | ||||
499 | const struct cmd_entry *entry; | ||||
500 | int created = 0; | ||||
501 | |||||
502 | if ((cmd = cmd_list_first(cmdlist)) == NULL((void *)0)) | ||||
503 | return (cmdq_get_callback(cmdq_empty_command, NULL)cmdq_get_callback1("cmdq_empty_command", cmdq_empty_command, ( (void *)0))); | ||||
504 | |||||
505 | if (state == NULL((void *)0)) { | ||||
506 | state = cmdq_new_state(NULL((void *)0), NULL((void *)0), 0); | ||||
507 | created = 1; | ||||
508 | } | ||||
509 | |||||
510 | while (cmd != NULL((void *)0)) { | ||||
511 | entry = cmd_get_entry(cmd); | ||||
512 | |||||
513 | item = xcalloc(1, sizeof *item); | ||||
514 | xasprintf(&item->name, "[%s/%p]", entry->name, item); | ||||
515 | item->type = CMDQ_COMMAND; | ||||
516 | |||||
517 | item->group = cmd_get_group(cmd); | ||||
518 | item->state = cmdq_link_state(state); | ||||
519 | |||||
520 | item->cmdlist = cmdlist; | ||||
521 | item->cmd = cmd; | ||||
522 | |||||
523 | cmdlist->references++; | ||||
524 | log_debug("%s: %s group %u", __func__, item->name, item->group); | ||||
525 | |||||
526 | if (first == NULL((void *)0)) | ||||
527 | first = item; | ||||
528 | if (last != NULL((void *)0)) | ||||
529 | last->next = item; | ||||
530 | last = item; | ||||
531 | |||||
532 | cmd = cmd_list_next(cmd); | ||||
533 | } | ||||
534 | |||||
535 | if (created) | ||||
536 | cmdq_free_state(state); | ||||
537 | return (first); | ||||
538 | } | ||||
539 | |||||
540 | /* Fill in flag for a command. */ | ||||
541 | static enum cmd_retval | ||||
542 | cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, | ||||
543 | const struct cmd_entry_flag *flag) | ||||
544 | { | ||||
545 | const char *value; | ||||
546 | |||||
547 | if (flag->flag == 0) { | ||||
548 | cmd_find_from_client(fs, item->target_client, 0); | ||||
549 | return (CMD_RETURN_NORMAL); | ||||
550 | } | ||||
551 | |||||
552 | value = args_get(cmd_get_args(item->cmd), flag->flag); | ||||
553 | if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) { | ||||
554 | cmd_find_clear_state(fs, 0); | ||||
555 | return (CMD_RETURN_ERROR); | ||||
556 | } | ||||
557 | return (CMD_RETURN_NORMAL); | ||||
558 | } | ||||
559 | |||||
560 | /* Add message with command. */ | ||||
561 | static void | ||||
562 | cmdq_add_message(struct cmdq_item *item) | ||||
563 | { | ||||
564 | struct client *c = item->client; | ||||
565 | struct cmdq_state *state = item->state; | ||||
566 | const char *key; | ||||
567 | char *tmp; | ||||
568 | uid_t uid; | ||||
569 | struct passwd *pw; | ||||
570 | char *user = NULL((void *)0); | ||||
571 | |||||
572 | tmp = cmd_print(item->cmd); | ||||
573 | if (c != NULL((void *)0)) { | ||||
574 | uid = proc_get_peer_uid(c->peer); | ||||
575 | if (uid != (uid_t)-1 && uid != getuid()) { | ||||
576 | if ((pw = getpwuid(uid)) != NULL((void *)0)) | ||||
577 | xasprintf(&user, "[%s]", pw->pw_name); | ||||
578 | else | ||||
579 | user = xstrdup("[unknown]"); | ||||
580 | } else | ||||
581 | user = xstrdup(""); | ||||
582 | if (c->session != NULL((void *)0) && state->event.key != KEYC_NONE0x000ff000000000ULL) { | ||||
583 | key = key_string_lookup_key(state->event.key, 0); | ||||
584 | server_add_message("%s%s key %s: %s", c->name, user, | ||||
585 | key, tmp); | ||||
586 | } else { | ||||
587 | server_add_message("%s%s command: %s", c->name, user, | ||||
588 | tmp); | ||||
589 | } | ||||
590 | free(user); | ||||
591 | } else | ||||
592 | server_add_message("command: %s", tmp); | ||||
593 | free(tmp); | ||||
594 | } | ||||
595 | |||||
596 | /* Fire command on command queue. */ | ||||
597 | static enum cmd_retval | ||||
598 | cmdq_fire_command(struct cmdq_item *item) | ||||
599 | { | ||||
600 | const char *name = cmdq_name(item->client); | ||||
601 | struct cmdq_state *state = item->state; | ||||
602 | struct cmd *cmd = item->cmd; | ||||
603 | struct args *args = cmd_get_args(cmd); | ||||
604 | const struct cmd_entry *entry = cmd_get_entry(cmd); | ||||
605 | struct client *tc, *saved = item->client; | ||||
606 | enum cmd_retval retval; | ||||
607 | struct cmd_find_state *fsp, fs; | ||||
608 | int flags, quiet = 0; | ||||
609 | char *tmp; | ||||
610 | |||||
611 | if (cfg_finished) | ||||
612 | cmdq_add_message(item); | ||||
613 | if (log_get_level() > 1) { | ||||
614 | tmp = cmd_print(cmd); | ||||
615 | log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp); | ||||
616 | free(tmp); | ||||
617 | } | ||||
618 | |||||
619 | flags = !!(state->flags & CMDQ_STATE_CONTROL0x2); | ||||
620 | cmdq_guard(item, "begin", flags); | ||||
621 | |||||
622 | if (item->client == NULL((void *)0)) | ||||
623 | item->client = cmd_find_client(item, NULL((void *)0), 1); | ||||
624 | |||||
625 | if (entry->flags & CMD_CLIENT_CANFAIL0x20) | ||||
626 | quiet = 1; | ||||
627 | if (entry->flags & CMD_CLIENT_CFLAG0x8) { | ||||
628 | tc = cmd_find_client(item, args_get(args, 'c'), quiet); | ||||
629 | if (tc == NULL((void *)0) && !quiet) { | ||||
630 | retval = CMD_RETURN_ERROR; | ||||
631 | goto out; | ||||
632 | } | ||||
633 | } else if (entry->flags & CMD_CLIENT_TFLAG0x10) { | ||||
634 | tc = cmd_find_client(item, args_get(args, 't'), quiet); | ||||
635 | if (tc == NULL((void *)0) && !quiet) { | ||||
636 | retval = CMD_RETURN_ERROR; | ||||
637 | goto out; | ||||
638 | } | ||||
639 | } else | ||||
640 | tc = cmd_find_client(item, NULL((void *)0), 1); | ||||
641 | item->target_client = tc; | ||||
642 | |||||
643 | retval = cmdq_find_flag(item, &item->source, &entry->source); | ||||
644 | if (retval == CMD_RETURN_ERROR) | ||||
645 | goto out; | ||||
646 | retval = cmdq_find_flag(item, &item->target, &entry->target); | ||||
647 | if (retval == CMD_RETURN_ERROR) | ||||
648 | goto out; | ||||
649 | |||||
650 | retval = entry->exec(cmd, item); | ||||
651 | if (retval == CMD_RETURN_ERROR) | ||||
652 | goto out; | ||||
653 | |||||
654 | if (entry->flags & CMD_AFTERHOOK0x4) { | ||||
655 | if (cmd_find_valid_state(&item->target)) | ||||
656 | fsp = &item->target; | ||||
657 | else if (cmd_find_valid_state(&item->state->current)) | ||||
658 | fsp = &item->state->current; | ||||
659 | else if (cmd_find_from_client(&fs, item->client, 0) == 0) | ||||
660 | fsp = &fs; | ||||
661 | else | ||||
662 | goto out; | ||||
663 | cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name); | ||||
664 | } | ||||
665 | |||||
666 | out: | ||||
667 | item->client = saved; | ||||
668 | if (retval == CMD_RETURN_ERROR) | ||||
669 | cmdq_guard(item, "error", flags); | ||||
670 | else | ||||
671 | cmdq_guard(item, "end", flags); | ||||
672 | return (retval); | ||||
673 | } | ||||
674 | |||||
675 | /* Get a callback for the command queue. */ | ||||
676 | struct cmdq_item * | ||||
677 | cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) | ||||
678 | { | ||||
679 | struct cmdq_item *item; | ||||
680 | |||||
681 | item = xcalloc(1, sizeof *item); | ||||
682 | xasprintf(&item->name, "[%s/%p]", name, item); | ||||
683 | item->type = CMDQ_CALLBACK; | ||||
684 | |||||
685 | item->group = 0; | ||||
686 | item->state = cmdq_new_state(NULL((void *)0), NULL((void *)0), 0); | ||||
687 | |||||
688 | item->cb = cb; | ||||
689 | item->data = data; | ||||
690 | |||||
691 | return (item); | ||||
692 | } | ||||
693 | |||||
694 | /* Generic error callback. */ | ||||
695 | static enum cmd_retval | ||||
696 | cmdq_error_callback(struct cmdq_item *item, void *data) | ||||
697 | { | ||||
698 | char *error = data; | ||||
699 | |||||
700 | cmdq_error(item, "%s", error); | ||||
701 | free(error); | ||||
702 | |||||
703 | return (CMD_RETURN_NORMAL); | ||||
704 | } | ||||
705 | |||||
706 | /* Get an error callback for the command queue. */ | ||||
707 | struct cmdq_item * | ||||
708 | cmdq_get_error(const char *error) | ||||
709 | { | ||||
710 | return (cmdq_get_callback(cmdq_error_callback, xstrdup(error))cmdq_get_callback1("cmdq_error_callback", cmdq_error_callback , xstrdup(error))); | ||||
711 | } | ||||
712 | |||||
713 | /* Fire callback on callback queue. */ | ||||
714 | static enum cmd_retval | ||||
715 | cmdq_fire_callback(struct cmdq_item *item) | ||||
716 | { | ||||
717 | return (item->cb(item, item->data)); | ||||
718 | } | ||||
719 | |||||
720 | /* Process next item on command queue. */ | ||||
721 | u_int | ||||
722 | cmdq_next(struct client *c) | ||||
723 | { | ||||
724 | struct cmdq_list *queue = cmdq_get(c); | ||||
725 | const char *name = cmdq_name(c); | ||||
726 | struct cmdq_item *item; | ||||
727 | enum cmd_retval retval; | ||||
728 | u_int items = 0; | ||||
729 | static u_int number; | ||||
730 | |||||
731 | if (TAILQ_EMPTY(&queue->list)(((&queue->list)->tqh_first) == ((void *)0))) { | ||||
| |||||
732 | log_debug("%s %s: empty", __func__, name); | ||||
733 | return (0); | ||||
734 | } | ||||
735 | if (TAILQ_FIRST(&queue->list)((&queue->list)->tqh_first)->flags & CMDQ_WAITING0x2) { | ||||
736 | log_debug("%s %s: waiting", __func__, name); | ||||
737 | return (0); | ||||
738 | } | ||||
739 | |||||
740 | log_debug("%s %s: enter", __func__, name); | ||||
741 | for (;;) { | ||||
742 | item = queue->item = TAILQ_FIRST(&queue->list)((&queue->list)->tqh_first); | ||||
743 | if (item
| ||||
744 | break; | ||||
745 | log_debug("%s %s: %s (%d), flags %x", __func__, name, | ||||
746 | item->name, item->type, item->flags); | ||||
| |||||
747 | |||||
748 | /* | ||||
749 | * Any item with the waiting flag set waits until an external | ||||
750 | * event clears the flag (for example, a job - look at | ||||
751 | * run-shell). | ||||
752 | */ | ||||
753 | if (item->flags & CMDQ_WAITING0x2) | ||||
754 | goto waiting; | ||||
755 | |||||
756 | /* | ||||
757 | * Items are only fired once, once the fired flag is set, a | ||||
758 | * waiting flag can only be cleared by an external event. | ||||
759 | */ | ||||
760 | if (~item->flags & CMDQ_FIRED0x1) { | ||||
761 | item->time = time(NULL((void *)0)); | ||||
762 | item->number = ++number; | ||||
763 | |||||
764 | switch (item->type) { | ||||
765 | case CMDQ_COMMAND: | ||||
766 | retval = cmdq_fire_command(item); | ||||
767 | |||||
768 | /* | ||||
769 | * If a command returns an error, remove any | ||||
770 | * subsequent commands in the same group. | ||||
771 | */ | ||||
772 | if (retval == CMD_RETURN_ERROR) | ||||
773 | cmdq_remove_group(item); | ||||
774 | break; | ||||
775 | case CMDQ_CALLBACK: | ||||
776 | retval = cmdq_fire_callback(item); | ||||
777 | break; | ||||
778 | default: | ||||
779 | retval = CMD_RETURN_ERROR; | ||||
780 | break; | ||||
781 | } | ||||
782 | item->flags |= CMDQ_FIRED0x1; | ||||
783 | |||||
784 | if (retval == CMD_RETURN_WAIT) { | ||||
785 | item->flags |= CMDQ_WAITING0x2; | ||||
786 | goto waiting; | ||||
787 | } | ||||
788 | items++; | ||||
789 | } | ||||
790 | cmdq_remove(item); | ||||
791 | } | ||||
792 | queue->item = NULL((void *)0); | ||||
793 | |||||
794 | log_debug("%s %s: exit (empty)", __func__, name); | ||||
795 | return (items); | ||||
796 | |||||
797 | waiting: | ||||
798 | log_debug("%s %s: exit (wait)", __func__, name); | ||||
799 | return (items); | ||||
800 | } | ||||
801 | |||||
802 | /* Get running item if any. */ | ||||
803 | struct cmdq_item * | ||||
804 | cmdq_running(struct client *c) | ||||
805 | { | ||||
806 | struct cmdq_list *queue = cmdq_get(c); | ||||
807 | |||||
808 | if (queue->item == NULL((void *)0)) | ||||
809 | return (NULL((void *)0)); | ||||
810 | if (queue->item->flags & CMDQ_WAITING0x2) | ||||
811 | return (NULL((void *)0)); | ||||
812 | return (queue->item); | ||||
813 | } | ||||
814 | |||||
815 | /* Print a guard line. */ | ||||
816 | void | ||||
817 | cmdq_guard(struct cmdq_item *item, const char *guard, int flags) | ||||
818 | { | ||||
819 | struct client *c = item->client; | ||||
820 | long t = item->time; | ||||
821 | u_int number = item->number; | ||||
822 | |||||
823 | if (c != NULL((void *)0) && (c->flags & CLIENT_CONTROL0x2000)) | ||||
824 | control_write(c, "%%%s %ld %u %d", guard, t, number, flags); | ||||
825 | } | ||||
826 | |||||
827 | /* Show message from command. */ | ||||
828 | void | ||||
829 | cmdq_print_data(struct cmdq_item *item, int parse, struct evbuffer *evb) | ||||
830 | { | ||||
831 | server_client_print(item->client, parse, evb); | ||||
832 | } | ||||
833 | |||||
834 | /* Show message from command. */ | ||||
835 | void | ||||
836 | cmdq_print(struct cmdq_item *item, const char *fmt, ...) | ||||
837 | { | ||||
838 | va_list ap; | ||||
839 | struct evbuffer *evb; | ||||
840 | |||||
841 | evb = evbuffer_new(); | ||||
842 | if (evb == NULL((void *)0)) | ||||
843 | fatalx("out of memory"); | ||||
844 | |||||
845 | va_start(ap, fmt)__builtin_va_start((ap), fmt); | ||||
846 | evbuffer_add_vprintf(evb, fmt, ap); | ||||
847 | va_end(ap)__builtin_va_end((ap)); | ||||
848 | |||||
849 | cmdq_print_data(item, 0, evb); | ||||
850 | evbuffer_free(evb); | ||||
851 | } | ||||
852 | |||||
853 | /* Show error from command. */ | ||||
854 | void | ||||
855 | cmdq_error(struct cmdq_item *item, const char *fmt, ...) | ||||
856 | { | ||||
857 | struct client *c = item->client; | ||||
858 | struct cmd *cmd = item->cmd; | ||||
859 | va_list ap; | ||||
860 | char *msg, *tmp; | ||||
861 | const char *file; | ||||
862 | u_int line; | ||||
863 | |||||
864 | va_start(ap, fmt)__builtin_va_start((ap), fmt); | ||||
865 | xvasprintf(&msg, fmt, ap); | ||||
866 | va_end(ap)__builtin_va_end((ap)); | ||||
867 | |||||
868 | log_debug("%s: %s", __func__, msg); | ||||
869 | |||||
870 | if (c == NULL((void *)0)) { | ||||
871 | cmd_get_source(cmd, &file, &line); | ||||
872 | cfg_add_cause("%s:%u: %s", file, line, msg); | ||||
873 | } else if (c->session == NULL((void *)0) || (c->flags & CLIENT_CONTROL0x2000)) { | ||||
874 | server_add_message("%s message: %s", c->name, msg); | ||||
875 | if (~c->flags & CLIENT_UTF80x10000) { | ||||
876 | tmp = msg; | ||||
877 | msg = utf8_sanitize(tmp); | ||||
878 | free(tmp); | ||||
879 | } | ||||
880 | if (c->flags & CLIENT_CONTROL0x2000) | ||||
881 | control_write(c, "%s", msg); | ||||
882 | else | ||||
883 | file_error(c, "%s\n", msg); | ||||
884 | c->retval = 1; | ||||
885 | } else { | ||||
886 | *msg = toupper((u_char) *msg); | ||||
887 | status_message_set(c, -1, 1, 0, "%s", msg); | ||||
888 | } | ||||
889 | |||||
890 | free(msg); | ||||
891 | } |