1/* $OpenBSD: file.c,v 1.40 2020/10/06 01:40:43 deraadt Exp $ */ 2/* $NetBSD: file.c,v 1.11 1996/11/08 19:34:37 christos Exp $ */ 3 4/*- 5 * Copyright (c) 1980, 1991, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33#include <sys/ioctl.h> 34#include <sys/stat.h> 35#include <sys/types.h> 36 37#include <dirent.h> 38#include <errno.h> 39#include <limits.h> 40#include <pwd.h> 41#include <stdlib.h> 42#include <string.h> 43#include <termios.h> 44#include <unistd.h> 45 46#include "csh.h" 47#include "extern.h" 48 49/* 50 * Tenex style file name recognition, .. and more. 51 * History: 52 * Author: Ken Greer, Sept. 1975, CMU. 53 * Finally got around to adding to the Cshell., Ken Greer, Dec. 1981. 54 */ 55 56#ifndef TRUE 57#define TRUE 1 58#endif 59#ifndef FALSE 60#define FALSE 0 61#endif 62 63#define ESC '\033' 64#define TABWIDTH 8 65 66typedef enum { 67 LIST, 68 RECOGNIZE 69} COMMAND; 70 71struct cmdline { 72 int fdin; 73 int fdout; 74 int istty; 75 int flags; 76#define CL_ALTWERASE 0x1 77#define CL_PROMPT 0x2 78 char *buf; 79 size_t len; 80 size_t size; 81 size_t cursor; 82}; 83 84/* Command line auxiliary functions. */ 85static void cl_beep(struct cmdline *); 86static void cl_flush(struct cmdline *); 87static int cl_getc(struct cmdline *); 88static Char *cl_lastw(struct cmdline *); 89static void cl_putc(struct cmdline *, int); 90static void cl_visc(struct cmdline *, int); 91 92/* Command line editing functions. */ 93static int cl_abort(struct cmdline *, int); 94static int cl_erasec(struct cmdline *, int); 95static int cl_erasew(struct cmdline *, int); 96static int cl_insert(struct cmdline *, int); 97static int cl_kill(struct cmdline *, int); 98static int cl_list(struct cmdline *, int); 99static int cl_literal(struct cmdline *, int); 100static int cl_recognize(struct cmdline *, int); 101static int cl_reprint(struct cmdline *, int); 102static int cl_status(struct cmdline *, int); 103 104static const struct termios *setup_tty(int); 105 106static void catn(Char *, Char *, int); 107static void copyn(Char *, Char *, int); 108static Char filetype(Char *, Char *); 109static void print_by_column(Char *, Char *[], int); 110static Char *tilde(Char *, Char *); 111static void extract_dir_and_name(Char *, Char *, Char *); 112static Char *getentry(DIR *, int); 113static void free_items(Char **, int); 114static int tsearch(Char *, COMMAND, int); 115static int recognize(Char *, Char *, int, int); 116static int is_prefix(Char *, Char *); 117static int is_suffix(Char *, Char *); 118static int ignored(Char *); 119 120/* 121 * Put this here so the binary can be patched with adb to enable file 122 * completion by default. Filec controls completion, nobeep controls 123 * ringing the terminal bell on incomplete expansions. 124 */ 125bool filec = 0; 126 127static void 128cl_flush(struct cmdline *cl) 129{ 130 size_t i, len; 131 int c; 132 133 if (cl->flags & CL_PROMPT) { 134 cl->flags &= ~CL_PROMPT; 135 printprompt(); 136 } 137 138 if (cl->cursor < cl->len) { 139 for (; cl->cursor < cl->len; cl->cursor++) 140 cl_visc(cl, cl->buf[cl->cursor]); 141 } else if (cl->cursor > cl->len) { 142 len = cl->cursor - cl->len; 143 for (i = len; i > 0; i--) { 144 c = cl->buf[--cl->cursor]; 145 if (c == '\t') 146 len += TABWIDTH - 1; 147 else if (iscntrl(c)) 148 len++; /* account for leading ^ */ 149 } 150 for (i = 0; i < len; i++) 151 cl_putc(cl, '\b'); 152 for (i = 0; i < len; i++) 153 cl_putc(cl, ' '); 154 for (i = 0; i < len; i++) 155 cl_putc(cl, '\b'); 156 cl->cursor = cl->len; 157 } 158} 159 160static int 161cl_getc(struct cmdline *cl) 162{ 163 ssize_t n; 164 unsigned char c; 165 166 for (;;) { 167 n = read(cl->fdin, &c, 1); 168 switch (n) { 169 case -1: 170 if (errno == EINTR) 171 continue; 172 /* FALLTHROUGH */ 173 case 0: 174 return 0; 175 default: 176 return c & 0x7F; 177 } 178 } 179} 180 181static Char * 182cl_lastw(struct cmdline *cl) 183{ 184 static Char word[BUFSIZ]; 185 const unsigned char *delimiters = " '\"\t;&<>()|^%"; 186 Char *cp; 187 size_t i; 188 189 for (i = cl->len; i > 0; i--) 190 if (strchr(delimiters, cl->buf[i - 1]) != NULL) 191 break; 192 193 cp = word; 194 for (; i < cl->len; i++) 195 *cp++ = cl->buf[i]; 196 *cp = '\0'; 197 198 return word; 199} 200 201static void 202cl_putc(struct cmdline *cl, int c) 203{ 204 unsigned char cc = c; 205 206 write(cl->fdout, &cc, 1); 207} 208 209static void 210cl_visc(struct cmdline *cl, int c) 211{ 212#define UNCNTRL(x) ((x) == 0x7F ? '?' : ((x) | 0x40)) 213 int i; 214 215 if (c == '\t') { 216 for (i = 0; i < TABWIDTH; i++) 217 cl_putc(cl, ' '); 218 } else if (c != '\n' && iscntrl(c)) { 219 cl_putc(cl, '^'); 220 cl_putc(cl, UNCNTRL(c)); 221 } else { 222 cl_putc(cl, c); 223 } 224} 225 226static int 227cl_abort(struct cmdline *cl, int c) 228{ 229 cl_visc(cl, c); 230 231 /* Abort while/foreach loop prematurely. */ 232 if (whyles) { 233 if (cl->istty) 234 setup_tty(0); 235 kill(getpid(), SIGINT); 236 } 237 238 cl_putc(cl, '\n'); 239 cl->len = cl->cursor = 0; 240 cl->flags |= CL_PROMPT; 241 242 return 0; 243} 244 245static int 246cl_erasec(struct cmdline *cl, int c) 247{ 248 if (cl->len > 0) 249 cl->len--; 250 251 return 0; 252} 253 254static int 255cl_erasew(struct cmdline *cl, int c) 256{ 257 const unsigned char *ws = " \t"; 258 259 for (; cl->len > 0; cl->len--) 260 if (strchr(ws, cl->buf[cl->len - 1]) == NULL && 261 ((cl->flags & CL_ALTWERASE) == 0 || 262 isalpha(cl->buf[cl->len - 1]))) 263 break; 264 for (; cl->len > 0; cl->len--) 265 if (strchr(ws, cl->buf[cl->len - 1]) != NULL || 266 ((cl->flags & CL_ALTWERASE) && 267 !isalpha(cl->buf[cl->len - 1]))) 268 break; 269 270 return 0; 271} 272 273static void 274cl_beep(struct cmdline *cl) 275{ 276 if (adrof(STRnobeep) == 0) 277 cl_putc(cl, '\007'); 278} 279 280static int 281cl_insert(struct cmdline *cl, int c) 282{ 283 if (cl->len == cl->size) 284 return 1; 285 286 cl->buf[cl->len++] = c; 287 288 if (c == '\n') 289 return 1; 290 291 return 0; 292} 293 294static int 295cl_kill(struct cmdline *cl, int c) 296{ 297 cl->len = 0; 298 299 return 0; 300} 301 302static int 303cl_list(struct cmdline *cl, int c) 304{ 305 Char *word; 306 size_t len; 307 308 if (adrof(STRignoreeof) || cl->len > 0) 309 cl_visc(cl, c); 310 311 if (cl->len == 0) 312 return 1; 313 314 cl_putc(cl, '\n'); 315 cl->cursor = 0; 316 cl->flags |= CL_PROMPT; 317 318 word = cl_lastw(cl); 319 len = Strlen(word); 320 tsearch(word, LIST, BUFSIZ - len - 1); /* NUL */ 321 322 return 0; 323} 324 325static int 326cl_literal(struct cmdline *cl, int c) 327{ 328 int literal; 329 330 literal = cl_getc(cl); 331 if (literal == '\n') 332 literal = '\r'; 333 cl_insert(cl, literal); 334 335 return 0; 336} 337 338static int 339cl_recognize(struct cmdline *cl, int c) 340{ 341 Char *word; 342 size_t len; 343 int nitems; 344 345 if (cl->len == 0) { 346 cl_beep(cl); 347 return 0; 348 } 349 350 word = cl_lastw(cl); 351 len = Strlen(word); 352 nitems = tsearch(word, RECOGNIZE, BUFSIZ - len - 1); /* NUL */ 353 for (word += len; *word != '\0'; word++) 354 cl_insert(cl, *word); 355 if (nitems != 1) 356 cl_beep(cl); 357 358 return 0; 359} 360 361static int 362cl_reprint(struct cmdline *cl, int c) 363{ 364 cl_visc(cl, c); 365 cl_putc(cl, '\n'); 366 cl->cursor = 0; 367 368 return 0; 369} 370 371static int 372cl_status(struct cmdline *cl, int c) 373{ 374 cl->cursor = 0; 375 if (cl->istty) 376 ioctl(cl->fdin, TIOCSTAT); 377 378 return 0; 379} 380 381const struct termios * 382setup_tty(int on) 383{ 384 static struct termios newtio, oldtio; 385 386 if (on) { 387 tcgetattr(SHIN, &oldtio); 388 389 newtio = oldtio; 390 newtio.c_lflag &= ~(ECHO | ICANON | ISIG); 391 newtio.c_cc[VEOL] = ESC; 392 newtio.c_cc[VLNEXT] = _POSIX_VDISABLE; 393 newtio.c_cc[VMIN] = 1; 394 newtio.c_cc[VTIME] = 0; 395 } else { 396 newtio = oldtio; 397 } 398 399 tcsetattr(SHIN, TCSADRAIN, &newtio); 400 401 /* 402 * Since VLNEXT is disabled, restore its previous value in order to make 403 * the key detectable. 404 */ 405 newtio.c_cc[VLNEXT] = oldtio.c_cc[VLNEXT]; 406 407 return &newtio; 408} 409 410/* 411 * Concatenate src onto tail of des. 412 * Des is a string whose maximum length is count. 413 * Always null terminate. 414 */ 415static void 416catn(Char *des, Char *src, int count) 417{ 418 while (--count >= 0 && *des) 419 des++; 420 while (--count >= 0) 421 if ((*des++ = *src++) == 0) 422 return; 423 *des = '\0'; 424} 425 426/* 427 * Places Char's like strlcpy, but no special return value. 428 */ 429static void 430copyn(Char *des, Char *src, int count) 431{ 432 while (--count >= 0) 433 if ((*des++ = *src++) == 0) 434 return; 435 *des = '\0'; 436} 437 438static Char 439filetype(Char *dir, Char *file) 440{ 441 Char path[PATH_MAX]; 442 struct stat statb; 443 444 Strlcpy(path, dir, sizeof path/sizeof(Char)); 445 catn(path, file, sizeof(path) / sizeof(Char)); 446 if (lstat(short2str(path), &statb) == 0) { 447 switch (statb.st_mode & S_IFMT) { 448 case S_IFDIR: 449 return ('/'); 450 451 case S_IFLNK: 452 if (stat(short2str(path), &statb) == 0 && /* follow it out */ 453 S_ISDIR(statb.st_mode)) 454 return ('>'); 455 else 456 return ('@'); 457 458 case S_IFSOCK: 459 return ('='); 460 461 default: 462 if (statb.st_mode & 0111) 463 return ('*'); 464 } 465 } 466 return (' '); 467} 468 469/* 470 * Print sorted down columns 471 */ 472static void 473print_by_column(Char *dir, Char *items[], int count) 474{ 475 struct winsize win; 476 int i, rows, r, c, maxwidth = 0, columns; 477 478 if (ioctl(SHOUT, TIOCGWINSZ, (ioctl_t) & win) == -1 || win.ws_col == 0) 479 win.ws_col = 80; 480 for (i = 0; i < count; i++) 481 maxwidth = maxwidth > (r = Strlen(items[i])) ? maxwidth : r; 482 maxwidth += 2; /* for the file tag and space */ 483 columns = win.ws_col / maxwidth; 484 if (columns == 0) 485 columns = 1; 486 rows = (count + (columns - 1)) / columns; 487 for (r = 0; r < rows; r++) { 488 for (c = 0; c < columns; c++) { 489 i = c * rows + r; 490 if (i < count) { 491 int w; 492 493 (void) fprintf(cshout, "%s", vis_str(items[i])); 494 (void) fputc(dir ? filetype(dir, items[i]) : ' ', cshout); 495 if (c < columns - 1) { /* last column? */ 496 w = Strlen(items[i]) + 1; 497 for (; w < maxwidth; w++) 498 (void) fputc(' ', cshout); 499 } 500 } 501 } 502 (void) fputc('\r', cshout); 503 (void) fputc('\n', cshout); 504 } 505} 506 507/* 508 * Expand file name with possible tilde usage 509 * ~person/mumble 510 * expands to 511 * home_directory_of_person/mumble 512 */ 513static Char * 514tilde(Char *new, Char *old) 515{ 516 Char *o, *p; 517 struct passwd *pw; 518 static Char person[40]; 519 520 if (old[0] != '~') { 521 Strlcpy(new, old, PATH_MAX); 522 return new; 523 } 524 525 for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++) 526 continue; 527 *p = '\0'; 528 if (person[0] == '\0') 529 (void) Strlcpy(new, value(STRhome), PATH_MAX); 530 else { 531 pw = getpwnam(short2str(person)); 532 if (pw == NULL) 533 return (NULL); 534 (void) Strlcpy(new, str2short(pw->pw_dir), PATH_MAX); 535 } 536 (void) Strlcat(new, o, PATH_MAX); 537 return (new); 538} 539 540/* 541 * Parse full path in file into 2 parts: directory and file names 542 * Should leave final slash (/) at end of dir. 543 */ 544static void 545extract_dir_and_name(Char *path, Char *dir, Char *name) 546{ 547 Char *p; 548 549 p = Strrchr(path, '/'); 550 if (p == NULL) { 551 copyn(name, path, MAXNAMLEN); 552 dir[0] = '\0'; 553 } 554 else { 555 copyn(name, ++p, MAXNAMLEN); 556 copyn(dir, path, p - path); 557 } 558} 559 560static Char * 561getentry(DIR *dir_fd, int looking_for_lognames) 562{ 563 struct passwd *pw; 564 struct dirent *dirp; 565 566 if (looking_for_lognames) { 567 if ((pw = getpwent()) == NULL) 568 return (NULL); 569 return (str2short(pw->pw_name)); 570 } 571 if ((dirp = readdir(dir_fd)) != NULL) 572 return (str2short(dirp->d_name)); 573 return (NULL); 574} 575 576static void 577free_items(Char **items, int numitems) 578{ 579 int i; 580 581 for (i = 0; i < numitems; i++) 582 free(items[i]); 583 free(items); 584} 585 586#define FREE_ITEMS(items) { \ 587 sigset_t sigset, osigset;\ 588\ 589 sigemptyset(&sigset);\ 590 sigaddset(&sigset, SIGINT);\ 591 sigprocmask(SIG_BLOCK, &sigset, &osigset);\ 592 free_items(items, numitems);\ 593 sigprocmask(SIG_SETMASK, &osigset, NULL);\ 594} 595 596/* 597 * Perform a RECOGNIZE or LIST command on string "word". 598 */ 599static int 600tsearch(Char *word, COMMAND command, int max_word_length) 601{ 602 DIR *dir_fd; 603 int numitems = 0, ignoring = TRUE, nignored = 0; 604 int name_length, looking_for_lognames; 605 Char tilded_dir[PATH_MAX], dir[PATH_MAX]; 606 Char name[MAXNAMLEN + 1], extended_name[MAXNAMLEN + 1]; 607 Char *entry; 608 Char **items = NULL; 609 size_t maxitems = 0; 610 611 looking_for_lognames = (*word == '~') && (Strchr(word, '/') == NULL); 612 if (looking_for_lognames) { 613 (void) setpwent(); 614 copyn(name, &word[1], MAXNAMLEN); /* name sans ~ */ 615 dir_fd = NULL; 616 } 617 else { 618 extract_dir_and_name(word, dir, name); 619 if (tilde(tilded_dir, dir) == 0) 620 return (0); 621 dir_fd = opendir(*tilded_dir ? short2str(tilded_dir) : "."); 622 if (dir_fd == NULL) 623 return (0); 624 } 625 626again: /* search for matches */ 627 name_length = Strlen(name); 628 for (numitems = 0; (entry = getentry(dir_fd, looking_for_lognames)) != NULL;) { 629 if (!is_prefix(name, entry)) 630 continue; 631 /* Don't match . files on null prefix match */ 632 if (name_length == 0 && entry[0] == '.' && 633 !looking_for_lognames) 634 continue; 635 if (command == LIST) { 636 if (numitems >= maxitems) { 637 maxitems += 1024; 638 items = xreallocarray(items, maxitems, sizeof(*items)); 639 } 640 items[numitems] = xreallocarray(NULL, (Strlen(entry) + 1), sizeof(Char)); 641 copyn(items[numitems], entry, MAXNAMLEN); 642 numitems++; 643 } 644 else { /* RECOGNIZE command */ 645 if (ignoring && ignored(entry)) 646 nignored++; 647 else if (recognize(extended_name, 648 entry, name_length, ++numitems)) 649 break; 650 } 651 } 652 if (ignoring && numitems == 0 && nignored > 0) { 653 ignoring = FALSE; 654 nignored = 0; 655 if (looking_for_lognames) 656 (void) setpwent(); 657 else 658 rewinddir(dir_fd); 659 goto again; 660 } 661 662 if (looking_for_lognames) 663 (void) endpwent(); 664 else 665 (void) closedir(dir_fd); 666 if (numitems == 0) 667 return (0); 668 if (command == RECOGNIZE) { 669 if (looking_for_lognames) 670 copyn(word, STRtilde, 1); 671 else 672 /* put back dir part */ 673 copyn(word, dir, max_word_length); 674 /* add extended name */ 675 catn(word, extended_name, max_word_length); 676 return (numitems); 677 } 678 else { /* LIST */ 679 qsort(items, numitems, sizeof(*items), sortscmp); 680 print_by_column(looking_for_lognames ? NULL : tilded_dir, 681 items, numitems); 682 if (items != NULL) 683 FREE_ITEMS(items); 684 } 685 return (0); 686} 687 688/* 689 * Object: extend what user typed up to an ambiguity. 690 * Algorithm: 691 * On first match, copy full entry (assume it'll be the only match) 692 * On subsequent matches, shorten extended_name to the first 693 * Character mismatch between extended_name and entry. 694 * If we shorten it back to the prefix length, stop searching. 695 */ 696static int 697recognize(Char *extended_name, Char *entry, int name_length, int numitems) 698{ 699 if (numitems == 1) /* 1st match */ 700 copyn(extended_name, entry, MAXNAMLEN); 701 else { /* 2nd & subsequent matches */ 702 Char *x, *ent; 703 int len = 0; 704 705 x = extended_name; 706 for (ent = entry; *x && *x == *ent++; x++, len++) 707 continue; 708 *x = '\0'; /* Shorten at 1st Char diff */ 709 if (len == name_length) /* Ambiguous to prefix? */ 710 return (-1); /* So stop now and save time */ 711 } 712 return (0); 713} 714 715/* 716 * Return true if check matches initial Chars in template. 717 * This differs from PWB imatch in that if check is null 718 * it matches anything. 719 */ 720static int 721is_prefix(Char *check, Char *template) 722{ 723 do 724 if (*check == 0) 725 return (TRUE); 726 while (*check++ == *template++); 727 return (FALSE); 728} 729 730/* 731 * Return true if the Chars in template appear at the 732 * end of check, I.e., are its suffix. 733 */ 734static int 735is_suffix(Char *check, Char *template) 736{ 737 Char *c, *t; 738 739 for (c = check; *c++;) 740 continue; 741 for (t = template; *t++;) 742 continue; 743 for (;;) { 744 if (t == template) 745 return 1; 746 if (c == check || *--t != *--c) 747 return 0; 748 } 749} 750 751int 752tenex(Char *inputline, int inputline_size) 753{ 754 static struct { 755 int (*fn)(struct cmdline *, int); 756 int idx; 757 } keys[] = { 758 { cl_abort, VINTR }, 759 { cl_erasec, VERASE }, 760 { cl_erasew, VWERASE }, 761 { cl_kill, VKILL }, 762 { cl_list, VEOF }, 763 { cl_literal, VLNEXT }, 764 { cl_recognize, VEOL }, 765 { cl_reprint, VREPRINT }, 766 { cl_status, VSTATUS }, 767 { cl_insert, -1 } 768 }; 769 unsigned char buf[BUFSIZ]; 770 const struct termios *tio; 771 struct cmdline cl; 772 size_t i; 773 int c, ret; 774 775 memset(&cl, 0, sizeof(cl)); 776 cl.fdin = SHIN; 777 cl.fdout = SHOUT; 778 cl.istty = isatty(SHIN); 779 780 if (cl.istty) 781 tio = setup_tty(1); 782 783 cl.buf = buf; 784 cl.size = sizeof(buf); 785 if (inputline_size < cl.size) 786 cl.size = inputline_size; 787 if (cl.istty && tio->c_lflag & ALTWERASE) 788 cl.flags |= CL_ALTWERASE; 789 if (needprompt) { 790 needprompt = 0; 791 cl.flags |= CL_PROMPT; 792 cl_flush(&cl); 793 } 794 795 for (;;) { 796 if ((c = cl_getc(&cl)) == 0) 797 break; 798 799 for (i = 0; keys[i].idx >= 0; i++) 800 if (cl.istty && CCEQ(tio->c_cc[keys[i].idx], c)) 801 break; 802 ret = keys[i].fn(&cl, c); 803 cl_flush(&cl); 804 if (ret) 805 break; 806 } 807 808 if (cl.istty) 809 setup_tty(0); 810 811 for (i = 0; i < cl.len; i++) 812 inputline[i] = cl.buf[i]; 813 /* 814 * NUL-terminating the buffer implies that it contains a complete 815 * command ready to be executed. Therefore, don't terminate if the 816 * buffer is full since more characters must be read in order to form a 817 * complete command. 818 */ 819 if (i < cl.size) 820 inputline[i] = '\0'; 821 822 return cl.len; 823} 824 825static int 826ignored(Char *entry) 827{ 828 struct varent *vp; 829 Char **cp; 830 831 if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL) 832 return (FALSE); 833 for (; *cp != NULL; cp++) 834 if (is_suffix(entry, *cp)) 835 return (TRUE); 836 return (FALSE); 837} 838