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