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