Annotation of src/usr.bin/tmux/cmd.c, Revision 1.92
1.92 ! nicm 1: /* $OpenBSD: cmd.c,v 1.91 2014/03/31 21:32:31 nicm Exp $ */
1.1 nicm 2:
3: /*
4: * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
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:
1.5 nicm 22: #include <fnmatch.h>
23: #include <paths.h>
1.61 nicm 24: #include <pwd.h>
1.1 nicm 25: #include <stdlib.h>
26: #include <string.h>
27: #include <unistd.h>
28:
29: #include "tmux.h"
30:
31: const struct cmd_entry *cmd_table[] = {
32: &cmd_attach_session_entry,
33: &cmd_bind_key_entry,
34: &cmd_break_pane_entry,
1.34 nicm 35: &cmd_capture_pane_entry,
1.42 nicm 36: &cmd_choose_buffer_entry,
1.15 nicm 37: &cmd_choose_client_entry,
1.1 nicm 38: &cmd_choose_session_entry,
1.67 nicm 39: &cmd_choose_tree_entry,
1.1 nicm 40: &cmd_choose_window_entry,
41: &cmd_clear_history_entry,
42: &cmd_clock_mode_entry,
43: &cmd_command_prompt_entry,
44: &cmd_confirm_before_entry,
45: &cmd_copy_mode_entry,
46: &cmd_delete_buffer_entry,
47: &cmd_detach_client_entry,
1.7 nicm 48: &cmd_display_message_entry,
1.16 nicm 49: &cmd_display_panes_entry,
1.1 nicm 50: &cmd_find_window_entry,
51: &cmd_has_session_entry,
1.4 nicm 52: &cmd_if_shell_entry,
1.37 nicm 53: &cmd_join_pane_entry,
1.1 nicm 54: &cmd_kill_pane_entry,
55: &cmd_kill_server_entry,
56: &cmd_kill_session_entry,
57: &cmd_kill_window_entry,
1.45 nicm 58: &cmd_last_pane_entry,
1.1 nicm 59: &cmd_last_window_entry,
60: &cmd_link_window_entry,
61: &cmd_list_buffers_entry,
62: &cmd_list_clients_entry,
63: &cmd_list_commands_entry,
64: &cmd_list_keys_entry,
1.23 nicm 65: &cmd_list_panes_entry,
1.1 nicm 66: &cmd_list_sessions_entry,
67: &cmd_list_windows_entry,
68: &cmd_load_buffer_entry,
1.19 nicm 69: &cmd_lock_client_entry,
1.1 nicm 70: &cmd_lock_server_entry,
1.19 nicm 71: &cmd_lock_session_entry,
1.63 nicm 72: &cmd_move_pane_entry,
1.1 nicm 73: &cmd_move_window_entry,
74: &cmd_new_session_entry,
75: &cmd_new_window_entry,
76: &cmd_next_layout_entry,
77: &cmd_next_window_entry,
78: &cmd_paste_buffer_entry,
1.24 nicm 79: &cmd_pipe_pane_entry,
1.1 nicm 80: &cmd_previous_layout_entry,
81: &cmd_previous_window_entry,
82: &cmd_refresh_client_entry,
83: &cmd_rename_session_entry,
84: &cmd_rename_window_entry,
85: &cmd_resize_pane_entry,
1.56 nicm 86: &cmd_respawn_pane_entry,
1.1 nicm 87: &cmd_respawn_window_entry,
88: &cmd_rotate_window_entry,
1.17 nicm 89: &cmd_run_shell_entry,
1.1 nicm 90: &cmd_save_buffer_entry,
91: &cmd_select_layout_entry,
92: &cmd_select_pane_entry,
93: &cmd_select_window_entry,
94: &cmd_send_keys_entry,
95: &cmd_send_prefix_entry,
96: &cmd_server_info_entry,
97: &cmd_set_buffer_entry,
1.13 nicm 98: &cmd_set_environment_entry,
1.1 nicm 99: &cmd_set_option_entry,
100: &cmd_set_window_option_entry,
101: &cmd_show_buffer_entry,
1.13 nicm 102: &cmd_show_environment_entry,
1.32 nicm 103: &cmd_show_messages_entry,
1.1 nicm 104: &cmd_show_options_entry,
105: &cmd_show_window_options_entry,
106: &cmd_source_file_entry,
107: &cmd_split_window_entry,
108: &cmd_start_server_entry,
109: &cmd_suspend_client_entry,
110: &cmd_swap_pane_entry,
111: &cmd_swap_window_entry,
112: &cmd_switch_client_entry,
113: &cmd_unbind_key_entry,
114: &cmd_unlink_window_entry,
1.82 nicm 115: &cmd_wait_for_entry,
1.1 nicm 116: NULL
117: };
118:
1.72 nicm 119: int cmd_session_better(struct session *, struct session *, int);
1.47 nicm 120: struct session *cmd_choose_session_list(struct sessionslist *);
1.52 nicm 121: struct session *cmd_choose_session(int);
1.31 nicm 122: struct client *cmd_choose_client(struct clients *);
1.5 nicm 123: struct client *cmd_lookup_client(const char *);
124: struct session *cmd_lookup_session(const char *, int *);
1.83 nicm 125: struct session *cmd_lookup_session_id(const char *);
1.5 nicm 126: struct winlink *cmd_lookup_window(struct session *, const char *, int *);
1.8 nicm 127: int cmd_lookup_index(struct session *, const char *, int *);
1.60 nicm 128: struct winlink *cmd_lookup_winlink_windowid(struct session *, const char *);
1.80 nicm 129: struct session *cmd_window_session(struct cmd_q *, struct window *,
130: struct winlink **);
1.43 nicm 131: struct winlink *cmd_find_window_offset(const char *, struct session *, int *);
132: int cmd_find_index_offset(const char *, struct session *, int *);
1.51 nicm 133: struct window_pane *cmd_find_pane_offset(const char *, struct winlink *);
1.5 nicm 134:
1.10 nicm 135: int
136: cmd_pack_argv(int argc, char **argv, char *buf, size_t len)
137: {
138: size_t arglen;
139: int i;
1.91 nicm 140:
141: if (argc == 0)
142: return (0);
1.10 nicm 143:
144: *buf = '\0';
145: for (i = 0; i < argc; i++) {
146: if (strlcpy(buf, argv[i], len) >= len)
147: return (-1);
148: arglen = strlen(argv[i]) + 1;
149: buf += arglen;
150: len -= arglen;
151: }
152:
153: return (0);
154: }
155:
156: int
157: cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv)
158: {
159: int i;
160: size_t arglen;
161:
162: if (argc == 0)
163: return (0);
164: *argv = xcalloc(argc, sizeof **argv);
165:
166: buf[len - 1] = '\0';
167: for (i = 0; i < argc; i++) {
168: if (len == 0) {
169: cmd_free_argv(argc, *argv);
170: return (-1);
171: }
172:
173: arglen = strlen(buf) + 1;
174: (*argv)[i] = xstrdup(buf);
175: buf += arglen;
176: len -= arglen;
177: }
178:
179: return (0);
1.46 nicm 180: }
181:
182: char **
1.49 nicm 183: cmd_copy_argv(int argc, char *const *argv)
1.46 nicm 184: {
185: char **new_argv;
186: int i;
187:
188: if (argc == 0)
189: return (NULL);
190: new_argv = xcalloc(argc, sizeof *new_argv);
191: for (i = 0; i < argc; i++) {
192: if (argv[i] != NULL)
193: new_argv[i] = xstrdup(argv[i]);
194: }
195: return (new_argv);
1.10 nicm 196: }
197:
198: void
199: cmd_free_argv(int argc, char **argv)
200: {
201: int i;
202:
203: if (argc == 0)
1.35 nicm 204: return;
1.68 nicm 205: for (i = 0; i < argc; i++)
206: free(argv[i]);
207: free(argv);
1.10 nicm 208: }
209:
1.1 nicm 210: struct cmd *
1.80 nicm 211: cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause)
1.1 nicm 212: {
213: const struct cmd_entry **entryp, *entry;
1.27 deraadt 214: struct cmd *cmd;
1.49 nicm 215: struct args *args;
1.1 nicm 216: char s[BUFSIZ];
1.49 nicm 217: int ambiguous = 0;
1.1 nicm 218:
219: *cause = NULL;
1.2 nicm 220: if (argc == 0) {
221: xasprintf(cause, "no command");
1.1 nicm 222: return (NULL);
1.2 nicm 223: }
1.1 nicm 224:
225: entry = NULL;
226: for (entryp = cmd_table; *entryp != NULL; entryp++) {
227: if ((*entryp)->alias != NULL &&
228: strcmp((*entryp)->alias, argv[0]) == 0) {
1.3 nicm 229: ambiguous = 0;
1.1 nicm 230: entry = *entryp;
231: break;
232: }
233:
234: if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0)
235: continue;
236: if (entry != NULL)
1.3 nicm 237: ambiguous = 1;
1.1 nicm 238: entry = *entryp;
239:
240: /* Bail now if an exact match. */
241: if (strcmp(entry->name, argv[0]) == 0)
242: break;
243: }
1.3 nicm 244: if (ambiguous)
245: goto ambiguous;
1.1 nicm 246: if (entry == NULL) {
247: xasprintf(cause, "unknown command: %s", argv[0]);
248: return (NULL);
249: }
250:
1.49 nicm 251: args = args_parse(entry->args_template, argc, argv);
252: if (args == NULL)
253: goto usage;
254: if (entry->args_lower != -1 && args->argc < entry->args_lower)
255: goto usage;
256: if (entry->args_upper != -1 && args->argc > entry->args_upper)
257: goto usage;
1.1 nicm 258:
1.80 nicm 259: cmd = xcalloc(1, sizeof *cmd);
1.1 nicm 260: cmd->entry = entry;
1.49 nicm 261: cmd->args = args;
1.80 nicm 262:
263: if (file != NULL)
264: cmd->file = xstrdup(file);
265: cmd->line = line;
266:
1.1 nicm 267: return (cmd);
268:
269: ambiguous:
270: *s = '\0';
271: for (entryp = cmd_table; *entryp != NULL; entryp++) {
272: if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0)
273: continue;
274: if (strlcat(s, (*entryp)->name, sizeof s) >= sizeof s)
275: break;
276: if (strlcat(s, ", ", sizeof s) >= sizeof s)
277: break;
278: }
279: s[strlen(s) - 2] = '\0';
280: xasprintf(cause, "ambiguous command: %s, could be: %s", argv[0], s);
281: return (NULL);
282:
283: usage:
1.49 nicm 284: if (args != NULL)
285: args_free(args);
1.1 nicm 286: xasprintf(cause, "usage: %s %s", entry->name, entry->usage);
287: return (NULL);
288: }
289:
290: size_t
291: cmd_print(struct cmd *cmd, char *buf, size_t len)
292: {
1.49 nicm 293: size_t off, used;
294:
295: off = xsnprintf(buf, len, "%s ", cmd->entry->name);
1.85 nicm 296: if (off + 1 < len) {
297: used = args_print(cmd->args, buf + off, len - off - 1);
1.49 nicm 298: if (used == 0)
1.64 nicm 299: off--;
300: else
1.49 nicm 301: off += used;
1.64 nicm 302: buf[off] = '\0';
1.49 nicm 303: }
304: return (off);
1.1 nicm 305: }
306:
1.5 nicm 307: /*
308: * Figure out the current session. Use: 1) the current session, if the command
1.31 nicm 309: * context has one; 2) the most recently used session containing the pty of the
310: * calling client, if any; 3) the session specified in the TMUX variable from
311: * the environment (as passed from the client); 4) the most recently used
312: * session from all sessions.
1.5 nicm 313: */
1.1 nicm 314: struct session *
1.80 nicm 315: cmd_current_session(struct cmd_q *cmdq, int prefer_unattached)
1.1 nicm 316: {
1.80 nicm 317: struct client *c = cmdq->client;
1.5 nicm 318: struct session *s;
1.47 nicm 319: struct sessionslist ss;
1.11 nicm 320: struct winlink *wl;
321: struct window_pane *wp;
1.80 nicm 322: const char *path;
1.11 nicm 323: int found;
1.1 nicm 324:
1.80 nicm 325: if (c != NULL && c->session != NULL)
326: return (c->session);
1.1 nicm 327:
1.11 nicm 328: /*
1.81 nicm 329: * If the name of the calling client's pty is known, build a list of
330: * the sessions that contain it and if any choose either the first or
331: * the newest.
1.11 nicm 332: */
1.80 nicm 333: path = c == NULL ? NULL : c->tty.path;
334: if (path != NULL) {
1.11 nicm 335: ARRAY_INIT(&ss);
1.47 nicm 336: RB_FOREACH(s, sessions, &sessions) {
1.11 nicm 337: found = 0;
338: RB_FOREACH(wl, winlinks, &s->windows) {
339: TAILQ_FOREACH(wp, &wl->window->panes, entry) {
1.80 nicm 340: if (strcmp(wp->tty, path) == 0) {
1.11 nicm 341: found = 1;
342: break;
343: }
344: }
345: if (found)
346: break;
347: }
348: if (found)
349: ARRAY_ADD(&ss, s);
350: }
351:
1.47 nicm 352: s = cmd_choose_session_list(&ss);
1.11 nicm 353: ARRAY_FREE(&ss);
1.47 nicm 354: if (s != NULL)
355: return (s);
356: }
357:
1.52 nicm 358: return (cmd_choose_session(prefer_unattached));
1.47 nicm 359: }
360:
1.72 nicm 361: /* Is this session better? */
362: int
363: cmd_session_better(struct session *s, struct session *best,
364: int prefer_unattached)
365: {
366: if (best == NULL)
1.74 nicm 367: return (1);
1.72 nicm 368: if (prefer_unattached) {
369: if (!(best->flags & SESSION_UNATTACHED) &&
370: (s->flags & SESSION_UNATTACHED))
1.74 nicm 371: return (1);
1.72 nicm 372: else if ((best->flags & SESSION_UNATTACHED) &&
373: !(s->flags & SESSION_UNATTACHED))
1.74 nicm 374: return (0);
1.72 nicm 375: }
376: return (timercmp(&s->activity_time, &best->activity_time, >));
377: }
378:
1.52 nicm 379: /*
380: * Find the most recently used session, preferring unattached if the flag is
381: * set.
382: */
1.47 nicm 383: struct session *
1.52 nicm 384: cmd_choose_session(int prefer_unattached)
1.47 nicm 385: {
1.72 nicm 386: struct session *s, *best;
1.47 nicm 387:
1.72 nicm 388: best = NULL;
1.47 nicm 389: RB_FOREACH(s, sessions, &sessions) {
1.72 nicm 390: if (cmd_session_better(s, best, prefer_unattached))
391: best = s;
1.47 nicm 392: }
1.72 nicm 393: return (best);
1.5 nicm 394: }
395:
1.31 nicm 396: /* Find the most recently used session from a list. */
1.5 nicm 397: struct session *
1.47 nicm 398: cmd_choose_session_list(struct sessionslist *ss)
1.5 nicm 399: {
1.31 nicm 400: struct session *s, *sbest;
1.5 nicm 401: struct timeval *tv = NULL;
402: u_int i;
403:
1.31 nicm 404: sbest = NULL;
1.11 nicm 405: for (i = 0; i < ARRAY_LENGTH(ss); i++) {
406: if ((s = ARRAY_ITEM(ss, i)) == NULL)
1.5 nicm 407: continue;
408:
1.31 nicm 409: if (tv == NULL || timercmp(&s->activity_time, tv, >)) {
410: sbest = s;
411: tv = &s->activity_time;
1.1 nicm 412: }
413: }
1.5 nicm 414:
1.31 nicm 415: return (sbest);
1.1 nicm 416: }
417:
1.30 nicm 418: /*
419: * Find the current client. First try the current client if set, then pick the
1.31 nicm 420: * most recently used of the clients attached to the current session if any,
421: * then of all clients.
1.30 nicm 422: */
423: struct client *
1.80 nicm 424: cmd_current_client(struct cmd_q *cmdq)
1.30 nicm 425: {
426: struct session *s;
427: struct client *c;
428: struct clients cc;
429: u_int i;
430:
1.80 nicm 431: if (cmdq->client != NULL && cmdq->client->session != NULL)
432: return (cmdq->client);
1.30 nicm 433:
434: /*
435: * No current client set. Find the current session and return the
436: * newest of its clients.
437: */
1.80 nicm 438: s = cmd_current_session(cmdq, 0);
1.30 nicm 439: if (s != NULL && !(s->flags & SESSION_UNATTACHED)) {
440: ARRAY_INIT(&cc);
441: for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
442: if ((c = ARRAY_ITEM(&clients, i)) == NULL)
443: continue;
444: if (s == c->session)
445: ARRAY_ADD(&cc, c);
446: }
447:
1.31 nicm 448: c = cmd_choose_client(&cc);
1.30 nicm 449: ARRAY_FREE(&cc);
450: if (c != NULL)
451: return (c);
452: }
453:
1.31 nicm 454: return (cmd_choose_client(&clients));
1.30 nicm 455: }
456:
1.31 nicm 457: /* Choose the most recently used client from a list. */
1.20 nicm 458: struct client *
1.31 nicm 459: cmd_choose_client(struct clients *cc)
1.20 nicm 460: {
1.31 nicm 461: struct client *c, *cbest;
1.20 nicm 462: struct timeval *tv = NULL;
463: u_int i;
464:
1.31 nicm 465: cbest = NULL;
1.30 nicm 466: for (i = 0; i < ARRAY_LENGTH(cc); i++) {
467: if ((c = ARRAY_ITEM(cc, i)) == NULL)
1.20 nicm 468: continue;
469: if (c->session == NULL)
470: continue;
471:
1.31 nicm 472: if (tv == NULL || timercmp(&c->activity_time, tv, >)) {
473: cbest = c;
474: tv = &c->activity_time;
1.20 nicm 475: }
476: }
477:
1.31 nicm 478: return (cbest);
1.20 nicm 479: }
480:
1.5 nicm 481: /* Find the target client or report an error and return NULL. */
1.1 nicm 482: struct client *
1.80 nicm 483: cmd_find_client(struct cmd_q *cmdq, const char *arg, int quiet)
1.1 nicm 484: {
1.30 nicm 485: struct client *c;
1.5 nicm 486: char *tmparg;
487: size_t arglen;
1.1 nicm 488:
1.5 nicm 489: /* A NULL argument means the current client. */
1.79 nicm 490: if (arg == NULL) {
1.80 nicm 491: c = cmd_current_client(cmdq);
1.79 nicm 492: if (c == NULL && !quiet)
1.80 nicm 493: cmdq_error(cmdq, "no clients");
1.79 nicm 494: return (c);
495: }
1.5 nicm 496: tmparg = xstrdup(arg);
497:
498: /* Trim a single trailing colon if any. */
499: arglen = strlen(tmparg);
500: if (arglen != 0 && tmparg[arglen - 1] == ':')
501: tmparg[arglen - 1] = '\0';
502:
503: /* Find the client, if any. */
504: c = cmd_lookup_client(tmparg);
505:
506: /* If no client found, report an error. */
1.79 nicm 507: if (c == NULL && !quiet)
1.80 nicm 508: cmdq_error(cmdq, "client not found: %s", tmparg);
1.5 nicm 509:
1.68 nicm 510: free(tmparg);
1.5 nicm 511: return (c);
512: }
513:
514: /*
515: * Lookup a client by device path. Either of a full match and a match without a
516: * leading _PATH_DEV ("/dev/") is accepted.
517: */
518: struct client *
519: cmd_lookup_client(const char *name)
520: {
521: struct client *c;
522: const char *path;
523: u_int i;
524:
525: for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
1.25 nicm 526: c = ARRAY_ITEM(&clients, i);
1.81 nicm 527: if (c == NULL || c->session == NULL || c->tty.path == NULL)
1.5 nicm 528: continue;
529: path = c->tty.path;
530:
531: /* Check for exact matches. */
532: if (strcmp(name, path) == 0)
533: return (c);
534:
535: /* Check without leading /dev if present. */
536: if (strncmp(path, _PATH_DEV, (sizeof _PATH_DEV) - 1) != 0)
537: continue;
538: if (strcmp(name, path + (sizeof _PATH_DEV) - 1) == 0)
539: return (c);
540: }
541:
542: return (NULL);
543: }
544:
1.83 nicm 545: /* Find the target session or report an error and return NULL. */
546: struct session *
547: cmd_lookup_session_id(const char *arg)
548: {
549: char *endptr;
550: long id;
551:
552: if (arg[0] != '$')
553: return (NULL);
554: id = strtol(arg + 1, &endptr, 10);
555: if (arg[1] != '\0' && *endptr == '\0')
556: return (session_find_by_id(id));
557: return (NULL);
558: }
559:
1.5 nicm 560: /* Lookup a session by name. If no session is found, NULL is returned. */
561: struct session *
562: cmd_lookup_session(const char *name, int *ambiguous)
563: {
564: struct session *s, *sfound;
565:
566: *ambiguous = 0;
1.83 nicm 567:
568: /* Look for $id first. */
569: if ((s = cmd_lookup_session_id(name)) != NULL)
570: return (s);
1.5 nicm 571:
572: /*
1.28 nicm 573: * Look for matches. First look for exact matches - session names must
574: * be unique so an exact match can't be ambigious and can just be
575: * returned.
1.5 nicm 576: */
1.47 nicm 577: if ((s = session_find(name)) != NULL)
578: return (s);
1.28 nicm 579:
580: /*
581: * Otherwise look for partial matches, returning early if it is found to
582: * be ambiguous.
583: */
584: sfound = NULL;
1.47 nicm 585: RB_FOREACH(s, sessions, &sessions) {
1.5 nicm 586: if (strncmp(name, s->name, strlen(name)) == 0 ||
587: fnmatch(name, s->name, 0) == 0) {
588: if (sfound != NULL) {
589: *ambiguous = 1;
590: return (NULL);
591: }
592: sfound = s;
593: }
594: }
1.35 nicm 595: return (sfound);
1.5 nicm 596: }
597:
598: /*
1.8 nicm 599: * Lookup a window or return -1 if not found or ambigious. First try as an
600: * index and if invalid, use fnmatch or leading prefix. Return NULL but fill in
1.41 nicm 601: * idx if the window index is a valid number but there is no window with that
1.8 nicm 602: * index.
1.5 nicm 603: */
604: struct winlink *
605: cmd_lookup_window(struct session *s, const char *name, int *ambiguous)
606: {
607: struct winlink *wl, *wlfound;
608: const char *errstr;
609: u_int idx;
610:
611: *ambiguous = 0;
612:
1.60 nicm 613: /* Try as a window id. */
614: if ((wl = cmd_lookup_winlink_windowid(s, name)) != NULL)
615: return (wl);
616:
1.5 nicm 617: /* First see if this is a valid window index in this session. */
618: idx = strtonum(name, 0, INT_MAX, &errstr);
619: if (errstr == NULL) {
620: if ((wl = winlink_find_by_index(&s->windows, idx)) != NULL)
621: return (wl);
622: }
1.35 nicm 623:
1.5 nicm 624: /* Look for exact matches, error if more than one. */
625: wlfound = NULL;
626: RB_FOREACH(wl, winlinks, &s->windows) {
1.8 nicm 627: if (strcmp(name, wl->window->name) == 0) {
1.5 nicm 628: if (wlfound != NULL) {
629: *ambiguous = 1;
630: return (NULL);
631: }
632: wlfound = wl;
1.1 nicm 633: }
634: }
1.5 nicm 635: if (wlfound != NULL)
636: return (wlfound);
637:
638: /* Now look for pattern matches, again error if multiple. */
639: wlfound = NULL;
640: RB_FOREACH(wl, winlinks, &s->windows) {
1.8 nicm 641: if (strncmp(name, wl->window->name, strlen(name)) == 0 ||
642: fnmatch(name, wl->window->name, 0) == 0) {
1.5 nicm 643: if (wlfound != NULL) {
644: *ambiguous = 1;
645: return (NULL);
646: }
647: wlfound = wl;
648: }
1.35 nicm 649: }
1.5 nicm 650: if (wlfound != NULL)
651: return (wlfound);
652:
653: return (NULL);
1.1 nicm 654: }
655:
1.8 nicm 656: /*
657: * Find a window index - if the window doesn't exist, check if it is a
658: * potential index and return it anyway.
659: */
660: int
661: cmd_lookup_index(struct session *s, const char *name, int *ambiguous)
662: {
663: struct winlink *wl;
664: const char *errstr;
665: u_int idx;
666:
667: if ((wl = cmd_lookup_window(s, name, ambiguous)) != NULL)
668: return (wl->idx);
669: if (*ambiguous)
670: return (-1);
671:
672: idx = strtonum(name, 0, INT_MAX, &errstr);
673: if (errstr == NULL)
674: return (idx);
675:
676: return (-1);
677: }
678:
1.60 nicm 679: /* Lookup pane id. An initial % means a pane id. */
1.51 nicm 680: struct window_pane *
681: cmd_lookup_paneid(const char *arg)
682: {
683: const char *errstr;
684: u_int paneid;
685:
686: if (*arg != '%')
687: return (NULL);
688:
689: paneid = strtonum(arg + 1, 0, UINT_MAX, &errstr);
690: if (errstr != NULL)
691: return (NULL);
692: return (window_pane_find_by_id(paneid));
693: }
694:
1.60 nicm 695: /* Lookup window id in a session. An initial @ means a window id. */
696: struct winlink *
697: cmd_lookup_winlink_windowid(struct session *s, const char *arg)
698: {
699: const char *errstr;
700: u_int windowid;
701:
702: if (*arg != '@')
703: return (NULL);
704:
705: windowid = strtonum(arg + 1, 0, UINT_MAX, &errstr);
706: if (errstr != NULL)
707: return (NULL);
708: return (winlink_find_by_window_id(&s->windows, windowid));
709: }
710:
711: /* Lookup window id. An initial @ means a window id. */
712: struct window *
713: cmd_lookup_windowid(const char *arg)
714: {
715: const char *errstr;
716: u_int windowid;
717:
718: if (*arg != '@')
719: return (NULL);
720:
721: windowid = strtonum(arg + 1, 0, UINT_MAX, &errstr);
722: if (errstr != NULL)
723: return (NULL);
724: return (window_find_by_id(windowid));
725: }
726:
727: /* Find session and winlink for window. */
1.51 nicm 728: struct session *
1.80 nicm 729: cmd_window_session(struct cmd_q *cmdq, struct window *w, struct winlink **wlp)
1.51 nicm 730: {
731: struct session *s;
732: struct sessionslist ss;
733: struct winlink *wl;
734:
1.60 nicm 735: /* If this window is in the current session, return that winlink. */
1.80 nicm 736: s = cmd_current_session(cmdq, 0);
1.51 nicm 737: if (s != NULL) {
1.60 nicm 738: wl = winlink_find_by_window(&s->windows, w);
1.51 nicm 739: if (wl != NULL) {
740: if (wlp != NULL)
741: *wlp = wl;
742: return (s);
743: }
744: }
745:
1.60 nicm 746: /* Otherwise choose from all sessions with this window. */
1.51 nicm 747: ARRAY_INIT(&ss);
748: RB_FOREACH(s, sessions, &sessions) {
1.60 nicm 749: if (winlink_find_by_window(&s->windows, w) != NULL)
1.51 nicm 750: ARRAY_ADD(&ss, s);
751: }
752: s = cmd_choose_session_list(&ss);
753: ARRAY_FREE(&ss);
754: if (wlp != NULL)
1.60 nicm 755: *wlp = winlink_find_by_window(&s->windows, w);
1.51 nicm 756: return (s);
757: }
758:
1.5 nicm 759: /* Find the target session or report an error and return NULL. */
1.1 nicm 760: struct session *
1.80 nicm 761: cmd_find_session(struct cmd_q *cmdq, const char *arg, int prefer_unattached)
1.1 nicm 762: {
1.51 nicm 763: struct session *s;
764: struct window_pane *wp;
1.60 nicm 765: struct window *w;
1.51 nicm 766: struct client *c;
767: char *tmparg;
768: size_t arglen;
769: int ambiguous;
1.1 nicm 770:
1.5 nicm 771: /* A NULL argument means the current session. */
1.1 nicm 772: if (arg == NULL)
1.80 nicm 773: return (cmd_current_session(cmdq, prefer_unattached));
1.5 nicm 774:
1.60 nicm 775: /* Lookup as pane id or window id. */
1.51 nicm 776: if ((wp = cmd_lookup_paneid(arg)) != NULL)
1.80 nicm 777: return (cmd_window_session(cmdq, wp->window, NULL));
1.60 nicm 778: if ((w = cmd_lookup_windowid(arg)) != NULL)
1.80 nicm 779: return (cmd_window_session(cmdq, w, NULL));
1.51 nicm 780:
1.5 nicm 781: /* Trim a single trailing colon if any. */
1.54 nicm 782: tmparg = xstrdup(arg);
1.5 nicm 783: arglen = strlen(tmparg);
784: if (arglen != 0 && tmparg[arglen - 1] == ':')
785: tmparg[arglen - 1] = '\0';
786:
1.53 nicm 787: /* An empty session name is the current session. */
788: if (*tmparg == '\0') {
1.68 nicm 789: free(tmparg);
1.80 nicm 790: return (cmd_current_session(cmdq, prefer_unattached));
1.53 nicm 791: }
792:
1.5 nicm 793: /* Find the session, if any. */
794: s = cmd_lookup_session(tmparg, &ambiguous);
795:
796: /* If it doesn't, try to match it as a client. */
797: if (s == NULL && (c = cmd_lookup_client(tmparg)) != NULL)
798: s = c->session;
799:
800: /* If no session found, report an error. */
801: if (s == NULL) {
802: if (ambiguous)
1.80 nicm 803: cmdq_error(cmdq, "more than one session: %s", tmparg);
1.5 nicm 804: else
1.80 nicm 805: cmdq_error(cmdq, "session not found: %s", tmparg);
1.1 nicm 806: }
1.5 nicm 807:
1.68 nicm 808: free(tmparg);
1.1 nicm 809: return (s);
810: }
811:
1.5 nicm 812: /* Find the target session and window or report an error and return NULL. */
1.1 nicm 813: struct winlink *
1.80 nicm 814: cmd_find_window(struct cmd_q *cmdq, const char *arg, struct session **sp)
1.1 nicm 815: {
1.51 nicm 816: struct session *s;
817: struct winlink *wl;
818: struct window_pane *wp;
819: const char *winptr;
820: char *sessptr = NULL;
821: int ambiguous = 0;
1.5 nicm 822:
823: /*
824: * Find the current session. There must always be a current session, if
825: * it can't be found, report an error.
826: */
1.80 nicm 827: if ((s = cmd_current_session(cmdq, 0)) == NULL) {
828: cmdq_error(cmdq, "can't establish current session");
1.5 nicm 829: return (NULL);
830: }
831:
832: /* A NULL argument means the current session and window. */
833: if (arg == NULL) {
834: if (sp != NULL)
835: *sp = s;
836: return (s->curw);
837: }
838:
1.51 nicm 839: /* Lookup as pane id. */
840: if ((wp = cmd_lookup_paneid(arg)) != NULL) {
1.80 nicm 841: s = cmd_window_session(cmdq, wp->window, &wl);
1.51 nicm 842: if (sp != NULL)
843: *sp = s;
844: return (wl);
845: }
846:
1.5 nicm 847: /* Time to look at the argument. If it is empty, that is an error. */
848: if (*arg == '\0')
849: goto not_found;
850:
1.8 nicm 851: /* Find the separating colon and split into window and session. */
1.5 nicm 852: winptr = strchr(arg, ':');
853: if (winptr == NULL)
1.8 nicm 854: goto no_colon;
855: winptr++; /* skip : */
856: sessptr = xstrdup(arg);
857: *strchr(sessptr, ':') = '\0';
1.5 nicm 858:
859: /* Try to lookup the session if present. */
1.8 nicm 860: if (*sessptr != '\0') {
861: if ((s = cmd_lookup_session(sessptr, &ambiguous)) == NULL)
1.5 nicm 862: goto no_session;
863: }
864: if (sp != NULL)
865: *sp = s;
866:
867: /*
868: * Then work out the window. An empty string is the current window,
1.38 nicm 869: * otherwise try special cases then to look it up in the session.
1.5 nicm 870: */
1.8 nicm 871: if (*winptr == '\0')
1.5 nicm 872: wl = s->curw;
1.38 nicm 873: else if (winptr[0] == '!' && winptr[1] == '\0')
874: wl = TAILQ_FIRST(&s->lastw);
1.73 nicm 875: else if (winptr[0] == '^' && winptr[1] == '\0')
876: wl = RB_MIN(winlinks, &s->windows);
877: else if (winptr[0] == '$' && winptr[1] == '\0')
878: wl = RB_MAX(winlinks, &s->windows);
1.43 nicm 879: else if (winptr[0] == '+' || winptr[0] == '-')
880: wl = cmd_find_window_offset(winptr, s, &ambiguous);
881: else
1.38 nicm 882: wl = cmd_lookup_window(s, winptr, &ambiguous);
883: if (wl == NULL)
1.5 nicm 884: goto not_found;
1.35 nicm 885:
1.5 nicm 886: if (sessptr != NULL)
1.68 nicm 887: free(sessptr);
1.5 nicm 888: return (wl);
889:
1.8 nicm 890: no_colon:
1.38 nicm 891: /*
892: * No colon in the string, first try special cases, then as a window
893: * and lastly as a session.
894: */
895: if (arg[0] == '!' && arg[1] == '\0') {
896: if ((wl = TAILQ_FIRST(&s->lastw)) == NULL)
897: goto not_found;
1.41 nicm 898: } else if (arg[0] == '+' || arg[0] == '-') {
1.43 nicm 899: if ((wl = cmd_find_window_offset(arg, s, &ambiguous)) == NULL)
1.41 nicm 900: goto lookup_session;
901: } else if ((wl = cmd_lookup_window(s, arg, &ambiguous)) == NULL)
902: goto lookup_session;
1.8 nicm 903:
904: if (sp != NULL)
905: *sp = s;
906:
907: return (wl);
908:
1.41 nicm 909: lookup_session:
910: if (ambiguous)
911: goto not_found;
1.53 nicm 912: if (*arg != '\0' && (s = cmd_lookup_session(arg, &ambiguous)) == NULL)
1.41 nicm 913: goto no_session;
914:
915: if (sp != NULL)
916: *sp = s;
917:
918: return (s->curw);
919:
1.5 nicm 920: no_session:
921: if (ambiguous)
1.80 nicm 922: cmdq_error(cmdq, "multiple sessions: %s", arg);
1.5 nicm 923: else
1.80 nicm 924: cmdq_error(cmdq, "session not found: %s", arg);
1.68 nicm 925: free(sessptr);
1.5 nicm 926: return (NULL);
927:
928: not_found:
929: if (ambiguous)
1.80 nicm 930: cmdq_error(cmdq, "multiple windows: %s", arg);
1.5 nicm 931: else
1.80 nicm 932: cmdq_error(cmdq, "window not found: %s", arg);
1.68 nicm 933: free(sessptr);
1.5 nicm 934: return (NULL);
935: }
1.1 nicm 936:
1.43 nicm 937: struct winlink *
938: cmd_find_window_offset(const char *winptr, struct session *s, int *ambiguous)
939: {
940: struct winlink *wl;
941: int offset = 1;
942:
943: if (winptr[1] != '\0')
944: offset = strtonum(winptr + 1, 1, INT_MAX, NULL);
945: if (offset == 0)
946: wl = cmd_lookup_window(s, winptr, ambiguous);
947: else {
948: if (winptr[0] == '+')
949: wl = winlink_next_by_number(s->curw, s, offset);
950: else
951: wl = winlink_previous_by_number(s->curw, s, offset);
952: }
953:
954: return (wl);
955: }
956:
1.5 nicm 957: /*
958: * Find the target session and window index, whether or not it exists in the
959: * session. Return -2 on error or -1 if no window index is specified. This is
1.8 nicm 960: * used when parsing an argument for a window target that may not exist (for
961: * example if it is going to be created).
1.5 nicm 962: */
963: int
1.80 nicm 964: cmd_find_index(struct cmd_q *cmdq, const char *arg, struct session **sp)
1.5 nicm 965: {
966: struct session *s;
1.38 nicm 967: struct winlink *wl;
1.8 nicm 968: const char *winptr;
1.5 nicm 969: char *sessptr = NULL;
970: int idx, ambiguous = 0;
971:
972: /*
973: * Find the current session. There must always be a current session, if
974: * it can't be found, report an error.
975: */
1.80 nicm 976: if ((s = cmd_current_session(cmdq, 0)) == NULL) {
977: cmdq_error(cmdq, "can't establish current session");
1.9 nicm 978: return (-2);
1.1 nicm 979: }
1.5 nicm 980:
981: /* A NULL argument means the current session and "no window" (-1). */
982: if (arg == NULL) {
983: if (sp != NULL)
984: *sp = s;
985: return (-1);
986: }
987:
988: /* Time to look at the argument. If it is empty, that is an error. */
989: if (*arg == '\0')
990: goto not_found;
991:
992: /* Find the separating colon. If none, assume the current session. */
993: winptr = strchr(arg, ':');
994: if (winptr == NULL)
1.8 nicm 995: goto no_colon;
996: winptr++; /* skip : */
997: sessptr = xstrdup(arg);
998: *strchr(sessptr, ':') = '\0';
1.5 nicm 999:
1000: /* Try to lookup the session if present. */
1001: if (sessptr != NULL && *sessptr != '\0') {
1.8 nicm 1002: if ((s = cmd_lookup_session(sessptr, &ambiguous)) == NULL)
1.5 nicm 1003: goto no_session;
1004: }
1.1 nicm 1005: if (sp != NULL)
1006: *sp = s;
1007:
1.5 nicm 1008: /*
1.8 nicm 1009: * Then work out the window. An empty string is a new window otherwise
1010: * try to look it up in the session.
1.5 nicm 1011: */
1.8 nicm 1012: if (*winptr == '\0')
1.38 nicm 1013: idx = -1;
1014: else if (winptr[0] == '!' && winptr[1] == '\0') {
1015: if ((wl = TAILQ_FIRST(&s->lastw)) == NULL)
1016: goto not_found;
1017: idx = wl->idx;
1.41 nicm 1018: } else if (winptr[0] == '+' || winptr[0] == '-') {
1.43 nicm 1019: if ((idx = cmd_find_index_offset(winptr, s, &ambiguous)) < 0)
1.41 nicm 1020: goto invalid_index;
1021: } else if ((idx = cmd_lookup_index(s, winptr, &ambiguous)) == -1)
1022: goto invalid_index;
1.35 nicm 1023:
1.68 nicm 1024: free(sessptr);
1.5 nicm 1025: return (idx);
1026:
1.8 nicm 1027: no_colon:
1.38 nicm 1028: /*
1029: * No colon in the string, first try special cases, then as a window
1030: * and lastly as a session.
1031: */
1032: if (arg[0] == '!' && arg[1] == '\0') {
1033: if ((wl = TAILQ_FIRST(&s->lastw)) == NULL)
1034: goto not_found;
1035: idx = wl->idx;
1.41 nicm 1036: } else if (arg[0] == '+' || arg[0] == '-') {
1.43 nicm 1037: if ((idx = cmd_find_index_offset(arg, s, &ambiguous)) < 0)
1.41 nicm 1038: goto lookup_session;
1039: } else if ((idx = cmd_lookup_index(s, arg, &ambiguous)) == -1)
1040: goto lookup_session;
1.8 nicm 1041:
1042: if (sp != NULL)
1043: *sp = s;
1044:
1045: return (idx);
1046:
1.41 nicm 1047: lookup_session:
1048: if (ambiguous)
1049: goto not_found;
1.53 nicm 1050: if (*arg != '\0' && (s = cmd_lookup_session(arg, &ambiguous)) == NULL)
1.41 nicm 1051: goto no_session;
1052:
1053: if (sp != NULL)
1054: *sp = s;
1055:
1056: return (-1);
1057:
1.5 nicm 1058: no_session:
1059: if (ambiguous)
1.80 nicm 1060: cmdq_error(cmdq, "multiple sessions: %s", arg);
1.5 nicm 1061: else
1.80 nicm 1062: cmdq_error(cmdq, "session not found: %s", arg);
1.68 nicm 1063: free(sessptr);
1.5 nicm 1064: return (-2);
1065:
1.41 nicm 1066: invalid_index:
1067: if (ambiguous)
1068: goto not_found;
1.80 nicm 1069: cmdq_error(cmdq, "invalid index: %s", arg);
1.41 nicm 1070:
1.68 nicm 1071: free(sessptr);
1.41 nicm 1072: return (-2);
1073:
1.5 nicm 1074: not_found:
1075: if (ambiguous)
1.80 nicm 1076: cmdq_error(cmdq, "multiple windows: %s", arg);
1.1 nicm 1077: else
1.80 nicm 1078: cmdq_error(cmdq, "window not found: %s", arg);
1.68 nicm 1079: free(sessptr);
1.5 nicm 1080: return (-2);
1.12 nicm 1081: }
1082:
1.43 nicm 1083: int
1084: cmd_find_index_offset(const char *winptr, struct session *s, int *ambiguous)
1085: {
1086: int idx, offset = 1;
1087:
1088: if (winptr[1] != '\0')
1089: offset = strtonum(winptr + 1, 1, INT_MAX, NULL);
1090: if (offset == 0)
1091: idx = cmd_lookup_index(s, winptr, ambiguous);
1092: else {
1093: if (winptr[0] == '+') {
1094: if (s->curw->idx == INT_MAX)
1095: idx = cmd_lookup_index(s, winptr, ambiguous);
1096: else
1097: idx = s->curw->idx + offset;
1098: } else {
1099: if (s->curw->idx == 0)
1100: idx = cmd_lookup_index(s, winptr, ambiguous);
1101: else
1102: idx = s->curw->idx - offset;
1103: }
1104: }
1105:
1106: return (idx);
1107: }
1108:
1.12 nicm 1109: /*
1110: * Find the target session, window and pane number or report an error and
1111: * return NULL. The pane number is separated from the session:window by a .,
1112: * such as mysession:mywindow.0.
1113: */
1114: struct winlink *
1.80 nicm 1115: cmd_find_pane(struct cmd_q *cmdq,
1.12 nicm 1116: const char *arg, struct session **sp, struct window_pane **wpp)
1117: {
1.55 nicm 1118: struct session *s;
1119: struct winlink *wl;
1120: const char *period, *errstr;
1121: char *winptr, *paneptr;
1122: u_int idx;
1.12 nicm 1123:
1124: /* Get the current session. */
1.80 nicm 1125: if ((s = cmd_current_session(cmdq, 0)) == NULL) {
1126: cmdq_error(cmdq, "can't establish current session");
1.12 nicm 1127: return (NULL);
1128: }
1129: if (sp != NULL)
1130: *sp = s;
1131:
1132: /* A NULL argument means the current session, window and pane. */
1133: if (arg == NULL) {
1134: *wpp = s->curw->window->active;
1135: return (s->curw);
1.51 nicm 1136: }
1137:
1138: /* Lookup as pane id. */
1139: if ((*wpp = cmd_lookup_paneid(arg)) != NULL) {
1.80 nicm 1140: s = cmd_window_session(cmdq, (*wpp)->window, &wl);
1.51 nicm 1141: if (sp != NULL)
1142: *sp = s;
1143: return (wl);
1.12 nicm 1144: }
1145:
1146: /* Look for a separating period. */
1147: if ((period = strrchr(arg, '.')) == NULL)
1148: goto no_period;
1149:
1150: /* Pull out the window part and parse it. */
1151: winptr = xstrdup(arg);
1152: winptr[period - arg] = '\0';
1153: if (*winptr == '\0')
1154: wl = s->curw;
1.80 nicm 1155: else if ((wl = cmd_find_window(cmdq, winptr, sp)) == NULL)
1.12 nicm 1156: goto error;
1157:
1158: /* Find the pane section and look it up. */
1159: paneptr = winptr + (period - arg) + 1;
1160: if (*paneptr == '\0')
1161: *wpp = wl->window->active;
1.43 nicm 1162: else if (paneptr[0] == '+' || paneptr[0] == '-')
1163: *wpp = cmd_find_pane_offset(paneptr, wl);
1164: else {
1.12 nicm 1165: idx = strtonum(paneptr, 0, INT_MAX, &errstr);
1.36 nicm 1166: if (errstr != NULL)
1167: goto lookup_string;
1.12 nicm 1168: *wpp = window_pane_at_index(wl->window, idx);
1.36 nicm 1169: if (*wpp == NULL)
1170: goto lookup_string;
1.12 nicm 1171: }
1172:
1.68 nicm 1173: free(winptr);
1.12 nicm 1174: return (wl);
1175:
1.36 nicm 1176: lookup_string:
1177: /* Try pane string description. */
1.55 nicm 1178: if ((*wpp = window_find_string(wl->window, paneptr)) == NULL) {
1.80 nicm 1179: cmdq_error(cmdq, "can't find pane: %s", paneptr);
1.36 nicm 1180: goto error;
1181: }
1182:
1.68 nicm 1183: free(winptr);
1.39 nicm 1184: return (wl);
1.36 nicm 1185:
1.12 nicm 1186: no_period:
1187: /* Try as a pane number alone. */
1188: idx = strtonum(arg, 0, INT_MAX, &errstr);
1189: if (errstr != NULL)
1190: goto lookup_window;
1191:
1192: /* Try index in the current session and window. */
1193: if ((*wpp = window_pane_at_index(s->curw->window, idx)) == NULL)
1194: goto lookup_window;
1.35 nicm 1195:
1.12 nicm 1196: return (s->curw);
1197:
1198: lookup_window:
1.36 nicm 1199: /* Try pane string description. */
1.55 nicm 1200: if ((*wpp = window_find_string(s->curw->window, arg)) != NULL)
1.36 nicm 1201: return (s->curw);
1202:
1.12 nicm 1203: /* Try as a window and use the active pane. */
1.80 nicm 1204: if ((wl = cmd_find_window(cmdq, arg, sp)) != NULL)
1.12 nicm 1205: *wpp = wl->window->active;
1206: return (wl);
1.35 nicm 1207:
1.12 nicm 1208: error:
1.68 nicm 1209: free(winptr);
1.12 nicm 1210: return (NULL);
1.43 nicm 1211: }
1212:
1213: struct window_pane *
1214: cmd_find_pane_offset(const char *paneptr, struct winlink *wl)
1215: {
1216: struct window *w = wl->window;
1217: struct window_pane *wp = w->active;
1218: u_int offset = 1;
1219:
1220: if (paneptr[1] != '\0')
1221: offset = strtonum(paneptr + 1, 1, INT_MAX, NULL);
1222: if (offset > 0) {
1223: if (paneptr[0] == '+')
1224: wp = window_pane_next_by_number(w, wp, offset);
1225: else
1226: wp = window_pane_previous_by_number(w, wp, offset);
1227: }
1228:
1229: return (wp);
1.15 nicm 1230: }
1231:
1232: /* Replace the first %% or %idx in template by s. */
1233: char *
1.75 nicm 1234: cmd_template_replace(const char *template, const char *s, int idx)
1.15 nicm 1235: {
1.75 nicm 1236: char ch, *buf;
1237: const char *ptr;
1.84 nicm 1238: int replaced;
1239: size_t len;
1.15 nicm 1240:
1.76 nicm 1241: if (strchr(template, '%') == NULL)
1.15 nicm 1242: return (xstrdup(template));
1243:
1244: buf = xmalloc(1);
1245: *buf = '\0';
1246: len = 0;
1247: replaced = 0;
1248:
1249: ptr = template;
1250: while (*ptr != '\0') {
1251: switch (ch = *ptr++) {
1252: case '%':
1253: if (*ptr < '1' || *ptr > '9' || *ptr - '0' != idx) {
1254: if (*ptr != '%' || replaced)
1255: break;
1256: replaced = 1;
1257: }
1258: ptr++;
1259:
1260: len += strlen(s);
1261: buf = xrealloc(buf, 1, len + 1);
1262: strlcat(buf, s, len + 1);
1263: continue;
1264: }
1265: buf = xrealloc(buf, 1, len + 2);
1266: buf[len++] = ch;
1267: buf[len] = '\0';
1268: }
1269:
1270: return (buf);
1.1 nicm 1271: }