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