1/* $OpenBSD$ */
2
3/*
4 * Copyright (c) 2013 Nicholas Marriott <[email protected]>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20
21#include <ctype.h>
22#include <stdlib.h>
23#include <string.h>
24#include <time.h>
25
26#include "tmux.h"
27
28/* Global command queue. */
29static struct cmdq_list global_queue = TAILQ_HEAD_INITIALIZER(global_queue);
30
31/* Get command queue name. */
32static const char *
33cmdq_name(struct client *c)
34{
35 static char s[256];
36
37 if (c == NULL)
38 return ("<global>");
39 if (c->name != NULL)
40 xsnprintf(s, sizeof s, "<%s>", c->name);
41 else
42 xsnprintf(s, sizeof s, "<%p>", c);
43 return (s);
44}
45
46/* Get command queue from client. */
47static struct cmdq_list *
48cmdq_get(struct client *c)
49{
50 if (c == NULL)
51 return (&global_queue);
52 return (&c->queue);
53}
54
55/* Append an item. */
56struct cmdq_item *
57cmdq_append(struct client *c, struct cmdq_item *item)
58{
59 struct cmdq_list *queue = cmdq_get(c);
60 struct cmdq_item *next;
61
62 do {
63 next = item->next;
64 item->next = NULL;
65
66 if (c != NULL)
67 c->references++;
68 item->client = c;
69
70 item->queue = queue;
71 TAILQ_INSERT_TAIL(queue, item, entry);
72 log_debug("%s %s: %s", __func__, cmdq_name(c), item->name);
73
74 item = next;
75 } while (item != NULL);
76 return (TAILQ_LAST(queue, cmdq_list));
77}
78
79/* Insert an item. */
80struct cmdq_item *
81cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item)
82{
83 struct client *c = after->client;
84 struct cmdq_list *queue = after->queue;
85 struct cmdq_item *next;
86
87 do {
88 next = item->next;
89 item->next = after->next;
90 after->next = item;
91
92 if (c != NULL)
93 c->references++;
94 item->client = c;
95
96 item->queue = queue;
97 TAILQ_INSERT_AFTER(queue, after, item, entry);
98 log_debug("%s %s: %s after %s", __func__, cmdq_name(c),
99 item->name, after->name);
100
101 after = item;
102 item = next;
103 } while (item != NULL);
104 return (after);
105}
106
107/* Insert a hook. */
108void
109cmdq_insert_hook(struct session *s, struct cmdq_item *item,
110 struct cmd_find_state *fs, const char *fmt, ...)
111{
112 struct options *oo;
113 va_list ap;
114 char *name;
115 struct cmdq_item *new_item;
116 struct options_entry *o;
117 struct options_array_item *a;
118 struct cmd_list *cmdlist;
119
120 if (item->flags & CMDQ_NOHOOKS)
121 return;
122 if (s == NULL)
123 oo = global_s_options;
124 else
125 oo = s->options;
126
127 va_start(ap, fmt);
128 xvasprintf(&name, fmt, ap);
129 va_end(ap);
130
131 o = options_get(oo, name);
132 if (o == NULL) {
133 free(name);
134 return;
135 }
136 log_debug("running hook %s (parent %p)", name, item);
137
138 a = options_array_first(o);
139 while (a != NULL) {
140 cmdlist = options_array_item_value(a)->cmdlist;
141 if (cmdlist == NULL) {
142 a = options_array_next(a);
143 continue;
144 }
145
146 new_item = cmdq_get_command(cmdlist, fs, NULL, CMDQ_NOHOOKS);
147 cmdq_format(new_item, "hook", "%s", name);
148 if (item != NULL)
149 item = cmdq_insert_after(item, new_item);
150 else
151 item = cmdq_append(NULL, new_item);
152
153 a = options_array_next(a);
154 }
155
156 free(name);
157}
158
159/* Continue processing command queue. */
160void
161cmdq_continue(struct cmdq_item *item)
162{
163 item->flags &= ~CMDQ_WAITING;
164}
165
166/* Remove an item. */
167static void
168cmdq_remove(struct cmdq_item *item)
169{
170 if (item->shared != NULL && --item->shared->references == 0) {
171 if (item->shared->formats != NULL)
172 format_free(item->shared->formats);
173 free(item->shared);
174 }
175
176 if (item->client != NULL)
177 server_client_unref(item->client);
178
179 if (item->cmdlist != NULL)
180 cmd_list_free(item->cmdlist);
181
182 TAILQ_REMOVE(item->queue, item, entry);
183
184 free(item->name);
185 free(item);
186}
187
188/* Remove all subsequent items that match this item's group. */
189static void
190cmdq_remove_group(struct cmdq_item *item)
191{
192 struct cmdq_item *this, *next;
193
194 if (item->group == 0)
195 return;
196 this = TAILQ_NEXT(item, entry);
197 while (this != NULL) {
198 next = TAILQ_NEXT(this, entry);
199 if (this->group == item->group)
200 cmdq_remove(this);
201 this = next;
202 }
203}
204
205/* Get a command for the command queue. */
206struct cmdq_item *
207cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current,
208 struct mouse_event *m, int flags)
209{
210 struct cmdq_item *item, *first = NULL, *last = NULL;
211 struct cmd *cmd;
212 struct cmdq_shared *shared = NULL;
213 u_int group = 0;
214
215 TAILQ_FOREACH(cmd, &cmdlist->list, qentry) {
216 if (cmd->group != group) {
217 shared = xcalloc(1, sizeof *shared);
218 if (current != NULL)
219 cmd_find_copy_state(&shared->current, current);
220 else
221 cmd_find_clear_state(&shared->current, 0);
222 if (m != NULL)
223 memcpy(&shared->mouse, m, sizeof shared->mouse);
224 group = cmd->group;
225 }
226
227 item = xcalloc(1, sizeof *item);
228 xasprintf(&item->name, "[%s/%p]", cmd->entry->name, item);
229 item->type = CMDQ_COMMAND;
230
231 item->group = cmd->group;
232 item->flags = flags;
233
234 item->shared = shared;
235 item->cmdlist = cmdlist;
236 item->cmd = cmd;
237
238 log_debug("%s: %s group %u", __func__, item->name, item->group);
239
240 shared->references++;
241 cmdlist->references++;
242
243 if (first == NULL)
244 first = item;
245 if (last != NULL)
246 last->next = item;
247 last = item;
248 }
249 return (first);
250}
251
252/* Fill in flag for a command. */
253static enum cmd_retval
254cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs,
255 const struct cmd_entry_flag *flag)
256{
257 const char *value;
258
259 if (flag->flag == 0) {
260 cmd_find_clear_state(fs, 0);
261 return (CMD_RETURN_NORMAL);
262 }
263
264 value = args_get(item->cmd->args, flag->flag);
265 if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) {
266 cmd_find_clear_state(fs, 0);
267 return (CMD_RETURN_ERROR);
268 }
269 return (CMD_RETURN_NORMAL);
270}
271
272/* Fire command on command queue. */
273static enum cmd_retval
274cmdq_fire_command(struct cmdq_item *item)
275{
276 struct client *c = item->client;
277 const char *name = cmdq_name(c);
278 struct cmdq_shared *shared = item->shared;
279 struct cmd *cmd = item->cmd;
280 const struct cmd_entry *entry = cmd->entry;
281 enum cmd_retval retval;
282 struct cmd_find_state *fsp, fs;
283 int flags;
284 char *tmp;
285
286 if (log_get_level() > 1) {
287 tmp = cmd_print(cmd);
288 log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp);
289 free(tmp);
290 }
291
292 flags = !!(shared->flags & CMDQ_SHARED_CONTROL);
293 cmdq_guard(item, "begin", flags);
294
295 if (item->client == NULL)
296 item->client = cmd_find_client(item, NULL, 1);
297 retval = cmdq_find_flag(item, &item->source, &entry->source);
298 if (retval == CMD_RETURN_ERROR)
299 goto out;
300 retval = cmdq_find_flag(item, &item->target, &entry->target);
301 if (retval == CMD_RETURN_ERROR)
302 goto out;
303
304 retval = entry->exec(cmd, item);
305 if (retval == CMD_RETURN_ERROR)
306 goto out;
307
308 if (entry->flags & CMD_AFTERHOOK) {
309 if (cmd_find_valid_state(&item->target))
310 fsp = &item->target;
311 else if (cmd_find_valid_state(&item->shared->current))
312 fsp = &item->shared->current;
313 else if (cmd_find_from_client(&fs, item->client, 0) == 0)
314 fsp = &fs;
315 else
316 goto out;
317 cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name);
318 }
319
320out:
321 item->client = c;
322 if (retval == CMD_RETURN_ERROR)
323 cmdq_guard(item, "error", flags);
324 else
325 cmdq_guard(item, "end", flags);
326 return (retval);
327}
328
329/* Get a callback for the command queue. */
330struct cmdq_item *
331cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
332{
333 struct cmdq_item *item;
334
335 item = xcalloc(1, sizeof *item);
336 xasprintf(&item->name, "[%s/%p]", name, item);
337 item->type = CMDQ_CALLBACK;
338
339 item->group = 0;
340 item->flags = 0;
341
342 item->cb = cb;
343 item->data = data;
344
345 return (item);
346}
347
348/* Generic error callback. */
349static enum cmd_retval
350cmdq_error_callback(struct cmdq_item *item, void *data)
351{
352 char *error = data;
353
354 cmdq_error(item, "%s", error);
355 free(error);
356
357 return (CMD_RETURN_NORMAL);
358}
359
360/* Get an error callback for the command queue. */
361struct cmdq_item *
362cmdq_get_error(const char *error)
363{
364 return (cmdq_get_callback(cmdq_error_callback, xstrdup(error)));
365}
366
367/* Fire callback on callback queue. */
368static enum cmd_retval
369cmdq_fire_callback(struct cmdq_item *item)
370{
371 return (item->cb(item, item->data));
372}
373
374/* Add a format to command queue. */
375void
376cmdq_format(struct cmdq_item *item, const char *key, const char *fmt, ...)
377{
378 struct cmdq_shared *shared = item->shared;
379 va_list ap;
380 char *value;
381
382 va_start(ap, fmt);
383 xvasprintf(&value, fmt, ap);
384 va_end(ap);
385
386 if (shared->formats == NULL)
387 shared->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
388 format_add(shared->formats, key, "%s", value);
389
390 free(value);
391}
392
393/* Process next item on command queue. */
394u_int
395cmdq_next(struct client *c)
396{
397 struct cmdq_list *queue = cmdq_get(c);
398 const char *name = cmdq_name(c);
399 struct cmdq_item *item;
400 enum cmd_retval retval;
401 u_int items = 0;
402 static u_int number;
403
404 if (TAILQ_EMPTY(queue)) {
405 log_debug("%s %s: empty", __func__, name);
406 return (0);
407 }
408 if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) {
409 log_debug("%s %s: waiting", __func__, name);
410 return (0);
411 }
412
413 log_debug("%s %s: enter", __func__, name);
414 for (;;) {
415 item = TAILQ_FIRST(queue);
416 if (item == NULL)
417 break;
418 log_debug("%s %s: %s (%d), flags %x", __func__, name,
419 item->name, item->type, item->flags);
420
421 /*
422 * Any item with the waiting flag set waits until an external
423 * event clears the flag (for example, a job - look at
424 * run-shell).
425 */
426 if (item->flags & CMDQ_WAITING)
427 goto waiting;
428
429 /*
430 * Items are only fired once, once the fired flag is set, a
431 * waiting flag can only be cleared by an external event.
432 */
433 if (~item->flags & CMDQ_FIRED) {
434 item->time = time(NULL);
435 item->number = ++number;
436
437 switch (item->type) {
438 case CMDQ_COMMAND:
439 retval = cmdq_fire_command(item);
440
441 /*
442 * If a command returns an error, remove any
443 * subsequent commands in the same group.
444 */
445 if (retval == CMD_RETURN_ERROR)
446 cmdq_remove_group(item);
447 break;
448 case CMDQ_CALLBACK:
449 retval = cmdq_fire_callback(item);
450 break;
451 default:
452 retval = CMD_RETURN_ERROR;
453 break;
454 }
455 item->flags |= CMDQ_FIRED;
456
457 if (retval == CMD_RETURN_WAIT) {
458 item->flags |= CMDQ_WAITING;
459 goto waiting;
460 }
461 items++;
462 }
463 cmdq_remove(item);
464 }
465
466 log_debug("%s %s: exit (empty)", __func__, name);
467 return (items);
468
469waiting:
470 log_debug("%s %s: exit (wait)", __func__, name);
471 return (items);
472}
473
474/* Print a guard line. */
475void
476cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
477{
478 struct client *c = item->client;
479 long t = item->time;
480 u_int number = item->number;
481
482 if (c != NULL && (c->flags & CLIENT_CONTROL))
483 file_print(c, "%%%s %ld %u %d\n", guard, t, number, flags);
484}
485
486/* Show message from command. */
487void
488cmdq_print(struct cmdq_item *item, const char *fmt, ...)
489{
490 struct client *c = item->client;
491 struct window_pane *wp;
492 struct window_mode_entry *wme;
493 va_list ap;
494 char *tmp, *msg;
495
496 va_start(ap, fmt);
497 xvasprintf(&msg, fmt, ap);
498 va_end(ap);
499
500 log_debug("%s: %s", __func__, msg);
501
502 if (c == NULL)
503 /* nothing */;
504 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
505 if (~c->flags & CLIENT_UTF8) {
506 tmp = msg;
507 msg = utf8_sanitize(tmp);
508 free(tmp);
509 }
510 file_print(c, "%s\n", msg);
511 } else {
512 wp = c->session->curw->window->active;
513 wme = TAILQ_FIRST(&wp->modes);
514 if (wme == NULL || wme->mode != &window_view_mode)
515 window_pane_set_mode(wp, &window_view_mode, NULL, NULL);
516 window_copy_add(wp, "%s", msg);
517 }
518
519 free(msg);
520}
521
522/* Show error from command. */
523void
524cmdq_error(struct cmdq_item *item, const char *fmt, ...)
525{
526 struct client *c = item->client;
527 struct cmd *cmd = item->cmd;
528 va_list ap;
529 char *msg;
530 char *tmp;
531
532 va_start(ap, fmt);
533 xvasprintf(&msg, fmt, ap);
534 va_end(ap);
535
536 log_debug("%s: %s", __func__, msg);
537
538 if (c == NULL)
539 cfg_add_cause("%s:%u: %s", cmd->file, cmd->line, msg);
540 else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
541 if (~c->flags & CLIENT_UTF8) {
542 tmp = msg;
543 msg = utf8_sanitize(tmp);
544 free(tmp);
545 }
546 if (c->flags & CLIENT_CONTROL)
547 file_print(c, "%s\n", msg);
548 else
549 file_error(c, "%s\n", msg);
550 c->retval = 1;
551 } else {
552 *msg = toupper((u_char) *msg);
553 status_message_set(c, "%s", msg);
554 }
555
556 free(msg);
557}
558