1 /* $Header: /src/pub/tcsh/tw.parse.c,v 3.92 2002/06/25 19:02:12 christos Exp $ */ 2 /* 3 * tw.parse.c: Everyone has taken a shot in this futile effort to 4 * lexically analyze a csh line... Well we cannot good 5 * a job as good as sh.lex.c; but we try. Amazing that 6 * it works considering how many hands have touched this code 7 */ 8 /*- 9 * Copyright (c) 1980, 1991 The Regents of the University of California. 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 #include "sh.h" 37 38 RCSID("$Id: tw.parse.c,v 3.92 2002/06/25 19:02:12 christos Exp $") 39 40 #include "tw.h" 41 #include "ed.h" 42 #include "tc.h" 43 44 #ifdef WINNT_NATIVE 45 #include "nt.const.h" 46 #endif /* WINNT_NATIVE */ 47 #define EVEN(x) (((x) & 1) != 1) 48 49 #define DOT_NONE 0 /* Don't display dot files */ 50 #define DOT_NOT 1 /* Don't display dot or dot-dot */ 51 #define DOT_ALL 2 /* Display all dot files */ 52 53 /* TW_NONE, TW_COMMAND, TW_VARIABLE, TW_LOGNAME, */ 54 /* TW_FILE, TW_DIRECTORY, TW_VARLIST, TW_USER, */ 55 /* TW_COMPLETION, TW_ALIAS, TW_SHELLVAR, TW_ENVVAR, */ 56 /* TW_BINDING, TW_WORDLIST, TW_LIMIT, TW_SIGNAL */ 57 /* TW_JOB, TW_EXPLAIN, TW_TEXT, TW_GRPNAME */ 58 static void (*tw_start_entry[]) __P((DIR *, Char *)) = { 59 tw_file_start, tw_cmd_start, tw_var_start, tw_logname_start, 60 tw_file_start, tw_file_start, tw_vl_start, tw_logname_start, 61 tw_complete_start, tw_alias_start, tw_var_start, tw_var_start, 62 tw_bind_start, tw_wl_start, tw_limit_start, tw_sig_start, 63 tw_job_start, tw_file_start, tw_file_start, tw_grpname_start 64 }; 65 66 static Char * (*tw_next_entry[]) __P((Char *, int *)) = { 67 tw_file_next, tw_cmd_next, tw_var_next, tw_logname_next, 68 tw_file_next, tw_file_next, tw_var_next, tw_logname_next, 69 tw_var_next, tw_var_next, tw_shvar_next, tw_envvar_next, 70 tw_bind_next, tw_wl_next, tw_limit_next, tw_sig_next, 71 tw_job_next, tw_file_next, tw_file_next, tw_grpname_next 72 }; 73 74 static void (*tw_end_entry[]) __P((void)) = { 75 tw_dir_end, tw_dir_end, tw_dir_end, tw_logname_end, 76 tw_dir_end, tw_dir_end, tw_dir_end, tw_logname_end, 77 tw_dir_end, tw_dir_end, tw_dir_end, tw_dir_end, 78 tw_dir_end, tw_dir_end, tw_dir_end, tw_dir_end, 79 tw_dir_end, tw_dir_end, tw_dir_end, tw_grpname_end 80 }; 81 82 /* #define TDEBUG */ 83 84 /* Set to TRUE if recexact is set and an exact match is found 85 * along with other, longer, matches. 86 */ 87 88 int curchoice = -1; 89 90 int match_unique_match = FALSE; 91 int non_unique_match = FALSE; 92 static bool SearchNoDirErr = 0; /* t_search returns -2 if dir is unreadable */ 93 94 /* state so if a completion is interrupted, the input line doesn't get 95 nuked */ 96 int InsideCompletion = 0; 97 98 /* do the expand or list on the command line -- SHOULD BE REPLACED */ 99 100 extern Char NeedsRedraw; /* from ed.h */ 101 extern int Tty_raw_mode; 102 extern int TermH; /* from the editor routines */ 103 extern int lbuffed; /* from sh.print.c */ 104 105 static void extract_dir_and_name __P((Char *, Char *, Char *)); 106 static int insert_meta __P((Char *, Char *, Char *, bool)); 107 static Char *tilde __P((Char *, Char *)); 108 #ifndef __MVS__ 109 static int expand_dir __P((Char *, Char *, DIR **, COMMAND)); 110 #endif 111 static bool nostat __P((Char *)); 112 static Char filetype __P((Char *, Char *)); 113 static int t_glob __P((Char ***, int)); 114 static int c_glob __P((Char ***)); 115 static int is_prefix __P((Char *, Char *)); 116 static int is_prefixmatch __P((Char *, Char *, int)); 117 static int is_suffix __P((Char *, Char *)); 118 static int recognize __P((Char *, Char *, int, int, int)); 119 static int ignored __P((Char *)); 120 static int isadirectory __P((Char *, Char *)); 121 #ifndef __MVS__ 122 static int tw_collect_items __P((COMMAND, int, Char *, Char *, 123 Char *, Char *, int)); 124 static int tw_collect __P((COMMAND, int, Char *, Char *, 125 Char **, Char *, int, DIR *)); 126 #endif 127 static Char tw_suffix __P((int, Char *, Char *, Char *, 128 Char *)); 129 static void tw_fixword __P((int, Char *, Char *, Char *, int)); 130 static void tw_list_items __P((int, int, int)); 131 static void add_scroll_tab __P((Char *)); 132 static void choose_scroll_tab __P((Char **, int)); 133 static void free_scroll_tab __P((void)); 134 static int find_rows __P((Char *[], int, int)); 135 136 #ifdef notdef 137 /* 138 * If we find a set command, then we break a=b to a= and word becomes 139 * b else, we don't break a=b. [don't use that; splits words badly and 140 * messes up tw_complete()] 141 */ 142 #define isaset(c, w) ((w)[-1] == '=' && \ 143 ((c)[0] == 's' && (c)[1] == 'e' && (c)[2] == 't' && \ 144 ((c[3] == ' ' || (c)[3] == '\t')))) 145 #endif 146 147 #define QLINESIZE (INBUFSIZE + 1) 148 149 /* TRUE if character must be quoted */ 150 #define tricky(w) (cmap(w, _META | _DOL | _QF | _QB | _ESC | _GLOB) && w != '#') 151 /* TRUE if double quotes don't protect character */ 152 #define tricky_dq(w) (cmap(w, _DOL | _QB)) 153 154 /* tenematch(): 155 * Return: 156 * > 1: No. of items found 157 * = 1: Exactly one match / spelling corrected 158 * = 0: No match / spelling was correct 159 * < 0: Error (incl spelling correction impossible) 160 */ 161 int 162 tenematch(inputline, num_read, command) 163 Char *inputline; /* match string prefix */ 164 int num_read; /* # actually in inputline */ 165 COMMAND command; /* LIST or RECOGNIZE or PRINT_HELP */ 166 167 { 168 Char qline[QLINESIZE]; 169 Char qu = 0, *pat = STRNULL; 170 Char *str_end, *cp, *wp, *wordp; 171 Char *cmd_start, *word_start, *word; 172 Char *ocmd_start = NULL, *oword_start = NULL, *oword = NULL; 173 int suf = 0; 174 int space_left; 175 int looking; /* what we are looking for */ 176 int search_ret; /* what search returned for debugging */ 177 int backq = 0; 178 179 if (num_read > QLINESIZE - 1) 180 return -1; 181 str_end = &inputline[num_read]; 182 183 word_start = inputline; 184 word = cmd_start = wp = qline; 185 for (cp = inputline; cp < str_end; cp++) { 186 if (!cmap(qu, _ESC)) { 187 if (cmap(*cp, _QF|_ESC)) { 188 if (qu == 0 || qu == *cp) { 189 qu ^= *cp; 190 continue; 191 } 192 } 193 if (qu != '\'' && cmap(*cp, _QB)) { 194 if ((backq ^= 1) != 0) { 195 ocmd_start = cmd_start; 196 oword_start = word_start; 197 oword = word; 198 word_start = cp + 1; 199 word = cmd_start = wp + 1; 200 } 201 else { 202 cmd_start = ocmd_start; 203 word_start = oword_start; 204 word = oword; 205 } 206 *wp++ = *cp; 207 continue; 208 } 209 } 210 if (iscmdmeta(*cp)) 211 cmd_start = wp + 1; 212 213 /* Don't quote '/' to make the recognize stuff work easily */ 214 /* Don't quote '$' in double quotes */ 215 216 if (cmap(*cp, _ESC) && cp < str_end - 1 && cp[1] == HIST) 217 *wp = *++cp | QUOTE; 218 else if (qu && (tricky(*cp) || *cp == '~') && !(qu == '\"' && tricky_dq(*cp))) 219 *wp = *cp | QUOTE; 220 else 221 *wp = *cp; 222 if (ismetahash(*wp) /* || isaset(cmd_start, wp + 1) */) 223 word = wp + 1, word_start = cp + 1; 224 wp++; 225 if (cmap(qu, _ESC)) 226 qu = 0; 227 } 228 *wp = 0; 229 230 #ifdef masscomp 231 /* 232 * Avoid a nasty message from the RTU 4.1A & RTU 5.0 compiler concerning 233 * the "overuse of registers". According to the compiler release notes, 234 * incorrect code may be produced unless the offending expression is 235 * rewritten. Therefore, we can't just ignore it, DAS DEC-90. 236 */ 237 space_left = QLINESIZE - 1; 238 space_left -= word - qline; 239 #else 240 space_left = QLINESIZE - 1 - (int) (word - qline); 241 #endif 242 243 /* 244 * SPECIAL HARDCODED COMPLETIONS: 245 * first word of command -> TW_COMMAND 246 * everything else -> TW_ZERO 247 * 248 */ 249 looking = starting_a_command(word - 1, qline) ? 250 TW_COMMAND : TW_ZERO; 251 252 wordp = word; 253 254 #ifdef TDEBUG 255 xprintf(CGETS(30, 1, "starting_a_command %d\n"), looking); 256 xprintf("\ncmd_start:%S:\n", cmd_start); 257 xprintf("qline:%S:\n", qline); 258 xprintf("qline:"); 259 for (wp = qline; *wp; wp++) 260 xprintf("%c", *wp & QUOTE ? '-' : ' '); 261 xprintf(":\n"); 262 xprintf("word:%S:\n", word); 263 xprintf("word:"); 264 /* Must be last, so wp is still pointing to the end of word */ 265 for (wp = word; *wp; wp++) 266 xprintf("%c", *wp & QUOTE ? '-' : ' '); 267 xprintf(":\n"); 268 #endif 269 270 if ((looking == TW_COMMAND || looking == TW_ZERO) && 271 (command == RECOGNIZE || command == LIST || command == SPELL || 272 command == RECOGNIZE_SCROLL)) { 273 #ifdef TDEBUG 274 xprintf(CGETS(30, 2, "complete %d "), looking); 275 #endif 276 looking = tw_complete(cmd_start, &wordp, &pat, looking, &suf); 277 #ifdef TDEBUG 278 xprintf(CGETS(30, 3, "complete %d %S\n"), looking, pat); 279 #endif 280 } 281 282 switch (command) { 283 Char buffer[FILSIZ + 1], *bptr; 284 Char *slshp; 285 Char *items[2], **ptr; 286 int i, count; 287 288 case RECOGNIZE: 289 case RECOGNIZE_SCROLL: 290 case RECOGNIZE_ALL: 291 if (adrof(STRautocorrect)) { 292 if ((slshp = Strrchr(wordp, '/')) != NULL && slshp[1] != '\0') { 293 SearchNoDirErr = 1; 294 for (bptr = wordp; bptr < slshp; bptr++) { 295 /* 296 * do not try to correct spelling of words containing 297 * globbing characters 298 */ 299 if (isglob(*bptr)) { 300 SearchNoDirErr = 0; 301 break; 302 } 303 } 304 } 305 } 306 else 307 slshp = STRNULL; 308 search_ret = t_search(wordp, wp, command, space_left, looking, 1, 309 pat, suf); 310 SearchNoDirErr = 0; 311 312 if (search_ret == -2) { 313 Char rword[FILSIZ + 1]; 314 315 (void) Strcpy(rword, slshp); 316 if (slshp != STRNULL) 317 *slshp = '\0'; 318 search_ret = spell_me(wordp, QLINESIZE - (wordp - qline), looking, 319 pat, suf); 320 if (search_ret == 1) { 321 (void) Strcat(wordp, rword); 322 wp = wordp + (int) Strlen(wordp); 323 search_ret = t_search(wordp, wp, command, space_left, 324 looking, 1, pat, suf); 325 } 326 } 327 if (*wp && insert_meta(word_start, str_end, word, !qu) < 0) 328 return -1; /* error inserting */ 329 return search_ret; 330 331 case SPELL: 332 for (bptr = word_start; bptr < str_end; bptr++) { 333 /* 334 * do not try to correct spelling of words containing globbing 335 * characters 336 */ 337 if (isglob(*bptr)) 338 return 0; 339 } 340 search_ret = spell_me(wordp, QLINESIZE - (wordp - qline), looking, 341 pat, suf); 342 if (search_ret == 1) { 343 if (insert_meta(word_start, str_end, word, !qu) < 0) 344 return -1; /* error inserting */ 345 } 346 return search_ret; 347 348 case PRINT_HELP: 349 do_help(cmd_start); 350 return 1; 351 352 case GLOB: 353 case GLOB_EXPAND: 354 (void) Strncpy(buffer, wordp, FILSIZ + 1); 355 items[0] = buffer; 356 items[1] = NULL; 357 ptr = items; 358 count = (looking == TW_COMMAND && Strchr(wordp, '/') == 0) ? 359 c_glob(&ptr) : 360 t_glob(&ptr, looking == TW_COMMAND); 361 if (count > 0) { 362 if (command == GLOB) 363 print_by_column(STRNULL, ptr, count, 0); 364 else { 365 DeleteBack(str_end - word_start);/* get rid of old word */ 366 for (i = 0; i < count; i++) 367 if (ptr[i] && *ptr[i]) { 368 (void) quote(ptr[i]); 369 if (insert_meta(0, 0, ptr[i], 0) < 0 || 370 InsertStr(STRspace) < 0) { 371 blkfree(ptr); 372 return -1; /* error inserting */ 373 } 374 } 375 } 376 blkfree(ptr); 377 } 378 return count; 379 380 case VARS_EXPAND: 381 if (dollar(buffer, word)) { 382 if (insert_meta(word_start, str_end, buffer, !qu) < 0) 383 return -1; /* error inserting */ 384 return 1; 385 } 386 return 0; 387 388 case PATH_NORMALIZE: 389 if ((bptr = dnormalize(wordp, symlinks == SYM_IGNORE || 390 symlinks == SYM_EXPAND)) != NULL) { 391 (void) Strcpy(buffer, bptr); 392 xfree((ptr_t) bptr); 393 if (insert_meta(word_start, str_end, buffer, !qu) < 0) 394 return -1; /* error inserting */ 395 return 1; 396 } 397 return 0; 398 399 case COMMAND_NORMALIZE: 400 if (!cmd_expand(wordp, buffer)) 401 return 0; 402 if (insert_meta(word_start, str_end, buffer, !qu) < 0) 403 return -1; /* error inserting */ 404 return 1; 405 406 case LIST: 407 case LIST_ALL: 408 search_ret = t_search(wordp, wp, LIST, space_left, looking, 1, 409 pat, suf); 410 return search_ret; 411 412 default: 413 xprintf(CGETS(30, 4, "%s: Internal match error.\n"), progname); 414 return 1; 415 416 } 417 } /* end tenematch */ 418 419 420 /* t_glob(): 421 * Return a list of files that match the pattern 422 */ 423 static int 424 t_glob(v, cmd) 425 register Char ***v; 426 int cmd; 427 { 428 jmp_buf_t osetexit; 429 430 if (**v == 0) 431 return (0); 432 gflag = 0, tglob(*v); 433 if (gflag) { 434 getexit(osetexit); /* make sure to come back here */ 435 if (setexit() == 0) 436 *v = globall(*v); 437 resexit(osetexit); 438 gargv = 0; 439 if (haderr) { 440 haderr = 0; 441 NeedsRedraw = 1; 442 return (-1); 443 } 444 if (*v == 0) 445 return (0); 446 } 447 else 448 return (0); 449 450 if (cmd) { 451 Char **av = *v, *p; 452 int fwd, i, ac = gargc; 453 454 for (i = 0, fwd = 0; i < ac; i++) 455 if (!executable(NULL, av[i], 0)) { 456 fwd++; 457 p = av[i]; 458 av[i] = NULL; 459 xfree((ptr_t) p); 460 } 461 else if (fwd) 462 av[i - fwd] = av[i]; 463 464 if (fwd) 465 av[i - fwd] = av[i]; 466 gargc -= fwd; 467 av[gargc] = NULL; 468 } 469 470 return (gargc); 471 } /* end t_glob */ 472 473 474 /* c_glob(): 475 * Return a list of commands that match the pattern 476 */ 477 static int 478 c_glob(v) 479 register Char ***v; 480 { 481 Char *pat = **v, *cmd, **av; 482 Char dir[MAXPATHLEN+1]; 483 int flag, at, ac; 484 485 if (pat == NULL) 486 return (0); 487 488 ac = 0; 489 at = 10; 490 av = (Char **) xmalloc((size_t) (at * sizeof(Char *))); 491 av[ac] = NULL; 492 493 tw_cmd_start(NULL, NULL); 494 while ((cmd = tw_cmd_next(dir, &flag)) != NULL) 495 if (Gmatch(cmd, pat)) { 496 if (ac + 1 >= at) { 497 at += 10; 498 av = (Char **) xrealloc((ptr_t) av, 499 (size_t) (at * sizeof(Char *))); 500 } 501 av[ac++] = Strsave(cmd); 502 av[ac] = NULL; 503 } 504 tw_dir_end(); 505 *v = av; 506 507 return (ac); 508 } /* end c_glob */ 509 510 511 /* insert_meta(): 512 * change the word before the cursor. 513 * cp must point to the start of the unquoted word. 514 * cpend to the end of it. 515 * word is the text that has to be substituted. 516 * strategy: 517 * try to keep all the quote characters of the user's input. 518 * change quote type only if necessary. 519 */ 520 static int 521 insert_meta(cp, cpend, word, closequotes) 522 Char *cp; 523 Char *cpend; 524 Char *word; 525 bool closequotes; 526 { 527 Char buffer[2 * FILSIZ + 1], *bptr, *wptr; 528 int in_sync = (cp != NULL); 529 int qu = 0; 530 int ndel = (int) (cp ? cpend - cp : 0); 531 Char w, wq; 532 #ifdef DSPMBYTE 533 int mbytepos = 1; 534 #endif /* DSPMBYTE */ 535 536 for (bptr = buffer, wptr = word;;) { 537 if (bptr > buffer + 2 * FILSIZ - 5) 538 break; 539 540 if (cp >= cpend) 541 in_sync = 0; 542 #ifdef DSPMBYTE 543 if (mbytepos == 1) 544 #endif /* DSPMBYTE */ 545 if (in_sync && !cmap(qu, _ESC) && cmap(*cp, _QF|_ESC)) 546 if (qu == 0 || qu == *cp) { 547 qu ^= *cp; 548 *bptr++ = *cp++; 549 continue; 550 } 551 w = *wptr; 552 if (w == 0) 553 break; 554 555 wq = w & QUOTE; 556 w &= ~QUOTE; 557 558 #ifdef DSPMBYTE 559 if (mbytepos == 2) 560 goto mbyteskip; 561 #endif /* DSPMBYTE */ 562 if (cmap(w, _ESC | _QF)) 563 wq = QUOTE; /* quotes are always quoted */ 564 565 if (!wq && qu && tricky(w) && !(qu == '\"' && tricky_dq(w))) { 566 /* We have to unquote the character */ 567 in_sync = 0; 568 if (cmap(qu, _ESC)) 569 bptr[-1] = w; 570 else { 571 *bptr++ = (Char) qu; 572 *bptr++ = w; 573 if (wptr[1] == 0) 574 qu = 0; 575 else 576 *bptr++ = (Char) qu; 577 } 578 } else if (qu && w == qu) { 579 in_sync = 0; 580 if (bptr > buffer && bptr[-1] == qu) { 581 /* User misunderstanding :) */ 582 bptr[-1] = '\\'; 583 *bptr++ = w; 584 qu = 0; 585 } else { 586 *bptr++ = (Char) qu; 587 *bptr++ = '\\'; 588 *bptr++ = w; 589 *bptr++ = (Char) qu; 590 } 591 } 592 else if (wq && qu == '\"' && tricky_dq(w)) { 593 in_sync = 0; 594 *bptr++ = (Char) qu; 595 *bptr++ = '\\'; 596 *bptr++ = w; 597 *bptr++ = (Char) qu; 598 } else if (wq && ((!qu && (tricky(w) || (w == HISTSUB && bptr == buffer))) || (!cmap(qu, _ESC) && w == HIST))) { 599 in_sync = 0; 600 *bptr++ = '\\'; 601 *bptr++ = w; 602 } else { 603 #ifdef DSPMBYTE 604 mbyteskip: 605 #endif /* DSPMBYTE */ 606 if (in_sync && *cp++ != w) 607 in_sync = 0; 608 *bptr++ = w; 609 #ifdef DSPMBYTE 610 if (mbytepos == 1 && Ismbyte1(w)) 611 mbytepos = 2; 612 else 613 mbytepos = 1; 614 #endif /* DSPMBYTE */ 615 } 616 wptr++; 617 if (cmap(qu, _ESC)) 618 qu = 0; 619 } 620 if (closequotes && qu && !cmap(qu, _ESC)) 621 *bptr++ = (Char) qu; 622 *bptr = '\0'; 623 if (ndel) 624 DeleteBack(ndel); 625 return InsertStr(buffer); 626 } /* end insert_meta */ 627 628 629 630 /* is_prefix(): 631 * return true if check matches initial chars in template 632 * This differs from PWB imatch in that if check is null 633 * it matches anything 634 */ 635 static int 636 is_prefix(check, template) 637 register Char *check, *template; 638 { 639 for (; *check; check++, template++) 640 if ((*check & TRIM) != (*template & TRIM)) 641 return (FALSE); 642 return (TRUE); 643 } /* end is_prefix */ 644 645 646 /* is_prefixmatch(): 647 * return true if check matches initial chars in template 648 * This differs from PWB imatch in that if check is null 649 * it matches anything 650 * and matches on shortening of commands 651 */ 652 static int 653 is_prefixmatch(check, template, igncase) 654 Char *check, *template; 655 int igncase; 656 { 657 Char MCH1, MCH2; 658 659 for (; *check; check++, template++) { 660 if ((*check & TRIM) != (*template & TRIM)) { 661 MCH1 = (*check & TRIM); 662 MCH2 = (*template & TRIM); 663 MCH1 = Isupper(MCH1) ? Tolower(MCH1) : MCH1; 664 MCH2 = Isupper(MCH2) ? Tolower(MCH2) : MCH2; 665 if (MCH1 != MCH2) { 666 if (!igncase && ((*check & TRIM) == '-' || 667 (*check & TRIM) == '.' || 668 (*check & TRIM) == '_')) { 669 MCH1 = MCH2 = (*check & TRIM); 670 if (MCH1 == '_') { 671 MCH2 = '-'; 672 } else if (MCH1 == '-') { 673 MCH2 = '_'; 674 } 675 for (;*template && (*template & TRIM) != MCH1 && 676 (*template & TRIM) != MCH2; template++) 677 continue; 678 if (!*template) { 679 return (FALSE); 680 } 681 } else { 682 return (FALSE); 683 } 684 } 685 } 686 } 687 return (TRUE); 688 } /* end is_prefixmatch */ 689 690 691 /* is_suffix(): 692 * Return true if the chars in template appear at the 693 * end of check, I.e., are it's suffix. 694 */ 695 static int 696 is_suffix(check, template) 697 register Char *check, *template; 698 { 699 register Char *t, *c; 700 701 for (t = template; *t++;) 702 continue; 703 for (c = check; *c++;) 704 continue; 705 for (;;) { 706 if (t == template) 707 return 1; 708 if (c == check || (*--t & TRIM) != (*--c & TRIM)) 709 return 0; 710 } 711 } /* end is_suffix */ 712 713 714 /* ignored(): 715 * Return true if this is an ignored item 716 */ 717 static int 718 ignored(item) 719 register Char *item; 720 { 721 struct varent *vp; 722 register Char **cp; 723 724 if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL) 725 return (FALSE); 726 for (; *cp != NULL; cp++) 727 if (is_suffix(item, *cp)) 728 return (TRUE); 729 return (FALSE); 730 } /* end ignored */ 731 732 733 734 /* starting_a_command(): 735 * return true if the command starting at wordstart is a command 736 */ 737 int 738 starting_a_command(wordstart, inputline) 739 register Char *wordstart, *inputline; 740 { 741 register Char *ptr, *ncmdstart; 742 int count; 743 static Char 744 cmdstart[] = {'`', ';', '&', '(', '|', '\0'}, 745 cmdalive[] = {' ', '\t', '\'', '"', '<', '>', '\0'}; 746 747 /* 748 * Find if the number of backquotes is odd or even. 749 */ 750 for (ptr = wordstart, count = 0; 751 ptr >= inputline; 752 count += (*ptr-- == '`')) 753 continue; 754 /* 755 * if the number of backquotes is even don't include the backquote char in 756 * the list of command starting delimiters [if it is zero, then it does not 757 * matter] 758 */ 759 ncmdstart = cmdstart + EVEN(count); 760 761 /* 762 * look for the characters previous to this word if we find a command 763 * starting delimiter we break. if we find whitespace and another previous 764 * word then we are not a command 765 * 766 * count is our state machine: 0 looking for anything 1 found white-space 767 * looking for non-ws 768 */ 769 for (count = 0; wordstart >= inputline; wordstart--) { 770 if (*wordstart == '\0') 771 continue; 772 if (Strchr(ncmdstart, *wordstart)) 773 break; 774 /* 775 * found white space 776 */ 777 if ((ptr = Strchr(cmdalive, *wordstart)) != NULL) 778 count = 1; 779 if (count == 1 && !ptr) 780 return (FALSE); 781 } 782 783 if (wordstart > inputline) 784 switch (*wordstart) { 785 case '&': /* Look for >& */ 786 while (wordstart > inputline && 787 (*--wordstart == ' ' || *wordstart == '\t')) 788 continue; 789 if (*wordstart == '>') 790 return (FALSE); 791 break; 792 case '(': /* check for foreach, if etc. */ 793 while (wordstart > inputline && 794 (*--wordstart == ' ' || *wordstart == '\t')) 795 continue; 796 if (!iscmdmeta(*wordstart) && 797 (*wordstart != ' ' && *wordstart != '\t')) 798 return (FALSE); 799 break; 800 default: 801 break; 802 } 803 return (TRUE); 804 } /* end starting_a_command */ 805 806 807 /* recognize(): 808 * Object: extend what user typed up to an ambiguity. 809 * Algorithm: 810 * On first match, copy full item (assume it'll be the only match) 811 * On subsequent matches, shorten exp_name to the first 812 * character mismatch between exp_name and item. 813 * If we shorten it back to the prefix length, stop searching. 814 */ 815 static int 816 recognize(exp_name, item, name_length, numitems, enhanced) 817 Char *exp_name, *item; 818 int name_length, numitems, enhanced; 819 { 820 Char MCH1, MCH2; 821 register Char *x, *ent; 822 register int len = 0; 823 #ifdef WINNT_NATIVE 824 struct varent *vp; 825 int igncase; 826 igncase = (vp = adrof(STRcomplete)) != NULL && vp->vec != NULL && 827 Strcmp(*(vp->vec), STRigncase) == 0; 828 #endif /* WINNT_NATIVE */ 829 830 if (numitems == 1) { /* 1st match */ 831 copyn(exp_name, item, MAXNAMLEN); 832 return (0); 833 } 834 if (!enhanced 835 #ifdef WINNT_NATIVE 836 && !igncase 837 #endif /* WINNT_NATIVE */ 838 ) { 839 for (x = exp_name, ent = item; *x && (*x & TRIM) == (*ent & TRIM); x++, ent++) 840 len++; 841 } else { 842 for (x = exp_name, ent = item; *x; x++, ent++) { 843 MCH1 = *x & TRIM; 844 MCH2 = *ent & TRIM; 845 MCH1 = Isupper(MCH1) ? Tolower(MCH1) : MCH1; 846 MCH2 = Isupper(MCH2) ? Tolower(MCH2) : MCH2; 847 if (MCH1 != MCH2) 848 break; 849 len++; 850 } 851 if (*x || !*ent) /* Shorter or exact match */ 852 copyn(exp_name, item, MAXNAMLEN); 853 } 854 *x = '\0'; /* Shorten at 1st char diff */ 855 if (!(match_unique_match || is_set(STRrecexact) || (enhanced && *ent)) && len == name_length) /* Ambiguous to prefix? */ 856 return (-1); /* So stop now and save time */ 857 return (0); 858 } /* end recognize */ 859 860 861 /* tw_collect_items(): 862 * Collect items that match target. 863 * SPELL command: 864 * Returns the spelling distance of the closest match. 865 * else 866 * Returns the number of items found. 867 * If none found, but some ignored items were found, 868 * It returns the -number of ignored items. 869 */ 870 static int 871 tw_collect_items(command, looking, exp_dir, exp_name, target, pat, flags) 872 COMMAND command; 873 int looking; 874 Char *exp_dir, *exp_name, *target, *pat; 875 int flags; 876 877 { 878 int done = FALSE; /* Search is done */ 879 int showdots; /* Style to show dot files */ 880 int nignored = 0; /* Number of fignored items */ 881 int numitems = 0; /* Number of matched items */ 882 int name_length = (int) Strlen(target); /* Length of prefix (file name) */ 883 int exec_check = flags & TW_EXEC_CHK;/* need to check executability */ 884 int dir_check = flags & TW_DIR_CHK; /* Need to check for directories */ 885 int text_check = flags & TW_TEXT_CHK;/* Need to check for non-directories */ 886 int dir_ok = flags & TW_DIR_OK; /* Ignore directories? */ 887 int gpat = flags & TW_PAT_OK; /* Match against a pattern */ 888 int ignoring = flags & TW_IGN_OK; /* Use fignore? */ 889 int d = 4, nd; /* Spelling distance */ 890 Char *item, *ptr; 891 Char buf[MAXPATHLEN+1]; 892 struct varent *vp; 893 int len, enhanced; 894 int cnt = 0; 895 int igncase = 0; 896 897 898 flags = 0; 899 900 showdots = DOT_NONE; 901 if ((ptr = varval(STRlistflags)) != STRNULL) 902 while (*ptr) 903 switch (*ptr++) { 904 case 'a': 905 showdots = DOT_ALL; 906 break; 907 case 'A': 908 showdots = DOT_NOT; 909 break; 910 default: 911 break; 912 } 913 914 while (!done && (item = (*tw_next_entry[looking])(exp_dir, &flags))) { 915 #ifdef TDEBUG 916 xprintf("item = %S\n", item); 917 #endif 918 switch (looking) { 919 case TW_FILE: 920 case TW_DIRECTORY: 921 case TW_TEXT: 922 /* 923 * Don't match . files on null prefix match 924 */ 925 if (showdots == DOT_NOT && (ISDOT(item) || ISDOTDOT(item))) 926 done = TRUE; 927 if (name_length == 0 && item[0] == '.' && showdots == DOT_NONE) 928 done = TRUE; 929 break; 930 931 case TW_COMMAND: 932 exec_check = flags & TW_EXEC_CHK; 933 dir_ok = flags & TW_DIR_OK; 934 break; 935 936 default: 937 break; 938 } 939 940 if (done) { 941 done = FALSE; 942 continue; 943 } 944 945 switch (command) { 946 947 case SPELL: /* correct the spelling of the last bit */ 948 if (name_length == 0) {/* zero-length word can't be misspelled */ 949 exp_name[0] = '\0';/* (not trying is important for ~) */ 950 d = 0; 951 done = TRUE; 952 break; 953 } 954 if (gpat && !Gmatch(item, pat)) 955 break; 956 /* 957 * Swapped the order of the spdist() arguments as suggested 958 * by eeide@asylum.cs.utah.edu (Eric Eide) 959 */ 960 nd = spdist(target, item); /* test the item against original */ 961 if (nd <= d && nd != 4) { 962 if (!(exec_check && !executable(exp_dir, item, dir_ok))) { 963 (void) Strcpy(exp_name, item); 964 d = nd; 965 if (d == 0) /* if found it exactly */ 966 done = TRUE; 967 } 968 } 969 else if (nd == 4) { 970 if (spdir(exp_name, exp_dir, item, target)) { 971 if (exec_check && !executable(exp_dir, exp_name, dir_ok)) 972 break; 973 #ifdef notdef 974 /* 975 * We don't want to stop immediately, because 976 * we might find an exact/better match later. 977 */ 978 d = 0; 979 done = TRUE; 980 #endif 981 d = 3; 982 } 983 } 984 break; 985 986 case LIST: 987 case RECOGNIZE: 988 case RECOGNIZE_ALL: 989 case RECOGNIZE_SCROLL: 990 991 #ifdef WINNT_NATIVE 992 igncase = (vp = adrof(STRcomplete)) != NULL && vp->vec != NULL && 993 Strcmp(*(vp->vec), STRigncase) == 0; 994 #endif /* WINNT_NATIVE */ 995 enhanced = (vp = adrof(STRcomplete)) != NULL && !Strcmp(*(vp->vec),STRenhance); 996 if (enhanced || igncase) { 997 if (!is_prefixmatch(target, item, igncase)) 998 break; 999 } else { 1000 if (!is_prefix(target, item)) 1001 break; 1002 } 1003 1004 if (exec_check && !executable(exp_dir, item, dir_ok)) 1005 break; 1006 1007 if (dir_check && !isadirectory(exp_dir, item)) 1008 break; 1009 1010 if (text_check && isadirectory(exp_dir, item)) 1011 break; 1012 1013 /* 1014 * Only pattern match directories if we're checking 1015 * for directories. 1016 */ 1017 if (gpat && !Gmatch(item, pat) && 1018 (dir_check || !isadirectory(exp_dir, item))) 1019 break; 1020 1021 /* 1022 * Remove duplicates in command listing and completion 1023 * AFEB added code for TW_LOGNAME and TW_USER cases 1024 */ 1025 if (looking == TW_COMMAND || looking == TW_LOGNAME 1026 || looking == TW_USER || command == LIST) { 1027 copyn(buf, item, MAXPATHLEN); 1028 len = (int) Strlen(buf); 1029 switch (looking) { 1030 case TW_COMMAND: 1031 if (!(dir_ok && exec_check)) 1032 break; 1033 if (filetype(exp_dir, item) == '/') { 1034 buf[len++] = '/'; 1035 buf[len] = '\0'; 1036 } 1037 break; 1038 1039 case TW_FILE: 1040 case TW_DIRECTORY: 1041 buf[len++] = filetype(exp_dir, item); 1042 buf[len] = '\0'; 1043 break; 1044 1045 default: 1046 break; 1047 } 1048 if ((looking == TW_COMMAND || looking == TW_USER 1049 || looking == TW_LOGNAME) && tw_item_find(buf)) 1050 break; 1051 else { 1052 /* maximum length 1 (NULL) + 1 (~ or $) + 1 (filetype) */ 1053 ptr = tw_item_add(len + 3); 1054 copyn(ptr, buf, MAXPATHLEN); 1055 if (command == LIST) 1056 numitems++; 1057 } 1058 } 1059 1060 if (command == RECOGNIZE || command == RECOGNIZE_ALL || 1061 command == RECOGNIZE_SCROLL) { 1062 if (ignoring && ignored(item)) { 1063 nignored++; 1064 break; 1065 } 1066 else if (command == RECOGNIZE_SCROLL) { 1067 add_scroll_tab(item); 1068 cnt++; 1069 } 1070 1071 if (match_unique_match || is_set(STRrecexact)) { 1072 if (StrQcmp(target, item) == 0) { /* EXACT match */ 1073 copyn(exp_name, item, MAXNAMLEN); 1074 numitems = 1; /* fake into expanding */ 1075 non_unique_match = TRUE; 1076 done = TRUE; 1077 break; 1078 } 1079 } 1080 if (recognize(exp_name, item, name_length, ++numitems, enhanced)) 1081 if (command != RECOGNIZE_SCROLL) 1082 done = TRUE; 1083 if (enhanced && (int)Strlen(exp_name) < name_length) 1084 copyn(exp_name, target, MAXNAMLEN); 1085 } 1086 break; 1087 1088 default: 1089 break; 1090 } 1091 #ifdef TDEBUG 1092 xprintf("done item = %S\n", item); 1093 #endif 1094 } 1095 1096 1097 if (command == RECOGNIZE_SCROLL) { 1098 if ((cnt <= curchoice) || (curchoice == -1)) { 1099 curchoice = -1; 1100 nignored = 0; 1101 numitems = 0; 1102 } else if (numitems > 1) { 1103 if (curchoice < -1) 1104 curchoice = cnt - 1; 1105 choose_scroll_tab(&exp_name, cnt); 1106 numitems = 1; 1107 } 1108 } 1109 free_scroll_tab(); 1110 1111 if (command == SPELL) 1112 return d; 1113 else { 1114 if (ignoring && numitems == 0 && nignored > 0) 1115 return -nignored; 1116 else 1117 return numitems; 1118 } 1119 } 1120 1121 1122 /* tw_suffix(): 1123 * Find and return the appropriate suffix character 1124 */ 1125 /*ARGSUSED*/ 1126 static Char 1127 tw_suffix(looking, exp_dir, exp_name, target, name) 1128 int looking; 1129 Char *exp_dir, *exp_name, *target, *name; 1130 { 1131 Char *ptr; 1132 struct varent *vp; 1133 1134 USE(name); 1135 (void) strip(exp_name); 1136 1137 switch (looking) { 1138 1139 case TW_LOGNAME: 1140 return '/'; 1141 1142 case TW_VARIABLE: 1143 /* 1144 * Don't consider array variables or empty variables 1145 */ 1146 if ((vp = adrof(exp_name)) != NULL && vp->vec != NULL) { 1147 if ((ptr = vp->vec[0]) == NULL || *ptr == '\0' || 1148 vp->vec[1] != NULL) 1149 return ' '; 1150 } 1151 else if ((ptr = tgetenv(exp_name)) == NULL || *ptr == '\0') 1152 return ' '; 1153 1154 *--target = '\0'; 1155 1156 return isadirectory(exp_dir, ptr) ? '/' : ' '; 1157 1158 1159 case TW_DIRECTORY: 1160 return '/'; 1161 1162 case TW_COMMAND: 1163 case TW_FILE: 1164 return isadirectory(exp_dir, exp_name) ? '/' : ' '; 1165 1166 case TW_ALIAS: 1167 case TW_VARLIST: 1168 case TW_WORDLIST: 1169 case TW_SHELLVAR: 1170 case TW_ENVVAR: 1171 case TW_USER: 1172 case TW_BINDING: 1173 case TW_LIMIT: 1174 case TW_SIGNAL: 1175 case TW_JOB: 1176 case TW_COMPLETION: 1177 case TW_TEXT: 1178 case TW_GRPNAME: 1179 return ' '; 1180 1181 default: 1182 return '\0'; 1183 } 1184 } /* end tw_suffix */ 1185 1186 1187 /* tw_fixword(): 1188 * Repair a word after a spalling or a recognizwe 1189 */ 1190 static void 1191 tw_fixword(looking, word, dir, exp_name, max_word_length) 1192 int looking; 1193 Char *word, *dir, *exp_name; 1194 int max_word_length; 1195 { 1196 Char *ptr; 1197 1198 switch (looking) { 1199 case TW_LOGNAME: 1200 copyn(word, STRtilde, 1); 1201 break; 1202 1203 case TW_VARIABLE: 1204 if ((ptr = Strrchr(word, '$')) != NULL) 1205 *++ptr = '\0'; /* Delete after the dollar */ 1206 else 1207 word[0] = '\0'; 1208 break; 1209 1210 case TW_DIRECTORY: 1211 case TW_FILE: 1212 case TW_TEXT: 1213 copyn(word, dir, max_word_length); /* put back dir part */ 1214 break; 1215 1216 default: 1217 word[0] = '\0'; 1218 break; 1219 } 1220 1221 (void) quote(exp_name); 1222 catn(word, exp_name, max_word_length); /* add extended name */ 1223 } /* end tw_fixword */ 1224 1225 1226 /* tw_collect(): 1227 * Collect items. Return -1 in case we were interrupted or 1228 * the return value of tw_collect 1229 * This is really a wrapper for tw_collect_items, serving two 1230 * purposes: 1231 * 1. Handles interrupt cleanups. 1232 * 2. Retries if we had no matches, but there were ignored matches 1233 */ 1234 static int 1235 tw_collect(command, looking, exp_dir, exp_name, target, pat, flags, dir_fd) 1236 COMMAND command; 1237 int looking; 1238 Char *exp_dir, *exp_name, **target, *pat; 1239 int flags; 1240 DIR *dir_fd; 1241 { 1242 static int ni; /* static so we don't get clobbered */ 1243 jmp_buf_t osetexit; 1244 1245 #ifdef TDEBUG 1246 xprintf("target = %S\n", *target); 1247 #endif 1248 ni = 0; 1249 getexit(osetexit); 1250 for (;;) { 1251 (*tw_start_entry[looking])(dir_fd, pat); 1252 InsideCompletion = 1; 1253 if (setexit()) { 1254 /* interrupted, clean up */ 1255 resexit(osetexit); 1256 InsideCompletion = 0; 1257 haderr = 0; 1258 1259 #if defined(SOLARIS2) && defined(i386) && !defined(__GNUC__) 1260 /* Compiler bug? (from PWP) */ 1261 if ((looking == TW_LOGNAME) || (looking == TW_USER)) 1262 tw_logname_end(); 1263 else 1264 if (looking == TW_GRPNAME) 1265 tw_grpname_end(); 1266 else 1267 tw_dir_end(); 1268 #else /* !(SOLARIS2 && i386 && !__GNUC__) */ 1269 (*tw_end_entry[looking])(); 1270 #endif /* !(SOLARIS2 && i386 && !__GNUC__) */ 1271 1272 /* flag error */ 1273 return(-1); 1274 } 1275 if ((ni = tw_collect_items(command, looking, exp_dir, exp_name, 1276 *target, pat, 1277 ni >= 0 ? flags : 1278 flags & ~TW_IGN_OK)) >= 0) { 1279 resexit(osetexit); 1280 InsideCompletion = 0; 1281 1282 #if defined(SOLARIS2) && defined(i386) && !defined(__GNUC__) 1283 /* Compiler bug? (from PWP) */ 1284 if ((looking == TW_LOGNAME) || (looking == TW_USER)) 1285 tw_logname_end(); 1286 else 1287 if (looking == TW_GRPNAME) 1288 tw_grpname_end(); 1289 else 1290 tw_dir_end(); 1291 #else /* !(SOLARIS2 && i386 && !__GNUC__) */ 1292 (*tw_end_entry[looking])(); 1293 #endif /* !(SOLARIS2 && i386 && !__GNUC__) */ 1294 1295 return(ni); 1296 } 1297 } 1298 } /* end tw_collect */ 1299 1300 1301 /* tw_list_items(): 1302 * List the items that were found 1303 * 1304 * NOTE instead of looking at numerical vars listmax and listmaxrows 1305 * we can look at numerical var listmax, and have a string value 1306 * listmaxtype (or similar) than can have values 'items' and 'rows' 1307 * (by default interpreted as 'items', for backwards compatibility) 1308 */ 1309 static void 1310 tw_list_items(looking, numitems, list_max) 1311 int looking, numitems, list_max; 1312 { 1313 Char *ptr; 1314 int max_items = 0; 1315 int max_rows = 0; 1316 1317 if (numitems == 0) 1318 return; 1319 1320 if ((ptr = varval(STRlistmax)) != STRNULL) { 1321 while (*ptr) { 1322 if (!Isdigit(*ptr)) { 1323 max_items = 0; 1324 break; 1325 } 1326 max_items = max_items * 10 + *ptr++ - '0'; 1327 } 1328 if ((max_items > 0) && (numitems > max_items) && list_max) 1329 max_items = numitems; 1330 else 1331 max_items = 0; 1332 } 1333 1334 if (max_items == 0 && (ptr = varval(STRlistmaxrows)) != STRNULL) { 1335 int rows; 1336 1337 while (*ptr) { 1338 if (!Isdigit(*ptr)) { 1339 max_rows = 0; 1340 break; 1341 } 1342 max_rows = max_rows * 10 + *ptr++ - '0'; 1343 } 1344 if (max_rows != 0 && looking != TW_JOB) 1345 rows = find_rows(tw_item_get(), numitems, TRUE); 1346 else 1347 rows = numitems; /* underestimate for lines wider than the termH */ 1348 if ((max_rows > 0) && (rows > max_rows) && list_max) 1349 max_rows = rows; 1350 else 1351 max_rows = 0; 1352 } 1353 1354 1355 if (max_items || max_rows) { 1356 char tc; 1357 const char *name; 1358 int maxs; 1359 1360 if (max_items) { 1361 name = CGETS(30, 5, "items"); 1362 maxs = max_items; 1363 } 1364 else { 1365 name = CGETS(30, 6, "rows"); 1366 maxs = max_rows; 1367 } 1368 1369 xprintf(CGETS(30, 7, "There are %d %s, list them anyway? [n/y] "), 1370 maxs, name); 1371 flush(); 1372 /* We should be in Rawmode here, so no \n to catch */ 1373 (void) read(SHIN, &tc, 1); 1374 xprintf("%c\r\n", tc); /* echo the char, do a newline */ 1375 /* 1376 * Perhaps we should use the yesexpr from the 1377 * actual locale 1378 */ 1379 if (strchr(CGETS(30, 13, "Yy"), tc) == NULL) 1380 return; 1381 } 1382 1383 if (looking != TW_SIGNAL) 1384 qsort((ptr_t) tw_item_get(), (size_t) numitems, sizeof(Char *), 1385 (int (*) __P((const void *, const void *))) fcompare); 1386 if (looking != TW_JOB) 1387 print_by_column(STRNULL, tw_item_get(), numitems, TRUE); 1388 else { 1389 /* 1390 * print one item on every line because jobs can have spaces 1391 * and it is confusing. 1392 */ 1393 int i; 1394 Char **w = tw_item_get(); 1395 1396 for (i = 0; i < numitems; i++) { 1397 xprintf("%S", w[i]); 1398 if (Tty_raw_mode) 1399 xputchar('\r'); 1400 xputchar('\n'); 1401 } 1402 } 1403 } /* end tw_list_items */ 1404 1405 1406 /* t_search(): 1407 * Perform a RECOGNIZE, LIST or SPELL command on string "word". 1408 * 1409 * Return value: 1410 * >= 0: SPELL command: "distance" (see spdist()) 1411 * other: No. of items found 1412 * < 0: Error (message or beep is output) 1413 */ 1414 /*ARGSUSED*/ 1415 int 1416 t_search(word, wp, command, max_word_length, looking, list_max, pat, suf) 1417 Char *word, *wp; /* original end-of-word */ 1418 COMMAND command; 1419 int max_word_length, looking, list_max; 1420 Char *pat; 1421 int suf; 1422 { 1423 int numitems, /* Number of items matched */ 1424 flags = 0, /* search flags */ 1425 gpat = pat[0] != '\0', /* Glob pattern search */ 1426 nd; /* Normalized directory return */ 1427 Char exp_dir[FILSIZ + 1], /* dir after ~ expansion */ 1428 dir[FILSIZ + 1], /* /x/y/z/ part in /x/y/z/f */ 1429 exp_name[MAXNAMLEN + 1], /* the recognized (extended) */ 1430 name[MAXNAMLEN + 1], /* f part in /d/d/d/f name */ 1431 *target; /* Target to expand/correct/list */ 1432 DIR *dir_fd = NULL; 1433 1434 USE(wp); 1435 1436 /* 1437 * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can 1438 * dump core when interrupted 1439 */ 1440 tw_item_free(); 1441 1442 non_unique_match = FALSE; /* See the recexact code below */ 1443 1444 extract_dir_and_name(word, dir, name); 1445 1446 /* 1447 * SPECIAL HARDCODED COMPLETIONS: 1448 * foo$variable -> TW_VARIABLE 1449 * ~user -> TW_LOGNAME 1450 * 1451 */ 1452 if ((*word == '~') && (Strchr(word, '/') == NULL)) { 1453 looking = TW_LOGNAME; 1454 target = name; 1455 gpat = 0; /* Override pattern mechanism */ 1456 } 1457 else if ((target = Strrchr(name, '$')) != 0 && 1458 (Strchr(name, '/') == NULL)) { 1459 target++; 1460 looking = TW_VARIABLE; 1461 gpat = 0; /* Override pattern mechanism */ 1462 } 1463 else 1464 target = name; 1465 1466 /* 1467 * Try to figure out what we should be looking for 1468 */ 1469 if (looking & TW_PATH) { 1470 gpat = 0; /* pattern holds the pathname to be used */ 1471 copyn(exp_dir, pat, MAXNAMLEN); 1472 if (exp_dir[Strlen(exp_dir) - 1] != '/') 1473 catn(exp_dir, STRslash, MAXNAMLEN); 1474 catn(exp_dir, dir, MAXNAMLEN); 1475 } 1476 else 1477 exp_dir[0] = '\0'; 1478 1479 switch (looking & ~TW_PATH) { 1480 case TW_NONE: 1481 return -1; 1482 1483 case TW_ZERO: 1484 looking = TW_FILE; 1485 break; 1486 1487 case TW_COMMAND: 1488 if (Strchr(word, '/') || (looking & TW_PATH)) { 1489 looking = TW_FILE; 1490 flags |= TW_EXEC_CHK; 1491 flags |= TW_DIR_OK; 1492 } 1493 #ifdef notdef 1494 /* PWP: don't even bother when doing ALL of the commands */ 1495 if (looking == TW_COMMAND && (*word == '\0')) 1496 return (-1); 1497 #endif 1498 break; 1499 1500 1501 case TW_VARLIST: 1502 case TW_WORDLIST: 1503 gpat = 0; /* pattern holds the name of the variable */ 1504 break; 1505 1506 case TW_EXPLAIN: 1507 if (command == LIST && pat != NULL) { 1508 xprintf("%S", pat); 1509 if (Tty_raw_mode) 1510 xputchar('\r'); 1511 xputchar('\n'); 1512 } 1513 return 2; 1514 1515 default: 1516 break; 1517 } 1518 1519 /* 1520 * let fignore work only when we are not using a pattern 1521 */ 1522 flags |= (gpat == 0) ? TW_IGN_OK : TW_PAT_OK; 1523 1524 #ifdef TDEBUG 1525 xprintf(CGETS(30, 8, "looking = %d\n"), looking); 1526 #endif 1527 1528 switch (looking) { 1529 case TW_ALIAS: 1530 case TW_SHELLVAR: 1531 case TW_ENVVAR: 1532 case TW_BINDING: 1533 case TW_LIMIT: 1534 case TW_SIGNAL: 1535 case TW_JOB: 1536 case TW_COMPLETION: 1537 case TW_GRPNAME: 1538 break; 1539 1540 1541 case TW_VARIABLE: 1542 if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0) 1543 return nd; 1544 break; 1545 1546 case TW_DIRECTORY: 1547 flags |= TW_DIR_CHK; 1548 1549 #ifdef notyet 1550 /* 1551 * This is supposed to expand the directory stack. 1552 * Problems: 1553 * 1. Slow 1554 * 2. directories with the same name 1555 */ 1556 flags |= TW_DIR_OK; 1557 #endif 1558 #ifdef notyet 1559 /* 1560 * Supposed to do delayed expansion, but it is inconsistent 1561 * from a user-interface point of view, since it does not 1562 * immediately obey addsuffix 1563 */ 1564 if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0) 1565 return nd; 1566 if (isadirectory(exp_dir, name)) { 1567 if (exp_dir[0] != '\0' || name[0] != '\0') { 1568 catn(dir, name, MAXNAMLEN); 1569 if (dir[Strlen(dir) - 1] != '/') 1570 catn(dir, STRslash, MAXNAMLEN); 1571 if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0) 1572 return nd; 1573 if (word[Strlen(word) - 1] != '/') 1574 catn(word, STRslash, MAXNAMLEN); 1575 name[0] = '\0'; 1576 } 1577 } 1578 #endif 1579 if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0) 1580 return nd; 1581 break; 1582 1583 case TW_TEXT: 1584 flags |= TW_TEXT_CHK; 1585 /*FALLTHROUGH*/ 1586 case TW_FILE: 1587 if ((nd = expand_dir(dir, exp_dir, &dir_fd, command)) != 0) 1588 return nd; 1589 break; 1590 1591 case TW_PATH | TW_TEXT: 1592 case TW_PATH | TW_FILE: 1593 case TW_PATH | TW_DIRECTORY: 1594 case TW_PATH | TW_COMMAND: 1595 if ((dir_fd = opendir(short2str(exp_dir))) == NULL) { 1596 if (command == RECOGNIZE) 1597 xprintf("\n"); 1598 xprintf("%S: %s", exp_dir, strerror(errno)); 1599 if (command != RECOGNIZE) 1600 xprintf("\n"); 1601 NeedsRedraw = 1; 1602 return -1; 1603 } 1604 if (exp_dir[Strlen(exp_dir) - 1] != '/') 1605 catn(exp_dir, STRslash, MAXNAMLEN); 1606 1607 looking &= ~TW_PATH; 1608 1609 switch (looking) { 1610 case TW_TEXT: 1611 flags |= TW_TEXT_CHK; 1612 break; 1613 1614 case TW_FILE: 1615 break; 1616 1617 case TW_DIRECTORY: 1618 flags |= TW_DIR_CHK; 1619 break; 1620 1621 case TW_COMMAND: 1622 copyn(target, word, MAXNAMLEN); /* so it can match things */ 1623 break; 1624 1625 default: 1626 abort(); /* Cannot happen */ 1627 break; 1628 } 1629 break; 1630 1631 case TW_LOGNAME: 1632 word++; 1633 /*FALLTHROUGH*/ 1634 case TW_USER: 1635 /* 1636 * Check if the spelling was already correct 1637 * From: Rob McMahon <cudcv@cu.warwick.ac.uk> 1638 */ 1639 if (command == SPELL && getpwnam(short2str(word)) != NULL) { 1640 #ifdef YPBUGS 1641 fix_yp_bugs(); 1642 #endif /* YPBUGS */ 1643 return (0); 1644 } 1645 copyn(name, word, MAXNAMLEN); /* name sans ~ */ 1646 if (looking == TW_LOGNAME) 1647 word--; 1648 break; 1649 1650 case TW_COMMAND: 1651 case TW_VARLIST: 1652 case TW_WORDLIST: 1653 copyn(target, word, MAXNAMLEN); /* so it can match things */ 1654 break; 1655 1656 default: 1657 xprintf(CGETS(30, 9, 1658 "\n%s internal error: I don't know what I'm looking for!\n"), 1659 progname); 1660 NeedsRedraw = 1; 1661 return (-1); 1662 } 1663 1664 numitems = tw_collect(command, looking, exp_dir, exp_name, 1665 &target, pat, flags, dir_fd); 1666 if (numitems == -1) 1667 return -1; 1668 1669 switch (command) { 1670 case RECOGNIZE: 1671 case RECOGNIZE_ALL: 1672 case RECOGNIZE_SCROLL: 1673 if (numitems <= 0) 1674 return (numitems); 1675 1676 tw_fixword(looking, word, dir, exp_name, max_word_length); 1677 1678 if (!match_unique_match && is_set(STRaddsuffix) && numitems == 1) { 1679 Char suffix[2]; 1680 1681 suffix[1] = '\0'; 1682 switch (suf) { 1683 case 0: /* Automatic suffix */ 1684 suffix[0] = tw_suffix(looking, exp_dir, exp_name, target, name); 1685 break; 1686 1687 case -1: /* No suffix */ 1688 return numitems; 1689 1690 default: /* completion specified suffix */ 1691 suffix[0] = (Char) suf; 1692 break; 1693 } 1694 catn(word, suffix, max_word_length); 1695 } 1696 return numitems; 1697 1698 case LIST: 1699 tw_list_items(looking, numitems, list_max); 1700 tw_item_free(); 1701 return (numitems); 1702 1703 case SPELL: 1704 tw_fixword(looking, word, dir, exp_name, max_word_length); 1705 return (numitems); 1706 1707 default: 1708 xprintf("Bad tw_command\n"); 1709 return (0); 1710 } 1711 } /* end t_search */ 1712 1713 1714 /* extract_dir_and_name(): 1715 * parse full path in file into 2 parts: directory and file names 1716 * Should leave final slash (/) at end of dir. 1717 */ 1718 static void 1719 extract_dir_and_name(path, dir, name) 1720 Char *path, *dir, *name; 1721 { 1722 register Char *p; 1723 1724 p = Strrchr(path, '/'); 1725 #ifdef WINNT_NATIVE 1726 if (p == NULL) 1727 p = Strrchr(path, ':'); 1728 #endif /* WINNT_NATIVE */ 1729 if (p == NULL) { 1730 copyn(name, path, MAXNAMLEN); 1731 dir[0] = '\0'; 1732 } 1733 else { 1734 p++; 1735 copyn(name, p, MAXNAMLEN); 1736 copyn(dir, path, p - path); 1737 } 1738 } /* end extract_dir_and_name */ 1739 1740 1741 /* dollar(): 1742 * expand "/$old1/$old2/old3/" 1743 * to "/value_of_old1/value_of_old2/old3/" 1744 */ 1745 Char * 1746 dollar(new, old) 1747 Char *new; 1748 const Char *old; 1749 { 1750 Char *p; 1751 size_t space; 1752 1753 for (space = FILSIZ, p = new; *old && space > 0;) 1754 if (*old != '$') { 1755 *p++ = *old++; 1756 space--; 1757 } 1758 else { 1759 if (expdollar(&p, &old, &space, QUOTE) == NULL) 1760 return NULL; 1761 } 1762 *p = '\0'; 1763 return (new); 1764 } /* end dollar */ 1765 1766 1767 /* tilde(): 1768 * expand ~person/foo to home_directory_of_person/foo 1769 * or =<stack-entry> to <dir in stack entry> 1770 */ 1771 static Char * 1772 tilde(new, old) 1773 Char *new, *old; 1774 { 1775 register Char *o, *p; 1776 1777 switch (old[0]) { 1778 case '~': 1779 for (p = new, o = &old[1]; *o && *o != '/'; *p++ = *o++) 1780 continue; 1781 *p = '\0'; 1782 if (gethdir(new)) { 1783 new[0] = '\0'; 1784 return NULL; 1785 } 1786 #ifdef apollo 1787 /* Special case: if the home directory expands to "/", we do 1788 * not want to create "//" by appending a slash from o. 1789 */ 1790 if (new[0] == '/' && new[1] == '\0' && *o == '/') 1791 ++o; 1792 #endif /* apollo */ 1793 (void) Strcat(new, o); 1794 return new; 1795 1796 case '=': 1797 if ((p = globequal(new, old)) == NULL) { 1798 *new = '\0'; 1799 return NULL; 1800 } 1801 if (p == new) 1802 return new; 1803 /*FALLTHROUGH*/ 1804 1805 default: 1806 (void) Strcpy(new, old); 1807 return new; 1808 } 1809 } /* end tilde */ 1810 1811 1812 /* expand_dir(): 1813 * Open the directory given, expanding ~user and $var 1814 * Optionally normalize the path given 1815 */ 1816 static int 1817 expand_dir(dir, edir, dfd, cmd) 1818 Char *dir, *edir; 1819 DIR **dfd; 1820 COMMAND cmd; 1821 { 1822 Char *nd = NULL; 1823 Char tdir[MAXPATHLEN + 1]; 1824 1825 if ((dollar(tdir, dir) == 0) || 1826 (tilde(edir, tdir) == 0) || 1827 !(nd = dnormalize(*edir ? edir : STRdot, symlinks == SYM_IGNORE || 1828 symlinks == SYM_EXPAND)) || 1829 ((*dfd = opendir(short2str(nd))) == NULL)) { 1830 xfree((ptr_t) nd); 1831 if (cmd == SPELL || SearchNoDirErr) 1832 return (-2); 1833 /* 1834 * From: Amos Shapira <amoss@cs.huji.ac.il> 1835 * Print a better message when completion fails 1836 */ 1837 xprintf("\n%S %s\n", 1838 *edir ? edir : 1839 (*tdir ? tdir : dir), 1840 (errno == ENOTDIR ? CGETS(30, 10, "not a directory") : 1841 (errno == ENOENT ? CGETS(30, 11, "not found") : 1842 CGETS(30, 12, "unreadable")))); 1843 NeedsRedraw = 1; 1844 return (-1); 1845 } 1846 if (nd) { 1847 if (*dir != '\0') { 1848 Char *s, *d, *p; 1849 1850 /* 1851 * Copy and append a / if there was one 1852 */ 1853 for (p = edir; *p; p++) 1854 continue; 1855 if (*--p == '/') { 1856 for (p = nd; *p; p++) 1857 continue; 1858 if (*--p != '/') 1859 p = NULL; 1860 } 1861 for (d = edir, s = nd; (*d++ = *s++) != '\0';) 1862 continue; 1863 if (!p) { 1864 *d-- = '\0'; 1865 *d = '/'; 1866 } 1867 } 1868 xfree((ptr_t) nd); 1869 } 1870 return 0; 1871 } /* end expand_dir */ 1872 1873 1874 /* nostat(): 1875 * Returns true if the directory should not be stat'd, 1876 * false otherwise. 1877 * This way, things won't grind to a halt when you complete in /afs 1878 * or very large directories. 1879 */ 1880 static bool 1881 nostat(dir) 1882 Char *dir; 1883 { 1884 struct varent *vp; 1885 register Char **cp; 1886 1887 if ((vp = adrof(STRnostat)) == NULL || (cp = vp->vec) == NULL) 1888 return FALSE; 1889 for (; *cp != NULL; cp++) { 1890 if (Strcmp(*cp, STRstar) == 0) 1891 return TRUE; 1892 if (Gmatch(dir, *cp)) 1893 return TRUE; 1894 } 1895 return FALSE; 1896 } /* end nostat */ 1897 1898 1899 /* filetype(): 1900 * Return a character that signifies a filetype 1901 * symbology from 4.3 ls command. 1902 */ 1903 static Char 1904 filetype(dir, file) 1905 Char *dir, *file; 1906 { 1907 if (dir) { 1908 Char path[512]; 1909 char *ptr; 1910 struct stat statb; 1911 #ifdef S_ISCDF 1912 /* 1913 * From: veals@crchh84d.bnr.ca (Percy Veals) 1914 * An extra stat is required for HPUX CDF files. 1915 */ 1916 struct stat hpstatb; 1917 #endif /* S_ISCDF */ 1918 1919 if (nostat(dir)) return(' '); 1920 1921 (void) Strcpy(path, dir); 1922 catn(path, file, (int) (sizeof(path) / sizeof(Char))); 1923 1924 if (lstat(ptr = short2str(path), &statb) != -1) 1925 /* see above #define of lstat */ 1926 { 1927 #ifdef S_ISLNK 1928 if (S_ISLNK(statb.st_mode)) { /* Symbolic link */ 1929 if (adrof(STRlistlinks)) { 1930 if (stat(ptr, &statb) == -1) 1931 return ('&'); 1932 else if (S_ISDIR(statb.st_mode)) 1933 return ('>'); 1934 else 1935 return ('@'); 1936 } 1937 else 1938 return ('@'); 1939 } 1940 #endif 1941 #ifdef S_ISSOCK 1942 if (S_ISSOCK(statb.st_mode)) /* Socket */ 1943 return ('='); 1944 #endif 1945 #ifdef S_ISFIFO 1946 if (S_ISFIFO(statb.st_mode)) /* Named Pipe */ 1947 return ('|'); 1948 #endif 1949 #ifdef S_ISHIDDEN 1950 if (S_ISHIDDEN(statb.st_mode)) /* Hidden Directory [aix] */ 1951 return ('+'); 1952 #endif 1953 #ifdef S_ISCDF 1954 (void) strcat(ptr, "+"); /* Must append a '+' and re-stat(). */ 1955 if ((stat(ptr, &hpstatb) != -1) && S_ISCDF(hpstatb.st_mode)) 1956 return ('+'); /* Context Dependent Files [hpux] */ 1957 #endif 1958 #ifdef S_ISNWK 1959 if (S_ISNWK(statb.st_mode)) /* Network Special [hpux] */ 1960 return (':'); 1961 #endif 1962 #ifdef S_ISCHR 1963 if (S_ISCHR(statb.st_mode)) /* char device */ 1964 return ('%'); 1965 #endif 1966 #ifdef S_ISBLK 1967 if (S_ISBLK(statb.st_mode)) /* block device */ 1968 return ('#'); 1969 #endif 1970 #ifdef S_ISDIR 1971 if (S_ISDIR(statb.st_mode)) /* normal Directory */ 1972 return ('/'); 1973 #endif 1974 if (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) 1975 return ('*'); 1976 } 1977 } 1978 return (' '); 1979 } /* end filetype */ 1980 1981 1982 /* isadirectory(): 1983 * Return trus if the file is a directory 1984 */ 1985 static int 1986 isadirectory(dir, file) /* return 1 if dir/file is a directory */ 1987 Char *dir, *file; /* uses stat rather than lstat to get dest. */ 1988 { 1989 if (dir) { 1990 Char path[MAXPATHLEN]; 1991 struct stat statb; 1992 1993 (void) Strcpy(path, dir); 1994 catn(path, file, (int) (sizeof(path) / sizeof(Char))); 1995 if (stat(short2str(path), &statb) >= 0) { /* resolve through 1996 * symlink */ 1997 #ifdef S_ISSOCK 1998 if (S_ISSOCK(statb.st_mode)) /* Socket */ 1999 return 0; 2000 #endif 2001 #ifdef S_ISFIFO 2002 if (S_ISFIFO(statb.st_mode)) /* Named Pipe */ 2003 return 0; 2004 #endif 2005 if (S_ISDIR(statb.st_mode)) /* normal Directory */ 2006 return 1; 2007 } 2008 } 2009 return 0; 2010 } /* end isadirectory */ 2011 2012 2013 2014 /* find_rows(): 2015 * Return how many rows needed to print sorted down columns 2016 */ 2017 static int 2018 find_rows(items, count, no_file_suffix) 2019 Char *items[]; 2020 int count, no_file_suffix; 2021 { 2022 register int i, columns, rows; 2023 unsigned int maxwidth = 0; 2024 2025 for (i = 0; i < count; i++) /* find widest string */ 2026 maxwidth = max(maxwidth, (unsigned int) Strlen(items[i])); 2027 2028 maxwidth += no_file_suffix ? 1 : 2; /* for the file tag and space */ 2029 columns = (TermH + 1) / maxwidth; /* PWP: terminal size change */ 2030 if (!columns) 2031 columns = 1; 2032 rows = (count + (columns - 1)) / columns; 2033 2034 return rows; 2035 } /* end rows_needed_by_print_by_column */ 2036 2037 2038 /* print_by_column(): 2039 * Print sorted down columns or across columns when the first 2040 * word of $listflags shell variable contains 'x'. 2041 * 2042 */ 2043 void 2044 print_by_column(dir, items, count, no_file_suffix) 2045 register Char *dir, *items[]; 2046 int count, no_file_suffix; 2047 { 2048 register int i, r, c, columns, rows; 2049 unsigned int w, maxwidth = 0; 2050 Char *val; 2051 bool across; 2052 2053 lbuffed = 0; /* turn off line buffering */ 2054 2055 2056 across = ((val = varval(STRlistflags)) != STRNULL) && 2057 (Strchr(val, 'x') != NULL); 2058 2059 for (i = 0; i < count; i++) /* find widest string */ 2060 maxwidth = max(maxwidth, (unsigned int) Strlen(items[i])); 2061 2062 maxwidth += no_file_suffix ? 1 : 2; /* for the file tag and space */ 2063 columns = TermH / maxwidth; /* PWP: terminal size change */ 2064 if (!columns || !isatty(didfds ? 1 : SHOUT)) 2065 columns = 1; 2066 rows = (count + (columns - 1)) / columns; 2067 2068 i = -1; 2069 for (r = 0; r < rows; r++) { 2070 for (c = 0; c < columns; c++) { 2071 i = across ? (i + 1) : (c * rows + r); 2072 2073 if (i < count) { 2074 w = (unsigned int) Strlen(items[i]); 2075 2076 #ifdef COLOR_LS_F 2077 if (no_file_suffix) { 2078 /* Print the command name */ 2079 Char f = items[i][w - 1]; 2080 items[i][w - 1] = 0; 2081 print_with_color(items[i], w - 1, f); 2082 } 2083 else { 2084 /* Print filename followed by '/' or '*' or ' ' */ 2085 print_with_color(items[i], w, filetype(dir, items[i])); 2086 w++; 2087 } 2088 #else /* ifndef COLOR_LS_F */ 2089 if (no_file_suffix) { 2090 /* Print the command name */ 2091 xprintf("%S", items[i]); 2092 } 2093 else { 2094 /* Print filename followed by '/' or '*' or ' ' */ 2095 xprintf("%S%c", items[i], 2096 filetype(dir, items[i])); 2097 w++; 2098 } 2099 #endif /* COLOR_LS_F */ 2100 2101 if (c < (columns - 1)) /* Not last column? */ 2102 for (; w < maxwidth; w++) 2103 xputchar(' '); 2104 } 2105 else if (across) 2106 break; 2107 } 2108 if (Tty_raw_mode) 2109 xputchar('\r'); 2110 xputchar('\n'); 2111 } 2112 2113 lbuffed = 1; /* turn back on line buffering */ 2114 flush(); 2115 } /* end print_by_column */ 2116 2117 2118 /* StrQcmp(): 2119 * Compare strings ignoring the quoting chars 2120 */ 2121 int 2122 StrQcmp(str1, str2) 2123 register Char *str1, *str2; 2124 { 2125 for (; *str1 && samecase(*str1 & TRIM) == samecase(*str2 & TRIM); 2126 str1++, str2++) 2127 continue; 2128 /* 2129 * The following case analysis is necessary so that characters which look 2130 * negative collate low against normal characters but high against the 2131 * end-of-string NUL. 2132 */ 2133 if (*str1 == '\0' && *str2 == '\0') 2134 return (0); 2135 else if (*str1 == '\0') 2136 return (-1); 2137 else if (*str2 == '\0') 2138 return (1); 2139 else 2140 return ((*str1 & TRIM) - (*str2 & TRIM)); 2141 } /* end StrQcmp */ 2142 2143 2144 /* fcompare(): 2145 * Comparison routine for qsort 2146 */ 2147 int 2148 fcompare(file1, file2) 2149 Char **file1, **file2; 2150 { 2151 return (int) collate(*file1, *file2); 2152 } /* end fcompare */ 2153 2154 2155 /* catn(): 2156 * Concatenate src onto tail of des. 2157 * Des is a string whose maximum length is count. 2158 * Always null terminate. 2159 */ 2160 void 2161 catn(des, src, count) 2162 register Char *des, *src; 2163 int count; 2164 { 2165 while (--count >= 0 && *des) 2166 des++; 2167 while (--count >= 0) 2168 if ((*des++ = *src++) == 0) 2169 return; 2170 *des = '\0'; 2171 } /* end catn */ 2172 2173 2174 /* copyn(): 2175 * like strncpy but always leave room for trailing \0 2176 * and always null terminate. 2177 */ 2178 void 2179 copyn(des, src, count) 2180 register Char *des, *src; 2181 int count; 2182 { 2183 while (--count >= 0) 2184 if ((*des++ = *src++) == 0) 2185 return; 2186 *des = '\0'; 2187 } /* end copyn */ 2188 2189 2190 /* tgetenv(): 2191 * like it's normal string counter-part 2192 * [apollo uses that in tc.os.c, so it cannot be static] 2193 */ 2194 Char * 2195 tgetenv(str) 2196 Char *str; 2197 { 2198 Char **var; 2199 int len, res; 2200 2201 len = (int) Strlen(str); 2202 /* Search the STR_environ for the entry matching str. */ 2203 for (var = STR_environ; var != NULL && *var != NULL; var++) 2204 if (Strlen(*var) >= len && (*var)[len] == '=') { 2205 /* Temporarily terminate the string so we can copy the variable 2206 name. */ 2207 (*var)[len] = '\0'; 2208 res = StrQcmp(*var, str); 2209 /* Restore the '=' and return a pointer to the value of the 2210 environment variable. */ 2211 (*var)[len] = '='; 2212 if (res == 0) 2213 return (&((*var)[len + 1])); 2214 } 2215 return (NULL); 2216 } /* end tgetenv */ 2217 2218 2219 struct scroll_tab_list *scroll_tab = 0; 2220 2221 static void 2222 add_scroll_tab(item) 2223 Char *item; 2224 { 2225 struct scroll_tab_list *new_scroll; 2226 2227 new_scroll = (struct scroll_tab_list *) xmalloc((size_t) 2228 sizeof(struct scroll_tab_list)); 2229 new_scroll->element = Strsave(item); 2230 new_scroll->next = scroll_tab; 2231 scroll_tab = new_scroll; 2232 } 2233 2234 static void 2235 choose_scroll_tab(exp_name, cnt) 2236 Char **exp_name; 2237 int cnt; 2238 { 2239 struct scroll_tab_list *loop; 2240 int tmp = cnt; 2241 Char **ptr; 2242 2243 ptr = (Char **) xmalloc((size_t) sizeof(Char *) * cnt); 2244 2245 for(loop = scroll_tab; loop && (tmp >= 0); loop = loop->next) 2246 ptr[--tmp] = loop->element; 2247 2248 qsort((ptr_t) ptr, (size_t) cnt, sizeof(Char *), 2249 (int (*) __P((const void *, const void *))) fcompare); 2250 2251 copyn(*exp_name, ptr[curchoice], (int) Strlen(ptr[curchoice])); 2252 xfree((ptr_t) ptr); 2253 } 2254 2255 static void 2256 free_scroll_tab() 2257 { 2258 struct scroll_tab_list *loop; 2259 2260 while(scroll_tab) { 2261 loop = scroll_tab; 2262 scroll_tab = scroll_tab->next; 2263 xfree((ptr_t) loop->element); 2264 xfree((ptr_t) loop); 2265 } 2266 } 2267