1/* $OpenBSD$ */
2
3/*
4 * Copyright (c) 2007 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#include <sys/time.h>
21
22#include <fnmatch.h>
23#include <pwd.h>
24#include <stdlib.h>
25#include <string.h>
26#include <unistd.h>
27
28#include "tmux.h"
29
30extern const struct cmd_entry cmd_attach_session_entry;
31extern const struct cmd_entry cmd_bind_key_entry;
32extern const struct cmd_entry cmd_break_pane_entry;
33extern const struct cmd_entry cmd_capture_pane_entry;
34extern const struct cmd_entry cmd_choose_buffer_entry;
35extern const struct cmd_entry cmd_choose_client_entry;
36extern const struct cmd_entry cmd_choose_tree_entry;
37extern const struct cmd_entry cmd_clear_history_entry;
38extern const struct cmd_entry cmd_clock_mode_entry;
39extern const struct cmd_entry cmd_command_prompt_entry;
40extern const struct cmd_entry cmd_confirm_before_entry;
41extern const struct cmd_entry cmd_copy_mode_entry;
42extern const struct cmd_entry cmd_delete_buffer_entry;
43extern const struct cmd_entry cmd_detach_client_entry;
44extern const struct cmd_entry cmd_display_menu_entry;
45extern const struct cmd_entry cmd_display_message_entry;
46extern const struct cmd_entry cmd_display_panes_entry;
47extern const struct cmd_entry cmd_down_pane_entry;
48extern const struct cmd_entry cmd_find_window_entry;
49extern const struct cmd_entry cmd_has_session_entry;
50extern const struct cmd_entry cmd_if_shell_entry;
51extern const struct cmd_entry cmd_join_pane_entry;
52extern const struct cmd_entry cmd_kill_pane_entry;
53extern const struct cmd_entry cmd_kill_server_entry;
54extern const struct cmd_entry cmd_kill_session_entry;
55extern const struct cmd_entry cmd_kill_window_entry;
56extern const struct cmd_entry cmd_last_pane_entry;
57extern const struct cmd_entry cmd_last_window_entry;
58extern const struct cmd_entry cmd_link_window_entry;
59extern const struct cmd_entry cmd_list_buffers_entry;
60extern const struct cmd_entry cmd_list_clients_entry;
61extern const struct cmd_entry cmd_list_commands_entry;
62extern const struct cmd_entry cmd_list_keys_entry;
63extern const struct cmd_entry cmd_list_panes_entry;
64extern const struct cmd_entry cmd_list_sessions_entry;
65extern const struct cmd_entry cmd_list_windows_entry;
66extern const struct cmd_entry cmd_load_buffer_entry;
67extern const struct cmd_entry cmd_lock_client_entry;
68extern const struct cmd_entry cmd_lock_server_entry;
69extern const struct cmd_entry cmd_lock_session_entry;
70extern const struct cmd_entry cmd_move_pane_entry;
71extern const struct cmd_entry cmd_move_window_entry;
72extern const struct cmd_entry cmd_new_session_entry;
73extern const struct cmd_entry cmd_new_window_entry;
74extern const struct cmd_entry cmd_next_layout_entry;
75extern const struct cmd_entry cmd_next_window_entry;
76extern const struct cmd_entry cmd_paste_buffer_entry;
77extern const struct cmd_entry cmd_pipe_pane_entry;
78extern const struct cmd_entry cmd_previous_layout_entry;
79extern const struct cmd_entry cmd_previous_window_entry;
80extern const struct cmd_entry cmd_refresh_client_entry;
81extern const struct cmd_entry cmd_rename_session_entry;
82extern const struct cmd_entry cmd_rename_window_entry;
83extern const struct cmd_entry cmd_resize_pane_entry;
84extern const struct cmd_entry cmd_resize_window_entry;
85extern const struct cmd_entry cmd_respawn_pane_entry;
86extern const struct cmd_entry cmd_respawn_window_entry;
87extern const struct cmd_entry cmd_rotate_window_entry;
88extern const struct cmd_entry cmd_run_shell_entry;
89extern const struct cmd_entry cmd_save_buffer_entry;
90extern const struct cmd_entry cmd_select_layout_entry;
91extern const struct cmd_entry cmd_select_pane_entry;
92extern const struct cmd_entry cmd_select_window_entry;
93extern const struct cmd_entry cmd_send_keys_entry;
94extern const struct cmd_entry cmd_send_prefix_entry;
95extern const struct cmd_entry cmd_set_buffer_entry;
96extern const struct cmd_entry cmd_set_environment_entry;
97extern const struct cmd_entry cmd_set_hook_entry;
98extern const struct cmd_entry cmd_set_option_entry;
99extern const struct cmd_entry cmd_set_window_option_entry;
100extern const struct cmd_entry cmd_show_buffer_entry;
101extern const struct cmd_entry cmd_show_environment_entry;
102extern const struct cmd_entry cmd_show_hooks_entry;
103extern const struct cmd_entry cmd_show_messages_entry;
104extern const struct cmd_entry cmd_show_options_entry;
105extern const struct cmd_entry cmd_show_window_options_entry;
106extern const struct cmd_entry cmd_source_file_entry;
107extern const struct cmd_entry cmd_split_window_entry;
108extern const struct cmd_entry cmd_start_server_entry;
109extern const struct cmd_entry cmd_suspend_client_entry;
110extern const struct cmd_entry cmd_swap_pane_entry;
111extern const struct cmd_entry cmd_swap_window_entry;
112extern const struct cmd_entry cmd_switch_client_entry;
113extern const struct cmd_entry cmd_unbind_key_entry;
114extern const struct cmd_entry cmd_unlink_window_entry;
115extern const struct cmd_entry cmd_up_pane_entry;
116extern const struct cmd_entry cmd_wait_for_entry;
117
118const struct cmd_entry *cmd_table[] = {
119 &cmd_attach_session_entry,
120 &cmd_bind_key_entry,
121 &cmd_break_pane_entry,
122 &cmd_capture_pane_entry,
123 &cmd_choose_buffer_entry,
124 &cmd_choose_client_entry,
125 &cmd_choose_tree_entry,
126 &cmd_clear_history_entry,
127 &cmd_clock_mode_entry,
128 &cmd_command_prompt_entry,
129 &cmd_confirm_before_entry,
130 &cmd_copy_mode_entry,
131 &cmd_delete_buffer_entry,
132 &cmd_detach_client_entry,
133 &cmd_display_menu_entry,
134 &cmd_display_message_entry,
135 &cmd_display_panes_entry,
136 &cmd_find_window_entry,
137 &cmd_has_session_entry,
138 &cmd_if_shell_entry,
139 &cmd_join_pane_entry,
140 &cmd_kill_pane_entry,
141 &cmd_kill_server_entry,
142 &cmd_kill_session_entry,
143 &cmd_kill_window_entry,
144 &cmd_last_pane_entry,
145 &cmd_last_window_entry,
146 &cmd_link_window_entry,
147 &cmd_list_buffers_entry,
148 &cmd_list_clients_entry,
149 &cmd_list_commands_entry,
150 &cmd_list_keys_entry,
151 &cmd_list_panes_entry,
152 &cmd_list_sessions_entry,
153 &cmd_list_windows_entry,
154 &cmd_load_buffer_entry,
155 &cmd_lock_client_entry,
156 &cmd_lock_server_entry,
157 &cmd_lock_session_entry,
158 &cmd_move_pane_entry,
159 &cmd_move_window_entry,
160 &cmd_new_session_entry,
161 &cmd_new_window_entry,
162 &cmd_next_layout_entry,
163 &cmd_next_window_entry,
164 &cmd_paste_buffer_entry,
165 &cmd_pipe_pane_entry,
166 &cmd_previous_layout_entry,
167 &cmd_previous_window_entry,
168 &cmd_refresh_client_entry,
169 &cmd_rename_session_entry,
170 &cmd_rename_window_entry,
171 &cmd_resize_pane_entry,
172 &cmd_resize_window_entry,
173 &cmd_respawn_pane_entry,
174 &cmd_respawn_window_entry,
175 &cmd_rotate_window_entry,
176 &cmd_run_shell_entry,
177 &cmd_save_buffer_entry,
178 &cmd_select_layout_entry,
179 &cmd_select_pane_entry,
180 &cmd_select_window_entry,
181 &cmd_send_keys_entry,
182 &cmd_send_prefix_entry,
183 &cmd_set_buffer_entry,
184 &cmd_set_environment_entry,
185 &cmd_set_hook_entry,
186 &cmd_set_option_entry,
187 &cmd_set_window_option_entry,
188 &cmd_show_buffer_entry,
189 &cmd_show_environment_entry,
190 &cmd_show_hooks_entry,
191 &cmd_show_messages_entry,
192 &cmd_show_options_entry,
193 &cmd_show_window_options_entry,
194 &cmd_source_file_entry,
195 &cmd_split_window_entry,
196 &cmd_start_server_entry,
197 &cmd_suspend_client_entry,
198 &cmd_swap_pane_entry,
199 &cmd_swap_window_entry,
200 &cmd_switch_client_entry,
201 &cmd_unbind_key_entry,
202 &cmd_unlink_window_entry,
203 &cmd_wait_for_entry,
204 NULL
205};
206
207static u_int cmd_list_next_group = 1;
208
209void printflike(3, 4)
210cmd_log_argv(int argc, char **argv, const char *fmt, ...)
211{
212 char *prefix;
213 va_list ap;
214 int i;
215
216 va_start(ap, fmt);
217 xvasprintf(&prefix, fmt, ap);
218 va_end(ap);
219
220 for (i = 0; i < argc; i++)
221 log_debug("%s: argv[%d]=%s", prefix, i, argv[i]);
222 free(prefix);
223}
224
225void
226cmd_prepend_argv(int *argc, char ***argv, char *arg)
227{
228 char **new_argv;
229 int i;
230
231 new_argv = xreallocarray(NULL, (*argc) + 1, sizeof *new_argv);
232 new_argv[0] = xstrdup(arg);
233 for (i = 0; i < *argc; i++)
234 new_argv[1 + i] = (*argv)[i];
235
236 free(*argv);
237 *argv = new_argv;
238 (*argc)++;
239}
240
241void
242cmd_append_argv(int *argc, char ***argv, char *arg)
243{
244 *argv = xreallocarray(*argv, (*argc) + 1, sizeof **argv);
245 (*argv)[(*argc)++] = xstrdup(arg);
246}
247
248int
249cmd_pack_argv(int argc, char **argv, char *buf, size_t len)
250{
251 size_t arglen;
252 int i;
253
254 if (argc == 0)
255 return (0);
256 cmd_log_argv(argc, argv, "%s", __func__);
257
258 *buf = '\0';
259 for (i = 0; i < argc; i++) {
260 if (strlcpy(buf, argv[i], len) >= len)
261 return (-1);
262 arglen = strlen(argv[i]) + 1;
263 buf += arglen;
264 len -= arglen;
265 }
266
267 return (0);
268}
269
270int
271cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv)
272{
273 int i;
274 size_t arglen;
275
276 if (argc == 0)
277 return (0);
278 *argv = xcalloc(argc, sizeof **argv);
279
280 buf[len - 1] = '\0';
281 for (i = 0; i < argc; i++) {
282 if (len == 0) {
283 cmd_free_argv(argc, *argv);
284 return (-1);
285 }
286
287 arglen = strlen(buf) + 1;
288 (*argv)[i] = xstrdup(buf);
289
290 buf += arglen;
291 len -= arglen;
292 }
293 cmd_log_argv(argc, *argv, "%s", __func__);
294
295 return (0);
296}
297
298char **
299cmd_copy_argv(int argc, char **argv)
300{
301 char **new_argv;
302 int i;
303
304 if (argc == 0)
305 return (NULL);
306 new_argv = xcalloc(argc + 1, sizeof *new_argv);
307 for (i = 0; i < argc; i++) {
308 if (argv[i] != NULL)
309 new_argv[i] = xstrdup(argv[i]);
310 }
311 return (new_argv);
312}
313
314void
315cmd_free_argv(int argc, char **argv)
316{
317 int i;
318
319 if (argc == 0)
320 return;
321 for (i = 0; i < argc; i++)
322 free(argv[i]);
323 free(argv);
324}
325
326char *
327cmd_stringify_argv(int argc, char **argv)
328{
329 char *buf;
330 int i;
331 size_t len;
332
333 if (argc == 0)
334 return (xstrdup(""));
335
336 len = 0;
337 buf = NULL;
338
339 for (i = 0; i < argc; i++) {
340 len += strlen(argv[i]) + 1;
341 buf = xrealloc(buf, len);
342
343 if (i == 0)
344 *buf = '\0';
345 else
346 strlcat(buf, " ", len);
347 strlcat(buf, argv[i], len);
348 }
349 return (buf);
350}
351
352char *
353cmd_get_alias(const char *name)
354{
355 struct options_entry *o;
356 struct options_array_item *a;
357 union options_value *ov;
358 size_t wanted, n;
359 const char *equals;
360
361 o = options_get_only(global_options, "command-alias");
362 if (o == NULL)
363 return (NULL);
364 wanted = strlen(name);
365
366 a = options_array_first(o);
367 while (a != NULL) {
368 ov = options_array_item_value(a);
369
370 equals = strchr(ov->string, '=');
371 if (equals != NULL) {
372 n = equals - ov->string;
373 if (n == wanted && strncmp(name, ov->string, n) == 0)
374 return (xstrdup(equals + 1));
375 }
376
377 a = options_array_next(a);
378 }
379 return (NULL);
380}
381
382static const struct cmd_entry *
383cmd_find(const char *name, char **cause)
384{
385 const struct cmd_entry **loop, *entry, *found = NULL;
386 int ambiguous;
387 char s[8192];
388
389 ambiguous = 0;
390 for (loop = cmd_table; *loop != NULL; loop++) {
391 entry = *loop;
392 if (entry->alias != NULL && strcmp(entry->alias, name) == 0) {
393 ambiguous = 0;
394 found = entry;
395 break;
396 }
397
398 if (strncmp(entry->name, name, strlen(name)) != 0)
399 continue;
400 if (found != NULL)
401 ambiguous = 1;
402 found = entry;
403
404 if (strcmp(entry->name, name) == 0)
405 break;
406 }
407 if (ambiguous)
408 goto ambiguous;
409 if (found == NULL) {
410 xasprintf(cause, "unknown command: %s", name);
411 return (NULL);
412 }
413 return (found);
414
415ambiguous:
416 *s = '\0';
417 for (loop = cmd_table; *loop != NULL; loop++) {
418 entry = *loop;
419 if (strncmp(entry->name, name, strlen(name)) != 0)
420 continue;
421 if (strlcat(s, entry->name, sizeof s) >= sizeof s)
422 break;
423 if (strlcat(s, ", ", sizeof s) >= sizeof s)
424 break;
425 }
426 s[strlen(s) - 2] = '\0';
427 xasprintf(cause, "ambiguous command: %s, could be: %s", name, s);
428 return (NULL);
429}
430
431struct cmd *
432cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause)
433{
434 const struct cmd_entry *entry;
435 const char *name;
436 struct cmd *cmd;
437 struct args *args;
438
439 if (argc == 0) {
440 xasprintf(cause, "no command");
441 return (NULL);
442 }
443 name = argv[0];
444
445 entry = cmd_find(name, cause);
446 if (entry == NULL)
447 return (NULL);
448 cmd_log_argv(argc, argv, "%s: %s", __func__, entry->name);
449
450 args = args_parse(entry->args.template, argc, argv);
451 if (args == NULL)
452 goto usage;
453 if (entry->args.lower != -1 && args->argc < entry->args.lower)
454 goto usage;
455 if (entry->args.upper != -1 && args->argc > entry->args.upper)
456 goto usage;
457
458 cmd = xcalloc(1, sizeof *cmd);
459 cmd->entry = entry;
460 cmd->args = args;
461
462 if (file != NULL)
463 cmd->file = xstrdup(file);
464 cmd->line = line;
465
466 cmd->alias = NULL;
467 cmd->argc = argc;
468 cmd->argv = cmd_copy_argv(argc, argv);
469
470 return (cmd);
471
472usage:
473 if (args != NULL)
474 args_free(args);
475 xasprintf(cause, "usage: %s %s", entry->name, entry->usage);
476 return (NULL);
477}
478
479void
480cmd_free(struct cmd *cmd)
481{
482 free(cmd->alias);
483 cmd_free_argv(cmd->argc, cmd->argv);
484
485 free(cmd->file);
486
487 args_free(cmd->args);
488 free(cmd);
489}
490
491char *
492cmd_print(struct cmd *cmd)
493{
494 char *out, *s;
495
496 s = args_print(cmd->args);
497 if (*s != '\0')
498 xasprintf(&out, "%s %s", cmd->entry->name, s);
499 else
500 out = xstrdup(cmd->entry->name);
501 free(s);
502
503 return (out);
504}
505
506struct cmd_list *
507cmd_list_new(void)
508{
509 struct cmd_list *cmdlist;
510
511 cmdlist = xcalloc(1, sizeof *cmdlist);
512 cmdlist->references = 1;
513 cmdlist->group = cmd_list_next_group++;
514 TAILQ_INIT(&cmdlist->list);
515 return (cmdlist);
516}
517
518void
519cmd_list_append(struct cmd_list *cmdlist, struct cmd *cmd)
520{
521 cmd->group = cmdlist->group;
522 TAILQ_INSERT_TAIL(&cmdlist->list, cmd, qentry);
523}
524
525void
526cmd_list_move(struct cmd_list *cmdlist, struct cmd_list *from)
527{
528 struct cmd *cmd, *cmd1;
529
530 TAILQ_FOREACH_SAFE(cmd, &from->list, qentry, cmd1) {
531 TAILQ_REMOVE(&from->list, cmd, qentry);
532 TAILQ_INSERT_TAIL(&cmdlist->list, cmd, qentry);
533 }
534 cmdlist->group = cmd_list_next_group++;
535}
536
537void
538cmd_list_free(struct cmd_list *cmdlist)
539{
540 struct cmd *cmd, *cmd1;
541
542 if (--cmdlist->references != 0)
543 return;
544
545 TAILQ_FOREACH_SAFE(cmd, &cmdlist->list, qentry, cmd1) {
546 TAILQ_REMOVE(&cmdlist->list, cmd, qentry);
547 cmd_free(cmd);
548 }
549
550 free(cmdlist);
551}
552
553char *
554cmd_list_print(struct cmd_list *cmdlist, int escaped)
555{
556 struct cmd *cmd;
557 char *buf, *this;
558 size_t len;
559
560 len = 1;
561 buf = xcalloc(1, len);
562
563 TAILQ_FOREACH(cmd, &cmdlist->list, qentry) {
564 this = cmd_print(cmd);
565
566 len += strlen(this) + 4;
567 buf = xrealloc(buf, len);
568
569 strlcat(buf, this, len);
570 if (TAILQ_NEXT(cmd, qentry) != NULL) {
571 if (escaped)
572 strlcat(buf, " \\; ", len);
573 else
574 strlcat(buf, " ; ", len);
575 }
576
577 free(this);
578 }
579
580 return (buf);
581}
582
583/* Adjust current mouse position for a pane. */
584int
585cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp,
586 u_int *yp, int last)
587{
588 u_int x, y;
589
590 if (last) {
591 x = m->lx + m->ox;
592 y = m->ly + m->oy;
593 } else {
594 x = m->x + m->ox;
595 y = m->y + m->oy;
596 }
597 log_debug("%s: x=%u, y=%u%s", __func__, x, y, last ? " (last)" : "");
598
599 if (m->statusat == 0 && y >= m->statuslines)
600 y -= m->statuslines;
601
602 if (x < wp->xoff || x >= wp->xoff + wp->sx)
603 return (-1);
604 if (y < wp->yoff || y >= wp->yoff + wp->sy)
605 return (-1);
606
607 if (xp != NULL)
608 *xp = x - wp->xoff;
609 if (yp != NULL)
610 *yp = y - wp->yoff;
611 return (0);
612}
613
614/* Get current mouse window if any. */
615struct winlink *
616cmd_mouse_window(struct mouse_event *m, struct session **sp)
617{
618 struct session *s;
619 struct window *w;
620 struct winlink *wl;
621
622 if (!m->valid)
623 return (NULL);
624 if (m->s == -1 || (s = session_find_by_id(m->s)) == NULL)
625 return (NULL);
626 if (m->w == -1)
627 wl = s->curw;
628 else {
629 if ((w = window_find_by_id(m->w)) == NULL)
630 return (NULL);
631 wl = winlink_find_by_window(&s->windows, w);
632 }
633 if (sp != NULL)
634 *sp = s;
635 return (wl);
636}
637
638/* Get current mouse pane if any. */
639struct window_pane *
640cmd_mouse_pane(struct mouse_event *m, struct session **sp,
641 struct winlink **wlp)
642{
643 struct winlink *wl;
644 struct window_pane *wp;
645
646 if ((wl = cmd_mouse_window(m, sp)) == NULL)
647 return (NULL);
648 if ((wp = window_pane_find_by_id(m->wp)) == NULL)
649 return (NULL);
650 if (!window_has_pane(wl->window, wp))
651 return (NULL);
652
653 if (wlp != NULL)
654 *wlp = wl;
655 return (wp);
656}
657
658/* Replace the first %% or %idx in template by s. */
659char *
660cmd_template_replace(const char *template, const char *s, int idx)
661{
662 char ch, *buf;
663 const char *ptr, *cp, quote[] = "\"\\$;~";
664 int replaced, quoted;
665 size_t len;
666
667 if (strchr(template, '%') == NULL)
668 return (xstrdup(template));
669
670 buf = xmalloc(1);
671 *buf = '\0';
672 len = 0;
673 replaced = 0;
674
675 ptr = template;
676 while (*ptr != '\0') {
677 switch (ch = *ptr++) {
678 case '%':
679 if (*ptr < '1' || *ptr > '9' || *ptr - '0' != idx) {
680 if (*ptr != '%' || replaced)
681 break;
682 replaced = 1;
683 }
684 ptr++;
685
686 quoted = (*ptr == '%');
687 if (quoted)
688 ptr++;
689
690 buf = xrealloc(buf, len + (strlen(s) * 3) + 1);
691 for (cp = s; *cp != '\0'; cp++) {
692 if (quoted && strchr(quote, *cp) != NULL)
693 buf[len++] = '\\';
694 buf[len++] = *cp;
695 }
696 buf[len] = '\0';
697 continue;
698 }
699 buf = xrealloc(buf, len + 2);
700 buf[len++] = ch;
701 buf[len] = '\0';
702 }
703
704 return (buf);
705}
706