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