1/* $OpenBSD: edit.c,v 1.71 2024/04/23 13:34:50 jsg Exp $ */ 2 3/* 4 * Command line editing - common code 5 * 6 */ 7 8#include "config.h" 9 10#include <sys/ioctl.h> 11#include <sys/stat.h> 12 13#include <ctype.h> 14#include <errno.h> 15#include <stdlib.h> 16#include <stdio.h> 17#include <string.h> 18#include <unistd.h> 19 20#include "sh.h" 21#include "edit.h" 22#include "tty.h" 23 24X_chars edchars; 25 26static void x_sigwinch(int); 27volatile sig_atomic_t got_sigwinch; 28static void check_sigwinch(void); 29 30static int x_file_glob(int, const char *, int, char ***); 31static int x_command_glob(int, const char *, int, char ***); 32static int x_locate_word(const char *, int, int, int *, int *); 33 34 35/* Called from main */ 36void 37x_init(void) 38{ 39 /* set to -2 to force initial binding */ 40 edchars.erase = edchars.kill = edchars.intr = edchars.quit = 41 edchars.eof = -2; 42 /* default value for deficient systems */ 43 edchars.werase = 027; /* ^W */ 44 45 if (setsig(&sigtraps[SIGWINCH], x_sigwinch, SS_RESTORE_ORIG|SS_SHTRAP)) 46 sigtraps[SIGWINCH].flags |= TF_SHELL_USES; 47 got_sigwinch = 1; /* force initial check */ 48 check_sigwinch(); 49 50#ifdef EMACS 51 x_init_emacs(); 52#endif /* EMACS */ 53} 54 55static void 56x_sigwinch(int sig) 57{ 58 got_sigwinch = 1; 59} 60 61static void 62check_sigwinch(void) 63{ 64 if (got_sigwinch) { 65 struct winsize ws; 66 67 got_sigwinch = 0; 68 if (procpid == kshpid && ioctl(tty_fd, TIOCGWINSZ, &ws) == 0) { 69 struct tbl *vp; 70 71 /* Do NOT export COLUMNS/LINES. Many applications 72 * check COLUMNS/LINES before checking ws.ws_col/row, 73 * so if the app is started with C/L in the environ 74 * and the window is then resized, the app won't 75 * see the change cause the environ doesn't change. 76 */ 77 if (ws.ws_col) { 78 x_cols = ws.ws_col < MIN_COLS ? MIN_COLS : 79 ws.ws_col; 80 81 if ((vp = typeset("COLUMNS", 0, 0, 0, 0))) 82 setint(vp, (int64_t) ws.ws_col); 83 } 84 if (ws.ws_row && (vp = typeset("LINES", 0, 0, 0, 0))) 85 setint(vp, (int64_t) ws.ws_row); 86 } 87 } 88} 89 90/* 91 * read an edited command line 92 */ 93int 94x_read(char *buf, size_t len) 95{ 96 int i; 97 98 x_mode(true); 99#ifdef EMACS 100 if (Flag(FEMACS) || Flag(FGMACS)) 101 i = x_emacs(buf, len); 102 else 103#endif 104#ifdef VI 105 if (Flag(FVI)) 106 i = x_vi(buf, len); 107 else 108#endif 109 i = -1; /* internal error */ 110 x_mode(false); 111 check_sigwinch(); 112 return i; 113} 114 115/* tty I/O */ 116 117int 118x_getc(void) 119{ 120 char c; 121 int n; 122 123 while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR) 124 if (trap) { 125 x_mode(false); 126 runtraps(0); 127 x_mode(true); 128 } 129 if (n != 1) 130 return -1; 131 return (int) (unsigned char) c; 132} 133 134void 135x_flush(void) 136{ 137 shf_flush(shl_out); 138} 139 140int 141x_putc(int c) 142{ 143 return shf_putc(c, shl_out); 144} 145 146void 147x_puts(const char *s) 148{ 149 while (*s != 0) 150 shf_putc(*s++, shl_out); 151} 152 153bool 154x_mode(bool onoff) 155{ 156 static bool x_cur_mode; 157 bool prev; 158 159 if (x_cur_mode == onoff) 160 return x_cur_mode; 161 prev = x_cur_mode; 162 x_cur_mode = onoff; 163 164 if (onoff) { 165 struct termios cb; 166 X_chars oldchars; 167 168 oldchars = edchars; 169 cb = tty_state; 170 171 edchars.erase = cb.c_cc[VERASE]; 172 edchars.kill = cb.c_cc[VKILL]; 173 edchars.intr = cb.c_cc[VINTR]; 174 edchars.quit = cb.c_cc[VQUIT]; 175 edchars.eof = cb.c_cc[VEOF]; 176 edchars.werase = cb.c_cc[VWERASE]; 177 cb.c_iflag &= ~(INLCR|ICRNL); 178 cb.c_lflag &= ~(ISIG|ICANON|ECHO); 179 /* osf/1 processes lnext when ~icanon */ 180 cb.c_cc[VLNEXT] = _POSIX_VDISABLE; 181 /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */ 182 cb.c_cc[VDISCARD] = _POSIX_VDISABLE; 183 cb.c_cc[VTIME] = 0; 184 cb.c_cc[VMIN] = 1; 185 186 tcsetattr(tty_fd, TCSADRAIN, &cb); 187 188 /* Convert unset values to internal `unset' value */ 189 if (edchars.erase == _POSIX_VDISABLE) 190 edchars.erase = -1; 191 if (edchars.kill == _POSIX_VDISABLE) 192 edchars.kill = -1; 193 if (edchars.intr == _POSIX_VDISABLE) 194 edchars.intr = -1; 195 if (edchars.quit == _POSIX_VDISABLE) 196 edchars.quit = -1; 197 if (edchars.eof == _POSIX_VDISABLE) 198 edchars.eof = -1; 199 if (edchars.werase == _POSIX_VDISABLE) 200 edchars.werase = -1; 201 if (memcmp(&edchars, &oldchars, sizeof(edchars)) != 0) { 202#ifdef EMACS 203 x_emacs_keys(&edchars); 204#endif 205 } 206 } else { 207 tcsetattr(tty_fd, TCSADRAIN, &tty_state); 208 } 209 210 return prev; 211} 212 213void 214set_editmode(const char *ed) 215{ 216 static const enum sh_flag edit_flags[] = { 217#ifdef EMACS 218 FEMACS, FGMACS, 219#endif 220#ifdef VI 221 FVI, 222#endif 223 }; 224 char *rcp; 225 unsigned int ele; 226 227 if ((rcp = strrchr(ed, '/'))) 228 ed = ++rcp; 229 for (ele = 0; ele < NELEM(edit_flags); ele++) 230 if (strstr(ed, sh_options[(int) edit_flags[ele]].name)) { 231 change_flag(edit_flags[ele], OF_SPECIAL, 1); 232 return; 233 } 234} 235 236/* ------------------------------------------------------------------------- */ 237/* Misc common code for vi/emacs */ 238 239/* Handle the commenting/uncommenting of a line. 240 * Returns: 241 * 1 if a carriage return is indicated (comment added) 242 * 0 if no return (comment removed) 243 * -1 if there is an error (not enough room for comment chars) 244 * If successful, *lenp contains the new length. Note: cursor should be 245 * moved to the start of the line after (un)commenting. 246 */ 247int 248x_do_comment(char *buf, int bsize, int *lenp) 249{ 250 int i, j; 251 int len = *lenp; 252 253 if (len == 0) 254 return 1; /* somewhat arbitrary - it's what at&t ksh does */ 255 256 /* Already commented? */ 257 if (buf[0] == '#') { 258 int saw_nl = 0; 259 260 for (j = 0, i = 1; i < len; i++) { 261 if (!saw_nl || buf[i] != '#') 262 buf[j++] = buf[i]; 263 saw_nl = buf[i] == '\n'; 264 } 265 *lenp = j; 266 return 0; 267 } else { 268 int n = 1; 269 270 /* See if there's room for the #'s - 1 per \n */ 271 for (i = 0; i < len; i++) 272 if (buf[i] == '\n') 273 n++; 274 if (len + n >= bsize) 275 return -1; 276 /* Now add them... */ 277 for (i = len, j = len + n; --i >= 0; ) { 278 if (buf[i] == '\n') 279 buf[--j] = '#'; 280 buf[--j] = buf[i]; 281 } 282 buf[0] = '#'; 283 *lenp += n; 284 return 1; 285 } 286} 287 288/* ------------------------------------------------------------------------- */ 289/* Common file/command completion code for vi/emacs */ 290 291 292static char *add_glob(const char *str, int slen); 293static void glob_table(const char *pat, XPtrV *wp, struct table *tp); 294static void glob_path(int flags, const char *pat, XPtrV *wp, 295 const char *path); 296 297static char * 298plain_fmt_entry(void *arg, int i, char *buf, int bsize) 299{ 300 const char *str = ((char *const *)arg)[i]; 301 char *buf0 = buf; 302 int ch; 303 304 if (buf == NULL || bsize <= 0) 305 internal_errorf("%s: buf %lx, bsize %d", 306 __func__, (long) buf, bsize); 307 308 while ((ch = (unsigned char)*str++) != '\0') { 309 if (iscntrl(ch)) { 310 if (bsize < 3) 311 break; 312 *buf++ = '^'; 313 *buf++ = UNCTRL(ch); 314 bsize -= 2; 315 continue; 316 } 317 if (bsize < 2) 318 break; 319 *buf++ = ch; 320 bsize--; 321 } 322 *buf = '\0'; 323 324 return buf0; 325} 326 327/* Compute the length of string taking into account escape characters. */ 328static size_t 329strlen_esc(const char *str) 330{ 331 size_t len = 0; 332 int ch; 333 334 while ((ch = (unsigned char)*str++) != '\0') { 335 if (iscntrl(ch)) 336 len++; 337 len++; 338 } 339 return len; 340} 341 342static int 343pr_list(char *const *ap) 344{ 345 char *const *pp; 346 int nwidth; 347 int i, n; 348 349 for (n = 0, nwidth = 0, pp = ap; *pp; n++, pp++) { 350 i = strlen_esc(*pp); 351 nwidth = (i > nwidth) ? i : nwidth; 352 } 353 print_columns(shl_out, n, plain_fmt_entry, (void *) ap, nwidth + 1, 0); 354 355 return n; 356} 357 358void 359x_print_expansions(int nwords, char *const *words, int is_command) 360{ 361 int prefix_len; 362 363 /* Check if all matches are in the same directory (in this 364 * case, we want to omit the directory name) 365 */ 366 if (!is_command && 367 (prefix_len = x_longest_prefix(nwords, words)) > 0) { 368 int i; 369 370 /* Special case for 1 match (prefix is whole word) */ 371 if (nwords == 1) 372 prefix_len = x_basename(words[0], NULL); 373 /* Any (non-trailing) slashes in non-common word suffixes? */ 374 for (i = 0; i < nwords; i++) 375 if (x_basename(words[i] + prefix_len, NULL) > 376 prefix_len) 377 break; 378 /* All in same directory? */ 379 if (i == nwords) { 380 XPtrV l; 381 382 while (prefix_len > 0 && words[0][prefix_len - 1] != '/') 383 prefix_len--; 384 XPinit(l, nwords + 1); 385 for (i = 0; i < nwords; i++) 386 XPput(l, words[i] + prefix_len); 387 XPput(l, NULL); 388 389 /* Enumerate expansions */ 390 x_putc('\r'); 391 x_putc('\n'); 392 pr_list((char **) XPptrv(l)); 393 394 XPfree(l); /* not x_free_words() */ 395 return; 396 } 397 } 398 399 /* Enumerate expansions */ 400 x_putc('\r'); 401 x_putc('\n'); 402 pr_list(words); 403} 404 405/* 406 * Do file globbing: 407 * - appends * to (copy of) str if no globbing chars found 408 * - does expansion, checks for no match, etc. 409 * - sets *wordsp to array of matching strings 410 * - returns number of matching strings 411 */ 412static int 413x_file_glob(int flags, const char *str, int slen, char ***wordsp) 414{ 415 char *toglob; 416 char **words; 417 int nwords; 418 XPtrV w; 419 struct source *s, *sold; 420 421 if (slen < 0) 422 return 0; 423 424 toglob = add_glob(str, slen); 425 426 /* 427 * Convert "foo*" (toglob) to an array of strings (words) 428 */ 429 sold = source; 430 s = pushs(SWSTR, ATEMP); 431 s->start = s->str = toglob; 432 source = s; 433 if (yylex(ONEWORD|UNESCAPE) != LWORD) { 434 source = sold; 435 internal_warningf("%s: substitute error", __func__); 436 return 0; 437 } 438 source = sold; 439 XPinit(w, 32); 440 expand(yylval.cp, &w, DOGLOB|DOTILDE|DOMARKDIRS); 441 XPput(w, NULL); 442 words = (char **) XPclose(w); 443 444 for (nwords = 0; words[nwords]; nwords++) 445 ; 446 if (nwords == 1) { 447 struct stat statb; 448 449 /* Check if file exists, also, check for empty 450 * result - happens if we tried to glob something 451 * which evaluated to an empty string (e.g., 452 * "$FOO" when there is no FOO, etc). 453 */ 454 if ((lstat(words[0], &statb) == -1) || 455 words[0][0] == '\0') { 456 x_free_words(nwords, words); 457 words = NULL; 458 nwords = 0; 459 } 460 } 461 afree(toglob, ATEMP); 462 463 if (nwords) { 464 *wordsp = words; 465 } else if (words) { 466 x_free_words(nwords, words); 467 *wordsp = NULL; 468 } 469 470 return nwords; 471} 472 473/* Data structure used in x_command_glob() */ 474struct path_order_info { 475 char *word; 476 int base; 477 int path_order; 478}; 479 480static int path_order_cmp(const void *aa, const void *bb); 481 482/* Compare routine used in x_command_glob() */ 483static int 484path_order_cmp(const void *aa, const void *bb) 485{ 486 const struct path_order_info *a = (const struct path_order_info *) aa; 487 const struct path_order_info *b = (const struct path_order_info *) bb; 488 int t; 489 490 t = strcmp(a->word + a->base, b->word + b->base); 491 return t ? t : a->path_order - b->path_order; 492} 493 494static int 495x_command_glob(int flags, const char *str, int slen, char ***wordsp) 496{ 497 char *toglob; 498 char *pat; 499 char *fpath; 500 int nwords; 501 XPtrV w; 502 struct block *l; 503 504 if (slen < 0) 505 return 0; 506 507 toglob = add_glob(str, slen); 508 509 /* Convert "foo*" (toglob) to a pattern for future use */ 510 pat = evalstr(toglob, DOPAT|DOTILDE); 511 afree(toglob, ATEMP); 512 513 XPinit(w, 32); 514 515 glob_table(pat, &w, &keywords); 516 glob_table(pat, &w, &aliases); 517 glob_table(pat, &w, &builtins); 518 for (l = genv->loc; l; l = l->next) 519 glob_table(pat, &w, &l->funs); 520 521 glob_path(flags, pat, &w, search_path); 522 if ((fpath = str_val(global("FPATH"))) != null) 523 glob_path(flags, pat, &w, fpath); 524 525 nwords = XPsize(w); 526 527 if (!nwords) { 528 *wordsp = NULL; 529 XPfree(w); 530 return 0; 531 } 532 533 /* Sort entries */ 534 if (flags & XCF_FULLPATH) { 535 /* Sort by basename, then path order */ 536 struct path_order_info *info; 537 struct path_order_info *last_info = NULL; 538 char **words = (char **) XPptrv(w); 539 int path_order = 0; 540 int i; 541 542 info = areallocarray(NULL, nwords, 543 sizeof(struct path_order_info), ATEMP); 544 545 for (i = 0; i < nwords; i++) { 546 info[i].word = words[i]; 547 info[i].base = x_basename(words[i], NULL); 548 if (!last_info || info[i].base != last_info->base || 549 strncmp(words[i], last_info->word, info[i].base) != 0) { 550 last_info = &info[i]; 551 path_order++; 552 } 553 info[i].path_order = path_order; 554 } 555 qsort(info, nwords, sizeof(struct path_order_info), 556 path_order_cmp); 557 for (i = 0; i < nwords; i++) 558 words[i] = info[i].word; 559 afree(info, ATEMP); 560 } else { 561 /* Sort and remove duplicate entries */ 562 char **words = (char **) XPptrv(w); 563 int i, j; 564 565 qsortp(XPptrv(w), (size_t) nwords, xstrcmp); 566 567 for (i = j = 0; i < nwords - 1; i++) { 568 if (strcmp(words[i], words[i + 1])) 569 words[j++] = words[i]; 570 else 571 afree(words[i], ATEMP); 572 } 573 words[j++] = words[i]; 574 nwords = j; 575 w.cur = (void **) &words[j]; 576 } 577 578 XPput(w, NULL); 579 *wordsp = (char **) XPclose(w); 580 581 return nwords; 582} 583 584#define IS_WORDC(c) !( ctype(c, C_LEX1) || (c) == '\'' || (c) == '"' || \ 585 (c) == '`' || (c) == '=' || (c) == ':' ) 586 587static int 588x_locate_word(const char *buf, int buflen, int pos, int *startp, 589 int *is_commandp) 590{ 591 int p; 592 int start, end; 593 594 /* Bad call? Probably should report error */ 595 if (pos < 0 || pos > buflen) { 596 *startp = pos; 597 *is_commandp = 0; 598 return 0; 599 } 600 /* The case where pos == buflen happens to take care of itself... */ 601 602 start = pos; 603 /* Keep going backwards to start of word (has effect of allowing 604 * one blank after the end of a word) 605 */ 606 for (; (start > 0 && IS_WORDC(buf[start - 1])) || 607 (start > 1 && buf[start-2] == '\\'); start--) 608 ; 609 /* Go forwards to end of word */ 610 for (end = start; end < buflen && IS_WORDC(buf[end]); end++) { 611 if (buf[end] == '\\' && (end+1) < buflen) 612 end++; 613 } 614 615 if (is_commandp) { 616 int iscmd; 617 618 /* Figure out if this is a command */ 619 for (p = start - 1; p >= 0 && isspace((unsigned char)buf[p]); 620 p--) 621 ; 622 iscmd = p < 0 || strchr(";|&()`", buf[p]); 623 if (iscmd) { 624 /* If command has a /, path, etc. is not searched; 625 * only current directory is searched, which is just 626 * like file globbing. 627 */ 628 for (p = start; p < end; p++) 629 if (buf[p] == '/') 630 break; 631 iscmd = p == end; 632 } 633 *is_commandp = iscmd; 634 } 635 636 *startp = start; 637 638 return end - start; 639} 640 641static int 642x_try_array(const char *buf, int buflen, const char *want, int wantlen, 643 int *nwords, char ***words) 644{ 645 const char *cmd, *cp; 646 int cmdlen, n, i, slen; 647 char *name, *s; 648 struct tbl *v, *vp; 649 650 *nwords = 0; 651 *words = NULL; 652 653 /* Walk back to find start of command. */ 654 if (want == buf) 655 return 0; 656 for (cmd = want; cmd > buf; cmd--) { 657 if (strchr(";|&()`", cmd[-1]) != NULL) 658 break; 659 } 660 while (cmd < want && isspace((u_char)*cmd)) 661 cmd++; 662 cmdlen = 0; 663 while (cmd + cmdlen < want && !isspace((u_char)cmd[cmdlen])) 664 cmdlen++; 665 for (i = 0; i < cmdlen; i++) { 666 if (!isalnum((u_char)cmd[i]) && cmd[i] != '_') 667 return 0; 668 } 669 670 /* Take a stab at argument count from here. */ 671 n = 1; 672 for (cp = cmd + cmdlen + 1; cp < want; cp++) { 673 if (!isspace((u_char)cp[-1]) && isspace((u_char)*cp)) 674 n++; 675 } 676 677 /* Try to find the array. */ 678 if (asprintf(&name, "complete_%.*s_%d", cmdlen, cmd, n) == -1) 679 internal_errorf("unable to allocate memory"); 680 v = global(name); 681 free(name); 682 if (~v->flag & (ISSET|ARRAY)) { 683 if (asprintf(&name, "complete_%.*s", cmdlen, cmd) == -1) 684 internal_errorf("unable to allocate memory"); 685 v = global(name); 686 free(name); 687 if (~v->flag & (ISSET|ARRAY)) 688 return 0; 689 } 690 691 /* Walk the array and build words list. */ 692 for (vp = v; vp; vp = vp->u.array) { 693 if (~vp->flag & ISSET) 694 continue; 695 696 s = str_val(vp); 697 slen = strlen(s); 698 699 if (slen < wantlen) 700 continue; 701 if (slen > wantlen) 702 slen = wantlen; 703 if (slen != 0 && strncmp(s, want, slen) != 0) 704 continue; 705 706 *words = areallocarray(*words, (*nwords) + 2, sizeof **words, 707 ATEMP); 708 (*words)[(*nwords)++] = str_save(s, ATEMP); 709 } 710 if (*nwords != 0) 711 (*words)[*nwords] = NULL; 712 713 return *nwords != 0; 714} 715 716int 717x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp, 718 int *endp, char ***wordsp, int *is_commandp) 719{ 720 int len; 721 int nwords; 722 char **words = NULL; 723 int is_command; 724 725 len = x_locate_word(buf, buflen, pos, startp, &is_command); 726 if (!(flags & XCF_COMMAND)) 727 is_command = 0; 728 /* Don't do command globing on zero length strings - it takes too 729 * long and isn't very useful. File globs are more likely to be 730 * useful, so allow these. 731 */ 732 if (len == 0 && is_command) 733 return 0; 734 735 if (is_command) 736 nwords = x_command_glob(flags, buf + *startp, len, &words); 737 else if (!x_try_array(buf, buflen, buf + *startp, len, &nwords, &words)) 738 nwords = x_file_glob(flags, buf + *startp, len, &words); 739 if (nwords == 0) { 740 *wordsp = NULL; 741 return 0; 742 } 743 744 if (is_commandp) 745 *is_commandp = is_command; 746 *wordsp = words; 747 *endp = *startp + len; 748 749 return nwords; 750} 751 752/* Given a string, copy it and possibly add a '*' to the end. The 753 * new string is returned. 754 */ 755static char * 756add_glob(const char *str, int slen) 757{ 758 char *toglob; 759 char *s; 760 bool saw_slash = false; 761 762 if (slen < 0) 763 return NULL; 764 765 toglob = str_nsave(str, slen + 1, ATEMP); /* + 1 for "*" */ 766 toglob[slen] = '\0'; 767 768 /* 769 * If the pathname contains a wildcard (an unquoted '*', 770 * '?', or '[') or parameter expansion ('$'), or a ~username 771 * with no trailing slash, then it is globbed based on that 772 * value (i.e., without the appended '*'). 773 */ 774 for (s = toglob; *s; s++) { 775 if (*s == '\\' && s[1]) 776 s++; 777 else if (*s == '*' || *s == '[' || *s == '?' || *s == '$' || 778 (s[1] == '(' /*)*/ && strchr("+@!", *s))) 779 break; 780 else if (*s == '/') 781 saw_slash = true; 782 } 783 if (!*s && (*toglob != '~' || saw_slash)) { 784 toglob[slen] = '*'; 785 toglob[slen + 1] = '\0'; 786 } 787 788 return toglob; 789} 790 791/* 792 * Find longest common prefix 793 */ 794int 795x_longest_prefix(int nwords, char *const *words) 796{ 797 int i, j; 798 int prefix_len; 799 char *p; 800 801 if (nwords <= 0) 802 return 0; 803 804 prefix_len = strlen(words[0]); 805 for (i = 1; i < nwords; i++) 806 for (j = 0, p = words[i]; j < prefix_len; j++) 807 if (p[j] != words[0][j]) { 808 prefix_len = j; 809 break; 810 } 811 return prefix_len; 812} 813 814void 815x_free_words(int nwords, char **words) 816{ 817 int i; 818 819 for (i = 0; i < nwords; i++) 820 afree(words[i], ATEMP); 821 afree(words, ATEMP); 822} 823 824/* Return the offset of the basename of string s (which ends at se - need not 825 * be null terminated). Trailing slashes are ignored. If s is just a slash, 826 * then the offset is 0 (actually, length - 1). 827 * s Return 828 * /etc 1 829 * /etc/ 1 830 * /etc// 1 831 * /etc/fo 5 832 * foo 0 833 * /// 2 834 * 0 835 */ 836int 837x_basename(const char *s, const char *se) 838{ 839 const char *p; 840 841 if (se == NULL) 842 se = s + strlen(s); 843 if (s == se) 844 return 0; 845 846 /* Skip trailing slashes */ 847 for (p = se - 1; p > s && *p == '/'; p--) 848 ; 849 for (; p > s && *p != '/'; p--) 850 ; 851 if (*p == '/' && p + 1 < se) 852 p++; 853 854 return p - s; 855} 856 857/* 858 * Apply pattern matching to a table: all table entries that match a pattern 859 * are added to wp. 860 */ 861static void 862glob_table(const char *pat, XPtrV *wp, struct table *tp) 863{ 864 struct tstate ts; 865 struct tbl *te; 866 867 for (ktwalk(&ts, tp); (te = ktnext(&ts)); ) { 868 if (gmatch(te->name, pat, false)) 869 XPput(*wp, str_save(te->name, ATEMP)); 870 } 871} 872 873static void 874glob_path(int flags, const char *pat, XPtrV *wp, const char *path) 875{ 876 const char *sp, *p; 877 char *xp; 878 int staterr; 879 int pathlen; 880 int patlen; 881 int oldsize, newsize, i, j; 882 char **words; 883 XString xs; 884 885 patlen = strlen(pat) + 1; 886 sp = path; 887 Xinit(xs, xp, patlen + 128, ATEMP); 888 while (sp) { 889 xp = Xstring(xs, xp); 890 if (!(p = strchr(sp, ':'))) 891 p = sp + strlen(sp); 892 pathlen = p - sp; 893 if (pathlen) { 894 /* Copy sp into xp, stuffing any MAGIC characters 895 * on the way 896 */ 897 const char *s = sp; 898 899 XcheckN(xs, xp, pathlen * 2); 900 while (s < p) { 901 if (ISMAGIC(*s)) 902 *xp++ = MAGIC; 903 *xp++ = *s++; 904 } 905 *xp++ = '/'; 906 pathlen++; 907 } 908 sp = p; 909 XcheckN(xs, xp, patlen); 910 memcpy(xp, pat, patlen); 911 912 oldsize = XPsize(*wp); 913 glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */ 914 newsize = XPsize(*wp); 915 916 /* Check that each match is executable... */ 917 words = (char **) XPptrv(*wp); 918 for (i = j = oldsize; i < newsize; i++) { 919 staterr = 0; 920 if ((search_access(words[i], X_OK, &staterr) >= 0) || 921 (staterr == EISDIR)) { 922 words[j] = words[i]; 923 if (!(flags & XCF_FULLPATH)) 924 memmove(words[j], words[j] + pathlen, 925 strlen(words[j] + pathlen) + 1); 926 j++; 927 } else 928 afree(words[i], ATEMP); 929 } 930 wp->cur = (void **) &words[j]; 931 932 if (!*sp++) 933 break; 934 } 935 Xfree(xs, xp); 936} 937 938/* 939 * if argument string contains any special characters, they will 940 * be escaped and the result will be put into edit buffer by 941 * keybinding-specific function 942 */ 943int 944x_escape(const char *s, size_t len, int (*putbuf_func) (const char *, size_t)) 945{ 946 size_t add, wlen; 947 const char *ifs = str_val(local("IFS", 0)); 948 int rval = 0; 949 950 for (add = 0, wlen = len; wlen - add > 0; add++) { 951 if (strchr("!\"#$&'()*:;<=>?[\\]`{|}", s[add]) || 952 strchr(ifs, s[add])) { 953 if (putbuf_func(s, add) != 0) { 954 rval = -1; 955 break; 956 } 957 958 putbuf_func("\\", 1); 959 putbuf_func(&s[add], 1); 960 961 add++; 962 wlen -= add; 963 s += add; 964 add = -1; /* after the increment it will go to 0 */ 965 } 966 } 967 if (wlen > 0 && rval == 0) 968 rval = putbuf_func(s, wlen); 969 970 return (rval); 971} 972