1 /* $OpenBSD$ */ 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 <pwd.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <unistd.h> 27 28 #include "tmux.h" 29 30 extern const struct cmd_entry cmd_attach_session_entry; 31 extern const struct cmd_entry cmd_bind_key_entry; 32 extern const struct cmd_entry cmd_break_pane_entry; 33 extern const struct cmd_entry cmd_capture_pane_entry; 34 extern const struct cmd_entry cmd_choose_buffer_entry; 35 extern const struct cmd_entry cmd_choose_client_entry; 36 extern const struct cmd_entry cmd_choose_session_entry; 37 extern const struct cmd_entry cmd_choose_tree_entry; 38 extern const struct cmd_entry cmd_choose_window_entry; 39 extern const struct cmd_entry cmd_clear_history_entry; 40 extern const struct cmd_entry cmd_clock_mode_entry; 41 extern const struct cmd_entry cmd_command_prompt_entry; 42 extern const struct cmd_entry cmd_confirm_before_entry; 43 extern const struct cmd_entry cmd_copy_mode_entry; 44 extern const struct cmd_entry cmd_delete_buffer_entry; 45 extern const struct cmd_entry cmd_detach_client_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_respawn_pane_entry; 86 extern const struct cmd_entry cmd_respawn_window_entry; 87 extern const struct cmd_entry cmd_rotate_window_entry; 88 extern const struct cmd_entry cmd_run_shell_entry; 89 extern const struct cmd_entry cmd_save_buffer_entry; 90 extern const struct cmd_entry cmd_select_layout_entry; 91 extern const struct cmd_entry cmd_select_pane_entry; 92 extern const struct cmd_entry cmd_select_window_entry; 93 extern const struct cmd_entry cmd_send_keys_entry; 94 extern const struct cmd_entry cmd_send_prefix_entry; 95 extern const struct cmd_entry cmd_server_info_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_session_entry, 127 &cmd_choose_tree_entry, 128 &cmd_choose_window_entry, 129 &cmd_clear_history_entry, 130 &cmd_clock_mode_entry, 131 &cmd_command_prompt_entry, 132 &cmd_confirm_before_entry, 133 &cmd_copy_mode_entry, 134 &cmd_delete_buffer_entry, 135 &cmd_detach_client_entry, 136 &cmd_display_message_entry, 137 &cmd_display_panes_entry, 138 &cmd_find_window_entry, 139 &cmd_has_session_entry, 140 &cmd_if_shell_entry, 141 &cmd_join_pane_entry, 142 &cmd_kill_pane_entry, 143 &cmd_kill_server_entry, 144 &cmd_kill_session_entry, 145 &cmd_kill_window_entry, 146 &cmd_last_pane_entry, 147 &cmd_last_window_entry, 148 &cmd_link_window_entry, 149 &cmd_list_buffers_entry, 150 &cmd_list_clients_entry, 151 &cmd_list_commands_entry, 152 &cmd_list_keys_entry, 153 &cmd_list_panes_entry, 154 &cmd_list_sessions_entry, 155 &cmd_list_windows_entry, 156 &cmd_load_buffer_entry, 157 &cmd_lock_client_entry, 158 &cmd_lock_server_entry, 159 &cmd_lock_session_entry, 160 &cmd_move_pane_entry, 161 &cmd_move_window_entry, 162 &cmd_new_session_entry, 163 &cmd_new_window_entry, 164 &cmd_next_layout_entry, 165 &cmd_next_window_entry, 166 &cmd_paste_buffer_entry, 167 &cmd_pipe_pane_entry, 168 &cmd_previous_layout_entry, 169 &cmd_previous_window_entry, 170 &cmd_refresh_client_entry, 171 &cmd_rename_session_entry, 172 &cmd_rename_window_entry, 173 &cmd_resize_pane_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_server_info_entry, 185 &cmd_set_buffer_entry, 186 &cmd_set_environment_entry, 187 &cmd_set_hook_entry, 188 &cmd_set_option_entry, 189 &cmd_set_window_option_entry, 190 &cmd_show_buffer_entry, 191 &cmd_show_environment_entry, 192 &cmd_show_hooks_entry, 193 &cmd_show_messages_entry, 194 &cmd_show_options_entry, 195 &cmd_show_window_options_entry, 196 &cmd_source_file_entry, 197 &cmd_split_window_entry, 198 &cmd_start_server_entry, 199 &cmd_suspend_client_entry, 200 &cmd_swap_pane_entry, 201 &cmd_swap_window_entry, 202 &cmd_switch_client_entry, 203 &cmd_unbind_key_entry, 204 &cmd_unlink_window_entry, 205 &cmd_wait_for_entry, 206 NULL 207 }; 208 209 int 210 cmd_pack_argv(int argc, char **argv, char *buf, size_t len) 211 { 212 size_t arglen; 213 int i; 214 215 if (argc == 0) 216 return (0); 217 218 *buf = '\0'; 219 for (i = 0; i < argc; i++) { 220 if (strlcpy(buf, argv[i], len) >= len) 221 return (-1); 222 arglen = strlen(argv[i]) + 1; 223 buf += arglen; 224 len -= arglen; 225 } 226 227 return (0); 228 } 229 230 int 231 cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv) 232 { 233 int i; 234 size_t arglen; 235 236 if (argc == 0) 237 return (0); 238 *argv = xcalloc(argc, sizeof **argv); 239 240 buf[len - 1] = '\0'; 241 for (i = 0; i < argc; i++) { 242 if (len == 0) { 243 cmd_free_argv(argc, *argv); 244 return (-1); 245 } 246 247 arglen = strlen(buf) + 1; 248 (*argv)[i] = xstrdup(buf); 249 buf += arglen; 250 len -= arglen; 251 } 252 253 return (0); 254 } 255 256 char ** 257 cmd_copy_argv(int argc, char **argv) 258 { 259 char **new_argv; 260 int i; 261 262 if (argc == 0) 263 return (NULL); 264 new_argv = xcalloc(argc + 1, sizeof *new_argv); 265 for (i = 0; i < argc; i++) { 266 if (argv[i] != NULL) 267 new_argv[i] = xstrdup(argv[i]); 268 } 269 return (new_argv); 270 } 271 272 void 273 cmd_free_argv(int argc, char **argv) 274 { 275 int i; 276 277 if (argc == 0) 278 return; 279 for (i = 0; i < argc; i++) 280 free(argv[i]); 281 free(argv); 282 } 283 284 char * 285 cmd_stringify_argv(int argc, char **argv) 286 { 287 char *buf; 288 int i; 289 size_t len; 290 291 if (argc == 0) 292 return (xstrdup("")); 293 294 len = 0; 295 buf = NULL; 296 297 for (i = 0; i < argc; i++) { 298 len += strlen(argv[i]) + 1; 299 buf = xrealloc(buf, len); 300 301 if (i == 0) 302 *buf = '\0'; 303 else 304 strlcat(buf, " ", len); 305 strlcat(buf, argv[i], len); 306 } 307 return (buf); 308 } 309 310 struct cmd * 311 cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause) 312 { 313 const struct cmd_entry **entryp, *entry; 314 struct cmd *cmd; 315 struct args *args; 316 char s[BUFSIZ]; 317 int ambiguous = 0; 318 319 *cause = NULL; 320 if (argc == 0) { 321 xasprintf(cause, "no command"); 322 return (NULL); 323 } 324 325 entry = NULL; 326 for (entryp = cmd_table; *entryp != NULL; entryp++) { 327 if ((*entryp)->alias != NULL && 328 strcmp((*entryp)->alias, argv[0]) == 0) { 329 ambiguous = 0; 330 entry = *entryp; 331 break; 332 } 333 334 if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0) 335 continue; 336 if (entry != NULL) 337 ambiguous = 1; 338 entry = *entryp; 339 340 /* Bail now if an exact match. */ 341 if (strcmp(entry->name, argv[0]) == 0) 342 break; 343 } 344 if (ambiguous) 345 goto ambiguous; 346 if (entry == NULL) { 347 xasprintf(cause, "unknown command: %s", argv[0]); 348 return (NULL); 349 } 350 351 args = args_parse(entry->args.template, argc, argv); 352 if (args == NULL) 353 goto usage; 354 if (entry->args.lower != -1 && args->argc < entry->args.lower) 355 goto usage; 356 if (entry->args.upper != -1 && args->argc > entry->args.upper) 357 goto usage; 358 359 cmd = xcalloc(1, sizeof *cmd); 360 cmd->entry = entry; 361 cmd->args = args; 362 363 if (file != NULL) 364 cmd->file = xstrdup(file); 365 cmd->line = line; 366 367 return (cmd); 368 369 ambiguous: 370 *s = '\0'; 371 for (entryp = cmd_table; *entryp != NULL; entryp++) { 372 if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0) 373 continue; 374 if (strlcat(s, (*entryp)->name, sizeof s) >= sizeof s) 375 break; 376 if (strlcat(s, ", ", sizeof s) >= sizeof s) 377 break; 378 } 379 s[strlen(s) - 2] = '\0'; 380 xasprintf(cause, "ambiguous command: %s, could be: %s", argv[0], s); 381 return (NULL); 382 383 usage: 384 if (args != NULL) 385 args_free(args); 386 xasprintf(cause, "usage: %s %s", entry->name, entry->usage); 387 return (NULL); 388 } 389 390 static int 391 cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag, 392 struct cmd_q *cmdq, struct cmd_q *parent) 393 { 394 int targetflags, error; 395 struct cmd_find_state *fs = NULL; 396 struct cmd_find_state *current = NULL; 397 struct cmd_find_state tmp; 398 399 if (flag == CMD_NONE || 400 flag == CMD_CLIENT || 401 flag == CMD_CLIENT_CANFAIL) 402 return (0); 403 404 if (c == 't') 405 fs = &cmdq->state.tflag; 406 else if (c == 's') 407 fs = &cmdq->state.sflag; 408 409 if (flag == CMD_SESSION_WITHPANE) { 410 if (target != NULL && target[strcspn(target, ":.")] != '\0') 411 flag = CMD_PANE; 412 else 413 flag = CMD_SESSION; 414 } 415 416 targetflags = 0; 417 switch (flag) { 418 case CMD_SESSION: 419 case CMD_SESSION_CANFAIL: 420 case CMD_SESSION_PREFERUNATTACHED: 421 if (flag == CMD_SESSION_CANFAIL) 422 targetflags |= CMD_FIND_QUIET; 423 if (flag == CMD_SESSION_PREFERUNATTACHED) 424 targetflags |= CMD_FIND_PREFER_UNATTACHED; 425 break; 426 case CMD_MOVEW_R: 427 flag = CMD_WINDOW_INDEX; 428 /* FALLTHROUGH */ 429 case CMD_WINDOW: 430 case CMD_WINDOW_CANFAIL: 431 case CMD_WINDOW_MARKED: 432 case CMD_WINDOW_INDEX: 433 if (flag == CMD_WINDOW_CANFAIL) 434 targetflags |= CMD_FIND_QUIET; 435 if (flag == CMD_WINDOW_MARKED) 436 targetflags |= CMD_FIND_DEFAULT_MARKED; 437 if (flag == CMD_WINDOW_INDEX) 438 targetflags |= CMD_FIND_WINDOW_INDEX; 439 break; 440 case CMD_PANE: 441 case CMD_PANE_CANFAIL: 442 case CMD_PANE_MARKED: 443 if (flag == CMD_PANE_CANFAIL) 444 targetflags |= CMD_FIND_QUIET; 445 if (flag == CMD_PANE_MARKED) 446 targetflags |= CMD_FIND_DEFAULT_MARKED; 447 break; 448 default: 449 fatalx("unknown %cflag %d", c, flag); 450 } 451 452 log_debug("%s: flag %c %d %#x", __func__, c, flag, targetflags); 453 if (parent != NULL) { 454 if (c == 't') 455 current = &parent->state.tflag; 456 else if (c == 's') 457 current = &parent->state.sflag; 458 } else { 459 error = cmd_find_current(&tmp, cmdq, targetflags); 460 if (error != 0 && ~targetflags & CMD_FIND_QUIET) 461 return (-1); 462 current = &tmp; 463 } 464 465 switch (flag) { 466 case CMD_NONE: 467 case CMD_CLIENT: 468 case CMD_CLIENT_CANFAIL: 469 return (0); 470 case CMD_SESSION: 471 case CMD_SESSION_CANFAIL: 472 case CMD_SESSION_PREFERUNATTACHED: 473 case CMD_SESSION_WITHPANE: 474 error = cmd_find_target(fs, current, cmdq, target, 475 CMD_FIND_SESSION, targetflags); 476 if (error != 0 && ~targetflags & CMD_FIND_QUIET) 477 return (-1); 478 break; 479 case CMD_MOVEW_R: 480 error = cmd_find_target(fs, current, cmdq, target, 481 CMD_FIND_SESSION, CMD_FIND_QUIET); 482 if (error == 0) 483 break; 484 /* FALLTHROUGH */ 485 case CMD_WINDOW: 486 case CMD_WINDOW_CANFAIL: 487 case CMD_WINDOW_MARKED: 488 case CMD_WINDOW_INDEX: 489 error = cmd_find_target(fs, current, cmdq, target, 490 CMD_FIND_WINDOW, targetflags); 491 if (error != 0 && ~targetflags & CMD_FIND_QUIET) 492 return (-1); 493 break; 494 case CMD_PANE: 495 case CMD_PANE_CANFAIL: 496 case CMD_PANE_MARKED: 497 error = cmd_find_target(fs, current, cmdq, target, 498 CMD_FIND_PANE, targetflags); 499 if (error != 0 && ~targetflags & CMD_FIND_QUIET) 500 return (-1); 501 break; 502 default: 503 fatalx("unknown %cflag %d", c, flag); 504 } 505 return (0); 506 } 507 508 int 509 cmd_prepare_state(struct cmd *cmd, struct cmd_q *cmdq, struct cmd_q *parent) 510 { 511 const struct cmd_entry *entry = cmd->entry; 512 struct cmd_state *state = &cmdq->state; 513 char *tmp; 514 enum cmd_entry_flag flag; 515 const char *s; 516 int error; 517 518 tmp = cmd_print(cmd); 519 log_debug("preparing state for %s (client %p)", tmp, cmdq->client); 520 free(tmp); 521 522 state->c = NULL; 523 cmd_find_clear_state(&state->tflag, NULL, 0); 524 cmd_find_clear_state(&state->sflag, NULL, 0); 525 526 flag = cmd->entry->cflag; 527 if (flag == CMD_NONE) { 528 flag = cmd->entry->tflag; 529 if (flag == CMD_CLIENT || flag == CMD_CLIENT_CANFAIL) 530 s = args_get(cmd->args, 't'); 531 else 532 s = NULL; 533 } else 534 s = args_get(cmd->args, 'c'); 535 switch (flag) { 536 case CMD_CLIENT: 537 state->c = cmd_find_client(cmdq, s, 0); 538 if (state->c == NULL) 539 return (-1); 540 break; 541 default: 542 state->c = cmd_find_client(cmdq, s, 1); 543 break; 544 } 545 546 s = args_get(cmd->args, 't'); 547 log_debug("preparing -t state: target %s", s == NULL ? "none" : s); 548 549 error = cmd_prepare_state_flag('t', s, entry->tflag, cmdq, parent); 550 if (error != 0) 551 return (error); 552 553 s = args_get(cmd->args, 's'); 554 log_debug("preparing -s state: target %s", s == NULL ? "none" : s); 555 556 error = cmd_prepare_state_flag('s', s, entry->sflag, cmdq, parent); 557 if (error != 0) 558 return (error); 559 560 return (0); 561 } 562 563 char * 564 cmd_print(struct cmd *cmd) 565 { 566 char *out, *s; 567 568 s = args_print(cmd->args); 569 if (*s != '\0') 570 xasprintf(&out, "%s %s", cmd->entry->name, s); 571 else 572 out = xstrdup(cmd->entry->name); 573 free(s); 574 575 return (out); 576 } 577 578 /* Adjust current mouse position for a pane. */ 579 int 580 cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp, 581 u_int *yp, int last) 582 { 583 u_int x, y; 584 585 if (last) { 586 x = m->lx; 587 y = m->ly; 588 } else { 589 x = m->x; 590 y = m->y; 591 } 592 593 if (m->statusat == 0 && y > 0) 594 y--; 595 else if (m->statusat > 0 && y >= (u_int)m->statusat) 596 y = m->statusat - 1; 597 598 if (x < wp->xoff || x >= wp->xoff + wp->sx) 599 return (-1); 600 if (y < wp->yoff || y >= wp->yoff + wp->sy) 601 return (-1); 602 603 *xp = x - wp->xoff; 604 *yp = y - wp->yoff; 605 return (0); 606 } 607 608 /* Get current mouse window if any. */ 609 struct winlink * 610 cmd_mouse_window(struct mouse_event *m, struct session **sp) 611 { 612 struct session *s; 613 struct window *w; 614 615 if (!m->valid || m->s == -1 || m->w == -1) 616 return (NULL); 617 if ((s = session_find_by_id(m->s)) == NULL) 618 return (NULL); 619 if ((w = window_find_by_id(m->w)) == NULL) 620 return (NULL); 621 622 if (sp != NULL) 623 *sp = s; 624 return (winlink_find_by_window(&s->windows, w)); 625 } 626 627 /* Get current mouse pane if any. */ 628 struct window_pane * 629 cmd_mouse_pane(struct mouse_event *m, struct session **sp, 630 struct winlink **wlp) 631 { 632 struct winlink *wl; 633 struct window_pane *wp; 634 635 if ((wl = cmd_mouse_window(m, sp)) == NULL) 636 return (NULL); 637 if ((wp = window_pane_find_by_id(m->wp)) == NULL) 638 return (NULL); 639 if (!window_has_pane(wl->window, wp)) 640 return (NULL); 641 642 if (wlp != NULL) 643 *wlp = wl; 644 return (wp); 645 } 646 647 /* Replace the first %% or %idx in template by s. */ 648 char * 649 cmd_template_replace(const char *template, const char *s, int idx) 650 { 651 char ch, *buf; 652 const char *ptr; 653 int replaced; 654 size_t len; 655 656 if (strchr(template, '%') == NULL) 657 return (xstrdup(template)); 658 659 buf = xmalloc(1); 660 *buf = '\0'; 661 len = 0; 662 replaced = 0; 663 664 ptr = template; 665 while (*ptr != '\0') { 666 switch (ch = *ptr++) { 667 case '%': 668 if (*ptr < '1' || *ptr > '9' || *ptr - '0' != idx) { 669 if (*ptr != '%' || replaced) 670 break; 671 replaced = 1; 672 } 673 ptr++; 674 675 len += strlen(s); 676 buf = xrealloc(buf, len + 1); 677 strlcat(buf, s, len + 1); 678 continue; 679 } 680 buf = xrealloc(buf, len + 2); 681 buf[len++] = ch; 682 buf[len] = '\0'; 683 } 684 685 return (buf); 686 } 687