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