1 /* $NetBSD: file.c,v 1.17 2000/05/31 22:48:45 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 1980, 1991, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/cdefs.h> 37 #ifndef lint 38 #if 0 39 static char sccsid[] = "@(#)file.c 8.2 (Berkeley) 3/19/94"; 40 #else 41 __RCSID("$NetBSD: file.c,v 1.17 2000/05/31 22:48:45 christos Exp $"); 42 #endif 43 #endif /* not lint */ 44 45 #ifdef FILEC 46 47 #include <sys/param.h> 48 #include <sys/ioctl.h> 49 #include <sys/stat.h> 50 #include <sys/tty.h> 51 #include <termios.h> 52 #include <dirent.h> 53 #include <pwd.h> 54 #include <stdlib.h> 55 #include <unistd.h> 56 #ifndef SHORT_STRINGS 57 #include <string.h> 58 #endif /* SHORT_STRINGS */ 59 #if __STDC__ 60 # include <stdarg.h> 61 #else 62 # include <varargs.h> 63 #endif 64 65 #include "csh.h" 66 #include "extern.h" 67 68 /* 69 * Tenex style file name recognition, .. and more. 70 * History: 71 * Author: Ken Greer, Sept. 1975, CMU. 72 * Finally got around to adding to the Cshell., Ken Greer, Dec. 1981. 73 */ 74 75 #define ON 1 76 #define OFF 0 77 #ifndef TRUE 78 #define TRUE 1 79 #endif 80 #ifndef FALSE 81 #define FALSE 0 82 #endif 83 84 #define ESC '\033' 85 86 typedef enum { 87 LIST, RECOGNIZE 88 } COMMAND; 89 90 static void setup_tty __P((int)); 91 static void back_to_col_1 __P((void)); 92 static int pushback __P((Char *)); 93 static void catn __P((Char *, Char *, int)); 94 static void copyn __P((Char *, Char *, int)); 95 static Char filetype __P((Char *, Char *)); 96 static void print_by_column __P((Char *, Char *[], int)); 97 static Char *tilde __P((Char *, Char *)); 98 static void retype __P((void)); 99 static void beep __P((void)); 100 static void print_recognized_stuff __P((Char *)); 101 static void extract_dir_and_name __P((Char *, Char *, Char *)); 102 static Char *getentry __P((DIR *, int)); 103 static void free_items __P((Char **)); 104 static int tsearch __P((Char *, COMMAND, int)); 105 static int recognize __P((Char *, Char *, int, int)); 106 static int is_prefix __P((Char *, Char *)); 107 static int is_suffix __P((Char *, Char *)); 108 static int ignored __P((Char *)); 109 110 /* 111 * Put this here so the binary can be patched with adb to enable file 112 * completion by default. Filec controls completion, nobeep controls 113 * ringing the terminal bell on incomplete expansions. 114 */ 115 bool filec = 0; 116 117 static void 118 setup_tty(on) 119 int on; 120 { 121 struct termios tchars; 122 123 (void) tcgetattr(SHIN, &tchars); 124 125 if (on) { 126 tchars.c_cc[VEOL] = ESC; 127 if (tchars.c_lflag & ICANON) 128 on = TCSADRAIN; 129 else { 130 tchars.c_lflag |= ICANON; 131 on = TCSAFLUSH; 132 } 133 } 134 else { 135 tchars.c_cc[VEOL] = _POSIX_VDISABLE; 136 on = TCSADRAIN; 137 } 138 139 (void) tcsetattr(SHIN, on, &tchars); 140 } 141 142 /* 143 * Move back to beginning of current line 144 */ 145 static void 146 back_to_col_1() 147 { 148 struct termios tty, tty_normal; 149 sigset_t sigset, osigset; 150 151 sigemptyset(&sigset); 152 (void) sigaddset(&sigset, SIGINT); 153 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 154 (void) tcgetattr(SHOUT, &tty); 155 tty_normal = tty; 156 tty.c_iflag &= ~INLCR; 157 tty.c_oflag &= ~ONLCR; 158 (void) tcsetattr(SHOUT, TCSADRAIN, &tty); 159 (void) write(SHOUT, "\r", 1); 160 (void) tcsetattr(SHOUT, TCSADRAIN, &tty_normal); 161 (void) sigprocmask(SIG_SETMASK, &osigset, NULL); 162 } 163 164 /* 165 * Push string contents back into tty queue 166 */ 167 static int 168 pushback(string) 169 Char *string; 170 { 171 Char *p; 172 struct termios tty, tty_normal; 173 sigset_t sigset, osigset; 174 char c; 175 char buf[TTYHOG]; 176 int nbuf, bufidx; 177 char svchars[TTYHOG]; 178 int nsv = 0, onsv; 179 int len_str, i; 180 int retrycnt; 181 182 sigemptyset(&sigset); 183 (void) sigaddset(&sigset, SIGINT); 184 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 185 (void) tcgetattr(SHOUT, &tty); 186 tty_normal = tty; 187 tty.c_lflag &= ~(ECHOKE | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOCTL); 188 /* FIONREAD works only in noncanonical mode. */ 189 tty.c_lflag &= ~ICANON; 190 tty.c_cc[VMIN] = 0; 191 (void) tcsetattr(SHOUT, TCSADRAIN, &tty); 192 193 for (retrycnt = 5; ; retrycnt--) { 194 /* 195 * Push back characters. 196 */ 197 for (p = string; (c = *p) != '\0'; p++) 198 (void) ioctl(SHOUT, TIOCSTI, (ioctl_t) &c); 199 for (i = 0; i < nsv; i++) 200 (void) ioctl(SHOUT, TIOCSTI, (ioctl_t) &svchars[i]); 201 202 if (retrycnt == 0) 203 break; /* give up salvaging characters */ 204 205 len_str = p - string; 206 207 if (ioctl(SHOUT, FIONREAD, (ioctl_t) &nbuf) || 208 nbuf <= len_str + nsv || /* The string fit. */ 209 nbuf > TTYHOG) /* For future binary compatibility 210 (and safety). */ 211 break; 212 213 /* 214 * User has typed characters before the pushback finished. 215 * Salvage the characters. 216 */ 217 218 /* This read() should be in noncanonical mode. */ 219 if (read(SHOUT, &buf, nbuf) != nbuf) 220 continue; /* hangup? */ 221 222 onsv = nsv; 223 for (bufidx = 0, i = 0; bufidx < nbuf; bufidx++, i++) { 224 c = buf[bufidx]; 225 if ((i < len_str) ? c != (char) string[i] : 226 (i < len_str + onsv) ? c != svchars[i - len_str] : 1) { 227 /* Salvage a character. */ 228 if (nsv < (int) (sizeof svchars / sizeof svchars[0])) { 229 svchars[nsv++] = c; 230 i--; /* try this comparison with the next char */ 231 } else 232 break; /* too many */ 233 } 234 } 235 } 236 237 #if 1 238 /* 239 * XXX Is this a bug or a feature of kernel tty driver? 240 * 241 * FIONREAD in canonical mode does not return correct byte count 242 * in tty input queue, but this is required to avoid unwanted echo. 243 */ 244 tty.c_lflag |= ICANON; 245 (void) tcsetattr(SHOUT, TCSADRAIN, &tty); 246 (void) ioctl(SHOUT, FIONREAD, (ioctl_t) &i); 247 #endif 248 (void) tcsetattr(SHOUT, TCSADRAIN, &tty_normal); 249 (void) sigprocmask(SIG_SETMASK, &osigset, NULL); 250 251 return nsv; 252 } 253 254 /* 255 * Concatenate src onto tail of des. 256 * Des is a string whose maximum length is count. 257 * Always null terminate. 258 */ 259 static void 260 catn(des, src, count) 261 Char *des, *src; 262 int count; 263 { 264 while (--count >= 0 && *des) 265 des++; 266 while (--count >= 0) 267 if ((*des++ = *src++) == 0) 268 return; 269 *des = '\0'; 270 } 271 272 /* 273 * Like strncpy but always leave room for trailing \0 274 * and always null terminate. 275 */ 276 static void 277 copyn(des, src, count) 278 Char *des, *src; 279 int count; 280 { 281 while (--count >= 0) 282 if ((*des++ = *src++) == 0) 283 return; 284 *des = '\0'; 285 } 286 287 static Char 288 filetype(dir, file) 289 Char *dir, *file; 290 { 291 Char path[MAXPATHLEN]; 292 struct stat statb; 293 294 catn(Strcpy(path, dir), file, sizeof(path) / sizeof(Char)); 295 if (lstat(short2str(path), &statb) == 0) { 296 switch (statb.st_mode & S_IFMT) { 297 case S_IFDIR: 298 return ('/'); 299 300 case S_IFLNK: 301 if (stat(short2str(path), &statb) == 0 && /* follow it out */ 302 S_ISDIR(statb.st_mode)) 303 return ('>'); 304 else 305 return ('@'); 306 307 case S_IFSOCK: 308 return ('='); 309 310 default: 311 if (statb.st_mode & 0111) 312 return ('*'); 313 } 314 } 315 return (' '); 316 } 317 318 static struct winsize win; 319 320 /* 321 * Print sorted down columns 322 */ 323 static void 324 print_by_column(dir, items, count) 325 Char *dir, *items[]; 326 int count; 327 { 328 int i, rows, r, c, maxwidth = 0, columns; 329 330 if (ioctl(SHOUT, TIOCGWINSZ, (ioctl_t) & win) < 0 || win.ws_col == 0) 331 win.ws_col = 80; 332 for (i = 0; i < count; i++) 333 maxwidth = maxwidth > (r = Strlen(items[i])) ? maxwidth : r; 334 maxwidth += 2; /* for the file tag and space */ 335 columns = win.ws_col / maxwidth; 336 if (columns == 0) 337 columns = 1; 338 rows = (count + (columns - 1)) / columns; 339 for (r = 0; r < rows; r++) { 340 for (c = 0; c < columns; c++) { 341 i = c * rows + r; 342 if (i < count) { 343 int w; 344 345 (void) fprintf(cshout, "%s", vis_str(items[i])); 346 (void) fputc(dir ? filetype(dir, items[i]) : ' ', cshout); 347 if (c < columns - 1) { /* last column? */ 348 w = Strlen(items[i]) + 1; 349 for (; w < maxwidth; w++) 350 (void) fputc(' ', cshout); 351 } 352 } 353 } 354 (void) fputc('\r', cshout); 355 (void) fputc('\n', cshout); 356 } 357 } 358 359 /* 360 * Expand file name with possible tilde usage 361 * ~person/mumble 362 * expands to 363 * home_directory_of_person/mumble 364 */ 365 static Char * 366 tilde(new, old) 367 Char *new, *old; 368 { 369 Char *o, *p; 370 struct passwd *pw; 371 static Char person[40]; 372 373 if (old[0] != '~') 374 return (Strcpy(new, old)); 375 376 for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++) 377 continue; 378 *p = '\0'; 379 if (person[0] == '\0') 380 (void) Strcpy(new, value(STRhome)); 381 else { 382 pw = getpwnam(short2str(person)); 383 if (pw == NULL) 384 return (NULL); 385 (void) Strcpy(new, str2short(pw->pw_dir)); 386 } 387 (void) Strcat(new, o); 388 return (new); 389 } 390 391 /* 392 * Cause pending line to be printed 393 */ 394 static void 395 retype() 396 { 397 struct termios tty; 398 399 (void) tcgetattr(SHOUT, &tty); 400 tty.c_lflag |= PENDIN; 401 (void) tcsetattr(SHOUT, TCSADRAIN, &tty); 402 } 403 404 static void 405 beep() 406 { 407 if (adrof(STRnobeep) == 0) 408 (void) write(SHOUT, "\007", 1); 409 } 410 411 /* 412 * Erase that silly ^[ and 413 * print the recognized part of the string 414 */ 415 static void 416 print_recognized_stuff(recognized_part) 417 Char *recognized_part; 418 { 419 /* An optimized erasing of that silly ^[ */ 420 (void) fputc('\b', cshout); 421 (void) fputc('\b', cshout); 422 switch (Strlen(recognized_part)) { 423 424 case 0: /* erase two Characters: ^[ */ 425 (void) fputc(' ', cshout); 426 (void) fputc(' ', cshout); 427 (void) fputc('\b', cshout); 428 (void) fputc('\b', cshout); 429 break; 430 431 case 1: /* overstrike the ^, erase the [ */ 432 (void) fprintf(cshout, "%s", vis_str(recognized_part)); 433 (void) fputc(' ', cshout); 434 (void) fputc('\b', cshout); 435 break; 436 437 default: /* overstrike both Characters ^[ */ 438 (void) fprintf(cshout, "%s", vis_str(recognized_part)); 439 break; 440 } 441 (void) fflush(cshout); 442 } 443 444 /* 445 * Parse full path in file into 2 parts: directory and file names 446 * Should leave final slash (/) at end of dir. 447 */ 448 static void 449 extract_dir_and_name(path, dir, name) 450 Char *path, *dir, *name; 451 { 452 Char *p; 453 454 p = Strrchr(path, '/'); 455 if (p == NULL) { 456 copyn(name, path, MAXNAMLEN); 457 dir[0] = '\0'; 458 } 459 else { 460 copyn(name, ++p, MAXNAMLEN); 461 copyn(dir, path, p - path); 462 } 463 } 464 465 static Char * 466 getentry(dir_fd, looking_for_lognames) 467 DIR *dir_fd; 468 int looking_for_lognames; 469 { 470 struct passwd *pw; 471 struct dirent *dirp; 472 473 if (looking_for_lognames) { 474 if ((pw = getpwent()) == NULL) 475 return (NULL); 476 return (str2short(pw->pw_name)); 477 } 478 if ((dirp = readdir(dir_fd)) != NULL) 479 return (str2short(dirp->d_name)); 480 return (NULL); 481 } 482 483 static void 484 free_items(items) 485 Char **items; 486 { 487 int i; 488 489 for (i = 0; items[i]; i++) 490 xfree((ptr_t) items[i]); 491 xfree((ptr_t) items); 492 } 493 494 #define FREE_ITEMS(items) { \ 495 sigset_t sigset, osigset;\ 496 \ 497 sigemptyset(&sigset);\ 498 (void) sigaddset(&sigset, SIGINT);\ 499 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset);\ 500 free_items(items);\ 501 items = NULL;\ 502 (void) sigprocmask(SIG_SETMASK, &osigset, NULL);\ 503 } 504 505 /* 506 * Perform a RECOGNIZE or LIST command on string "word". 507 */ 508 static int 509 tsearch(word, command, max_word_length) 510 Char *word; 511 COMMAND command; 512 int max_word_length; 513 { 514 static Char **items = NULL; 515 DIR *dir_fd; 516 int numitems = 0, ignoring = TRUE, nignored = 0; 517 int name_length, looking_for_lognames; 518 Char tilded_dir[MAXPATHLEN + 1], dir[MAXPATHLEN + 1]; 519 Char name[MAXNAMLEN + 1], extended_name[MAXNAMLEN + 1]; 520 Char *entry; 521 522 #define MAXITEMS 1024 523 524 if (items != NULL) 525 FREE_ITEMS(items); 526 527 looking_for_lognames = (*word == '~') && (Strchr(word, '/') == NULL); 528 if (looking_for_lognames) { 529 (void) setpwent(); 530 copyn(name, &word[1], MAXNAMLEN); /* name sans ~ */ 531 dir_fd = NULL; 532 } 533 else { 534 extract_dir_and_name(word, dir, name); 535 if (tilde(tilded_dir, dir) == 0) 536 return (0); 537 dir_fd = opendir(*tilded_dir ? short2str(tilded_dir) : "."); 538 if (dir_fd == NULL) 539 return (0); 540 } 541 542 again: /* search for matches */ 543 name_length = Strlen(name); 544 for (numitems = 0; (entry = getentry(dir_fd, looking_for_lognames)) != NULL;) { 545 if (!is_prefix(name, entry)) 546 continue; 547 /* Don't match . files on null prefix match */ 548 if (name_length == 0 && entry[0] == '.' && 549 !looking_for_lognames) 550 continue; 551 if (command == LIST) { 552 if (numitems >= MAXITEMS) { 553 (void) fprintf(csherr, "\nYikes!! Too many %s!!\n", 554 looking_for_lognames ? 555 "names in password file" : "files"); 556 break; 557 } 558 if (items == NULL) 559 items = (Char **) xcalloc(sizeof(items[0]), MAXITEMS); 560 items[numitems] = (Char *) xmalloc((size_t) (Strlen(entry) + 1) * 561 sizeof(Char)); 562 copyn(items[numitems], entry, MAXNAMLEN); 563 numitems++; 564 } 565 else { /* RECOGNIZE command */ 566 if (ignoring && ignored(entry)) 567 nignored++; 568 else if (recognize(extended_name, 569 entry, name_length, ++numitems)) 570 break; 571 } 572 } 573 if (ignoring && numitems == 0 && nignored > 0) { 574 ignoring = FALSE; 575 nignored = 0; 576 if (looking_for_lognames) 577 (void) setpwent(); 578 else 579 rewinddir(dir_fd); 580 goto again; 581 } 582 583 if (looking_for_lognames) 584 (void) endpwent(); 585 else 586 (void) closedir(dir_fd); 587 if (numitems == 0) 588 return (0); 589 if (command == RECOGNIZE) { 590 if (looking_for_lognames) 591 copyn(word, STRtilde, 1); 592 else 593 /* put back dir part */ 594 copyn(word, dir, max_word_length); 595 /* add extended name */ 596 catn(word, extended_name, max_word_length); 597 return (numitems); 598 } 599 else { /* LIST */ 600 qsort((ptr_t) items, numitems, sizeof(items[0]), 601 (int (*) __P((const void *, const void *))) sortscmp); 602 print_by_column(looking_for_lognames ? NULL : tilded_dir, 603 items, numitems); 604 if (items != NULL) 605 FREE_ITEMS(items); 606 } 607 return (0); 608 } 609 610 /* 611 * Object: extend what user typed up to an ambiguity. 612 * Algorithm: 613 * On first match, copy full entry (assume it'll be the only match) 614 * On subsequent matches, shorten extended_name to the first 615 * Character mismatch between extended_name and entry. 616 * If we shorten it back to the prefix length, stop searching. 617 */ 618 static int 619 recognize(extended_name, entry, name_length, numitems) 620 Char *extended_name, *entry; 621 int name_length, numitems; 622 { 623 if (numitems == 1) /* 1st match */ 624 copyn(extended_name, entry, MAXNAMLEN); 625 else { /* 2nd & subsequent matches */ 626 Char *x, *ent; 627 int len = 0; 628 629 x = extended_name; 630 for (ent = entry; *x && *x == *ent++; x++, len++) 631 continue; 632 *x = '\0'; /* Shorten at 1st Char diff */ 633 if (len == name_length) /* Ambiguous to prefix? */ 634 return (-1); /* So stop now and save time */ 635 } 636 return (0); 637 } 638 639 /* 640 * Return true if check matches initial Chars in template. 641 * This differs from PWB imatch in that if check is null 642 * it matches anything. 643 */ 644 static int 645 is_prefix(check, template) 646 Char *check, *template; 647 { 648 do 649 if (*check == 0) 650 return (TRUE); 651 while (*check++ == *template++); 652 return (FALSE); 653 } 654 655 /* 656 * Return true if the Chars in template appear at the 657 * end of check, I.e., are it's suffix. 658 */ 659 static int 660 is_suffix(check, template) 661 Char *check, *template; 662 { 663 Char *c, *t; 664 665 for (c = check; *c++;) 666 continue; 667 for (t = template; *t++;) 668 continue; 669 for (;;) { 670 if (t == template) 671 return 1; 672 if (c == check || *--t != *--c) 673 return 0; 674 } 675 } 676 677 int 678 tenex(inputline, inputline_size) 679 Char *inputline; 680 int inputline_size; 681 { 682 int numitems, num_read; 683 char tinputline[BUFSIZE]; 684 685 686 setup_tty(ON); 687 688 while ((num_read = read(SHIN, tinputline, BUFSIZE)) > 0) { 689 int i; 690 static Char delims[] = {' ', '\'', '"', '\t', ';', '&', '<', 691 '>', '(', ')', '|', '^', '%', '\0'}; 692 Char *str_end, *word_start, last_Char, should_retype; 693 int space_left; 694 COMMAND command; 695 696 for (i = 0; i < num_read; i++) 697 inputline[i] = (unsigned char) tinputline[i]; 698 last_Char = inputline[num_read - 1] & ASCII; 699 700 if (last_Char == '\n' || num_read == inputline_size) 701 break; 702 command = (last_Char == ESC) ? RECOGNIZE : LIST; 703 if (command == LIST) 704 (void) fputc('\n', cshout); 705 str_end = &inputline[num_read]; 706 if (last_Char == ESC) 707 --str_end; /* wipeout trailing cmd Char */ 708 *str_end = '\0'; 709 /* 710 * Find LAST occurence of a delimiter in the inputline. The word start 711 * is one Character past it. 712 */ 713 for (word_start = str_end; word_start > inputline; --word_start) 714 if (Strchr(delims, word_start[-1])) 715 break; 716 space_left = inputline_size - (word_start - inputline) - 1; 717 numitems = tsearch(word_start, command, space_left); 718 719 if (command == RECOGNIZE) { 720 /* print from str_end on */ 721 print_recognized_stuff(str_end); 722 if (numitems != 1) /* Beep = No match/ambiguous */ 723 beep(); 724 } 725 726 /* 727 * Tabs in the input line cause trouble after a pushback. tty driver 728 * won't backspace over them because column positions are now 729 * incorrect. This is solved by retyping over current line. 730 */ 731 should_retype = FALSE; 732 if (Strchr(inputline, '\t')) { /* tab Char in input line? */ 733 back_to_col_1(); 734 should_retype = TRUE; 735 } 736 if (command == LIST) /* Always retype after a LIST */ 737 should_retype = TRUE; 738 if (pushback(inputline)) 739 should_retype = TRUE; 740 if (should_retype) { 741 if (command == RECOGNIZE) 742 (void) fputc('\n', cshout); 743 printprompt(); 744 } 745 if (should_retype) 746 retype(); 747 } 748 setup_tty(OFF); 749 return (num_read); 750 } 751 752 static int 753 ignored(entry) 754 Char *entry; 755 { 756 struct varent *vp; 757 Char **cp; 758 759 if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL) 760 return (FALSE); 761 for (; *cp != NULL; cp++) 762 if (is_suffix(entry, *cp)) 763 return (TRUE); 764 return (FALSE); 765 } 766 #endif /* FILEC */ 767