xref: /illumos-gate/usr/src/cmd/csh/sh.file.c (revision e3ae4b35c024af1196582063ecee3ab79367227d)
1 /*
2  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
7 /*	  All Rights Reserved  	*/
8 
9 /*
10  * Copyright (c) 1980 Regents of the University of California.
11  * All rights reserved.  The Berkeley Software License Agreement
12  * specifies the terms and conditions for redistribution.
13  */
14 
15 #ifdef FILEC
16 /*
17  * Tenex style file name recognition, .. and more.
18  * History:
19  *	Author: Ken Greer, Sept. 1975, CMU.
20  *	Finally got around to adding to the Cshell., Ken Greer, Dec. 1981.
21  */
22 
23 #include "sh.h"
24 #include <sys/types.h>
25 #include <dirent.h>
26 #include <pwd.h>
27 #include "sh.tconst.h"
28 
29 #define	TRUE	1
30 #define	FALSE	0
31 #define	ON	1
32 #define	OFF	0
33 
34 #define	ESC	'\033'
35 
36 extern DIR *opendir_(tchar *);
37 
38 static char *BELL = "\07";
39 static char *CTRLR = "^R\n";
40 
41 typedef enum {LIST, RECOGNIZE} COMMAND;
42 
43 static jmp_buf osetexit;		/* saved setexit() state */
44 static struct termios  tty_save;	/* saved terminal state */
45 static struct termios  tty_new;		/* new terminal state */
46 
47 static int	is_prefix(tchar *, tchar *);
48 static int	is_suffix(tchar *, tchar *);
49 static int	ignored(tchar *);
50 
51 /*
52  * Put this here so the binary can be patched with adb to enable file
53  * completion by default.  Filec controls completion, nobeep controls
54  * ringing the terminal bell on incomplete expansions.
55  */
56 bool filec = 0;
57 
58 static void
59 setup_tty(int on)
60 {
61 	int omask;
62 #ifdef TRACE
63 	tprintf("TRACE- setup_tty()\n");
64 #endif
65 
66 	omask = sigblock(sigmask(SIGINT));
67 	if (on) {
68 		/*
69 		 * The shell makes sure that the tty is not in some weird state
70 		 * and fixes it if it is.  But it should be noted that the
71 		 * tenex routine will not work correctly in CBREAK or RAW mode
72 		 * so this code below is, therefore, mandatory.
73 		 *
74 		 * Also, in order to recognize the ESC (filename-completion)
75 		 * character, set EOL to ESC.  This way, ESC will terminate
76 		 * the line, but still be in the input stream.
77 		 * EOT (filename list) will also terminate the line,
78 		 * but will not appear in the input stream.
79 		 *
80 		 * The getexit/setexit contortions ensure that the
81 		 * tty state will be restored if the user types ^C.
82 		 */
83 		(void) ioctl(SHIN, TCGETS,  (char *)&tty_save);
84 		getexit(osetexit);
85 		if (setjmp(reslab)) {
86 			(void) ioctl(SHIN, TCSETSW,  (char *)&tty_save);
87 			resexit(osetexit);
88 			reset();
89 		}
90 		tty_new = tty_save;
91 		tty_new.c_cc[VEOL] = ESC;
92 		tty_new.c_iflag |= IMAXBEL | BRKINT | IGNPAR;
93 		tty_new.c_lflag |= ICANON;
94 		tty_new.c_lflag |= ECHOCTL;
95 		tty_new.c_oflag &= ~OCRNL;
96 		(void) ioctl(SHIN, TCSETSW,  (char *)&tty_new);
97 	} else {
98 		/*
99 		 * Reset terminal state to what user had when invoked
100 		 */
101 		(void) ioctl(SHIN, TCSETSW,  (char *)&tty_save);
102 		resexit(osetexit);
103 	}
104 	(void) sigsetmask(omask);
105 }
106 
107 static void
108 termchars(void)
109 {
110 	extern char *tgetstr();
111 	char bp[1024];
112 	static char area[256];
113 	static int been_here = 0;
114 	char *ap = area;
115 	char *s;
116 	char *term;
117 
118 #ifdef TRACE
119 	tprintf("TRACE- termchars()\n");
120 #endif
121 	if (been_here)
122 		return;
123 	been_here = TRUE;
124 
125 	if ((term = getenv("TERM")) == NULL)
126 		return;
127 	if (tgetent(bp, term) != 1)
128 		return;
129 	if (s = tgetstr("vb", &ap))		/* Visible Bell */
130 		BELL = s;
131 }
132 
133 /*
134  * Move back to beginning of current line
135  */
136 static void
137 back_to_col_1(void)
138 {
139 	int omask;
140 
141 #ifdef TRACE
142 	tprintf("TRACE- back_to_col_1()\n");
143 #endif
144 	omask = sigblock(sigmask(SIGINT));
145 	(void) write(SHOUT, "\r", 1);
146 	(void) sigsetmask(omask);
147 }
148 
149 /*
150  * Push string contents back into tty queue
151  */
152 static void
153 pushback(tchar *string, int echoflag)
154 {
155 	tchar *p;
156 	struct termios tty;
157 	int omask, retry = 0;
158 
159 #ifdef TRACE
160 	tprintf("TRACE- pushback()\n");
161 #endif
162 	omask = sigblock(sigmask(SIGINT));
163 	tty = tty_new;
164 	if (!echoflag)
165 		tty.c_lflag &= ~ECHO;
166 
167 again:
168 	(void) ioctl(SHIN, TCSETSF, (char *)&tty);
169 
170 	for (p = string; *p; p++) {
171 		char	mbc[MB_LEN_MAX];
172 		int	i, j = wctomb(mbc, (wchar_t)*p);
173 
174 		if (j < 0) {
175 			/* Error! But else what can we do? */
176 			continue;
177 		}
178 		for (i = 0; i < j; ++i) {
179 			if (ioctl(SHIN, TIOCSTI, mbc + i) != 0 &&
180 			    errno == EAGAIN) {
181 				if (retry++ < 5)
182 					goto again;
183 				/* probably no worth retrying any more */
184 			}
185 		}
186 	}
187 
188 	if (tty.c_lflag != tty_new.c_lflag)
189 		(void) ioctl(SHIN, TCSETS,  (char *)&tty_new);
190 	(void) sigsetmask(omask);
191 }
192 
193 /*
194  * Concatenate src onto tail of des.
195  * Des is a string whose maximum length is count.
196  * Always null terminate.
197  */
198 void
199 catn(tchar *des, tchar *src, int count)
200 {
201 #ifdef TRACE
202 	tprintf("TRACE- catn()\n");
203 #endif
204 
205 	while (--count >= 0 && *des)
206 		des++;
207 	while (--count >= 0)
208 		if ((*des++ = *src++) == '\0')
209 			return;
210 	*des = '\0';
211 }
212 
213 static int
214 max(a, b)
215 {
216 
217 	return (a > b ? a : b);
218 }
219 
220 /*
221  * Like strncpy but always leave room for trailing \0
222  * and always null terminate.
223  */
224 void
225 copyn(tchar *des, tchar *src, int count)
226 {
227 
228 #ifdef TRACE
229 	tprintf("TRACE- copyn()\n");
230 #endif
231 	while (--count >= 0)
232 		if ((*des++ = *src++) == '\0')
233 			return;
234 	*des = '\0';
235 }
236 
237 /*
238  * For qsort()
239  */
240 static int
241 fcompare(tchar **file1, tchar **file2)
242 {
243 
244 #ifdef TRACE
245 	tprintf("TRACE- fcompare()\n");
246 #endif
247 	return (strcoll_(*file1, *file2));
248 }
249 
250 static char
251 filetype(tchar *dir, tchar *file, int nosym)
252 {
253 	tchar path[MAXPATHLEN + 1];
254 	struct stat statb;
255 
256 #ifdef TRACE
257 	tprintf("TRACE- filetype()\n");
258 #endif
259 	if (dir) {
260 		catn(strcpy_(path, dir), file, MAXPATHLEN);
261 		if (nosym) {
262 			if (stat_(path, &statb) < 0)
263 				return (' ');
264 		} else {
265 			if (lstat_(path, &statb) < 0)
266 				return (' ');
267 		}
268 		if ((statb.st_mode & S_IFMT) == S_IFLNK)
269 			return ('@');
270 		if ((statb.st_mode & S_IFMT) == S_IFDIR)
271 			return ('/');
272 		if (((statb.st_mode & S_IFMT) == S_IFREG) &&
273 		    (statb.st_mode & 011))
274 			return ('*');
275 	}
276 	return (' ');
277 }
278 
279 /*
280  * Print sorted down columns
281  */
282 static void
283 print_by_column(tchar *dir, tchar *items[], int count, int looking_for_command)
284 {
285 	int i, rows, r, c, maxwidth = 0, columns;
286 
287 #ifdef TRACE
288 	tprintf("TRACE- print_by_column()\n");
289 #endif
290 	for (i = 0; i < count; i++)
291 		maxwidth = max(maxwidth, tswidth(items[i]));
292 
293 	/* for the file tag and space */
294 	maxwidth += looking_for_command ? 1 : 2;
295 	columns = max(78 / maxwidth, 1);
296 	rows = (count + (columns - 1)) / columns;
297 
298 	for (r = 0; r < rows; r++) {
299 		for (c = 0; c < columns; c++) {
300 			i = c * rows + r;
301 			if (i < count) {
302 				int w;
303 
304 				/*
305 				 * Print filename followed by
306 				 * '@' or '/' or '*' or ' '
307 				 */
308 				printf("%t", items[i]);
309 				w = tswidth(items[i]);
310 				if (!looking_for_command) {
311 					printf("%c",
312 					    (tchar) filetype(dir, items[i], 0));
313 					w++;
314 				}
315 				if (c < columns - 1)	/* last column? */
316 					for (; w < maxwidth; w++)
317 						printf(" ");
318 			}
319 		}
320 		printf("\n");
321 	}
322 }
323 
324 /*
325  * Expand file name with possible tilde usage
326  *	~person/mumble
327  * expands to
328  *	home_directory_of_person/mumble
329  */
330 tchar *
331 tilde(tchar *new, tchar *old)
332 {
333 	tchar *o, *p;
334 	struct passwd *pw;
335 	static tchar person[40];
336 	char person_[40];		/* work */
337 	tchar *pw_dir;			/* work */
338 
339 #ifdef TRACE
340 	tprintf("TRACE- tilde()\n");
341 #endif
342 	if (old[0] != '~')
343 		return (strcpy_(new, old));
344 
345 	for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++)
346 		;
347 	*p = '\0';
348 	if (person[0] == '\0')
349 		(void) strcpy_(new, value(S_home /* "home" */));
350 	else {
351 		pw = getpwnam(tstostr(person_, person));
352 		if (pw == NULL)
353 			return (NULL);
354 		pw_dir = strtots((tchar *)NULL, pw->pw_dir);	/* allocate */
355 		(void) strcpy_(new, pw_dir);
356 		xfree(pw_dir);					/* free it */
357 	}
358 	(void) strcat_(new, o);
359 	return (new);
360 }
361 
362 /*
363  * Cause pending line to be printed
364  */
365 static void
366 sim_retype(void)
367 {
368 #ifdef notdef
369 	struct termios tty_pending;
370 
371 #ifdef TRACE
372 	tprintf("TRACE- sim_retypr()\n");
373 #endif
374 	tty_pending = tty_new;
375 	tty_pending.c_lflag |= PENDIN;
376 
377 	(void) ioctl(SHIN, TCSETS,  (char *)&tty_pending);
378 #else
379 #ifdef TRACE
380 	tprintf("TRACE- sim_retype()\n");
381 #endif
382 	(void) write(SHOUT, CTRLR, strlen(CTRLR));
383 	printprompt();
384 #endif
385 }
386 
387 static int
388 beep_outc(int c)
389 {
390 	char	buf[1];
391 
392 	buf[0] = c;
393 
394 	(void) write(SHOUT, buf, 1);
395 
396 	return 0;
397 }
398 
399 static void
400 beep(void)
401 {
402 
403 #ifdef TRACE
404 	tprintf("TRACE- beep()\n");
405 #endif
406 	if (adrof(S_nobeep /* "nobeep" */) == 0)
407 		(void) tputs(BELL, 0, beep_outc);
408 }
409 
410 /*
411  * Erase that silly ^[ and print the recognized part of the string.
412  */
413 static void
414 print_recognized_stuff(tchar *recognized_part)
415 {
416 	int unit =  didfds ? 1 : SHOUT;
417 
418 #ifdef TRACE
419 	tprintf("TRACE- print_recognized_stuff()\n");
420 #endif
421 
422 	/*
423 	 * An optimized erasing of that silly ^[
424 	 *
425 	 * One would think that line speeds have become fast enough that this
426 	 * isn't necessary, but it turns out that the visual difference is
427 	 * quite noticeable.
428 	 */
429 	flush();
430 	switch (tswidth(recognized_part)) {
431 	case 0:
432 		/* erase two characters: ^[ */
433 		write(unit, "\b\b  \b\b", sizeof "\b\b  \b\b" - 1);
434 		break;
435 
436 	case 1:
437 		/* overstrike the ^, erase the [ */
438 		write(unit, "\b\b", 2);
439 		printf("%t", recognized_part);
440 		write(unit, "  \b\b", 4);
441 		break;
442 
443 	default:
444 		/* overstrike both characters ^[ */
445 		write(unit, "\b\b", 2);
446 		printf("%t", recognized_part);
447 		break;
448 	}
449 	flush();
450 }
451 
452 /*
453  * Parse full path in file into 2 parts: directory and file names
454  * Should leave final slash (/) at end of dir.
455  */
456 static void
457 extract_dir_and_name(tchar *path, tchar *dir, tchar *name)
458 {
459 	tchar  *p;
460 
461 #ifdef TRACE
462 	tprintf("TRACE- extract_dir_and_name()\n");
463 #endif
464 	p = rindex_(path, '/');
465 	if (p == NOSTR) {
466 		copyn(name, path, MAXNAMLEN);
467 		dir[0] = '\0';
468 	} else {
469 		copyn(name, ++p, MAXNAMLEN);
470 		copyn(dir, path, p - path);
471 	}
472 }
473 
474 tchar *
475 getentry(DIR *dir_fd, int looking_for_lognames)
476 {
477 	struct passwd *pw;
478 	struct dirent *dirp;
479 	/*
480 	 * For char * -> tchar * Conversion
481 	 */
482 	static tchar strbuf[MAXNAMLEN+1];
483 
484 #ifdef TRACE
485 	tprintf("TRACE- getentry()\n");
486 #endif
487 	if (looking_for_lognames) {
488 		if ((pw = getpwent()) == NULL)
489 			return (NULL);
490 		return (strtots(strbuf, pw->pw_name));
491 	}
492 	if (dirp = readdir(dir_fd))
493 		return (strtots(strbuf, dirp->d_name));
494 	return (NULL);
495 }
496 
497 static void
498 free_items(tchar **items)
499 {
500 	int i;
501 
502 #ifdef TRACE
503 	tprintf("TRACE- free_items()\n");
504 #endif
505 	for (i = 0; items[i]; i++)
506 		xfree(items[i]);
507 	xfree((char *)items);
508 }
509 
510 #define	FREE_ITEMS(items) { \
511 	int omask;\
512 \
513 	omask = sigblock(sigmask(SIGINT));\
514 	free_items(items);\
515 	items = NULL;\
516 	(void) sigsetmask(omask);\
517 }
518 
519 /*
520  * Perform a RECOGNIZE or LIST command on string "word".
521  */
522 static int
523 search2(tchar *word, COMMAND command, int max_word_length)
524 {
525 	static tchar **items = NULL;
526 	DIR *dir_fd;
527 	int numitems = 0, ignoring = TRUE, nignored = 0;
528 	int name_length, looking_for_lognames;
529 	tchar tilded_dir[MAXPATHLEN + 1], dir[MAXPATHLEN + 1];
530 	tchar name[MAXNAMLEN + 1], extended_name[MAXNAMLEN+1];
531 	tchar *entry;
532 #define	MAXITEMS 1024
533 #ifdef TRACE
534 	tprintf("TRACE- search2()\n");
535 #endif
536 
537 	if (items != NULL)
538 		FREE_ITEMS(items);
539 
540 	looking_for_lognames = (*word == '~') && (index_(word, '/') == NULL);
541 	if (looking_for_lognames) {
542 		(void) setpwent();
543 		copyn(name, &word[1], MAXNAMLEN);	/* name sans ~ */
544 	} else {
545 		extract_dir_and_name(word, dir, name);
546 		if (tilde(tilded_dir, dir) == 0)
547 			return (0);
548 		dir_fd = opendir_(*tilded_dir ? tilded_dir : S_DOT /* "." */);
549 		if (dir_fd == NULL)
550 			return (0);
551 	}
552 
553 again:	/* search for matches */
554 	name_length = strlen_(name);
555 	for (numitems = 0; entry = getentry(dir_fd, looking_for_lognames); ) {
556 		if (!is_prefix(name, entry))
557 			continue;
558 		/* Don't match . files on null prefix match */
559 		if (name_length == 0 && entry[0] == '.' &&
560 		    !looking_for_lognames)
561 			continue;
562 		if (command == LIST) {
563 			if (numitems >= MAXITEMS) {
564 				printf("\nYikes!! Too many %s!!\n",
565 				    looking_for_lognames ?
566 					"names in password file":"files");
567 				break;
568 			}
569 			if (items == NULL)
570 				items =  (tchar **)xcalloc(sizeof (items[1]),
571 				    MAXITEMS+1);
572 			items[numitems] = (tchar *)xalloc((unsigned)(strlen_(entry) + 1) * sizeof (tchar));
573 			copyn(items[numitems], entry, MAXNAMLEN);
574 			numitems++;
575 		} else {			/* RECOGNIZE command */
576 			if (ignoring && ignored(entry))
577 				nignored++;
578 			else if (recognize(extended_name,
579 			    entry, name_length, ++numitems))
580 				break;
581 		}
582 	}
583 	if (ignoring && numitems == 0 && nignored > 0) {
584 		ignoring = FALSE;
585 		nignored = 0;
586 		if (looking_for_lognames)
587 			(void) setpwent();
588 		else
589 			rewinddir(dir_fd);
590 		goto again;
591 	}
592 
593 	if (looking_for_lognames)
594 		(void) endpwent();
595 	else {
596 		unsetfd(dir_fd->dd_fd);
597 		closedir_(dir_fd);
598 	}
599 	if (command == RECOGNIZE && numitems > 0) {
600 		if (looking_for_lognames)
601 			copyn(word, S_TIL /* "~" */, 1);
602 		else
603 			/* put back dir part */
604 			copyn(word, dir, max_word_length);
605 		/* add extended name */
606 		catn(word, extended_name, max_word_length);
607 		return (numitems);
608 	}
609 	if (command == LIST) {
610 		qsort((char *)items, numitems, sizeof (items[1]),
611 		    (int (*)(const void *, const void *))fcompare);
612 		/*
613 		 * Never looking for commands in this version, so final
614 		 * argument forced to 0.  If command name completion is
615 		 * reinstated, this must change.
616 		 */
617 		print_by_column(looking_for_lognames ? NULL : tilded_dir,
618 		    items, numitems, 0);
619 		if (items != NULL)
620 			FREE_ITEMS(items);
621 	}
622 	return (0);
623 }
624 
625 /*
626  * Object: extend what user typed up to an ambiguity.
627  * Algorithm:
628  * On first match, copy full entry (assume it'll be the only match)
629  * On subsequent matches, shorten extended_name to the first
630  * character mismatch between extended_name and entry.
631  * If we shorten it back to the prefix length, stop searching.
632  */
633 int
634 recognize(tchar *extended_name, tchar *entry, int name_length, int numitems)
635 {
636 
637 #ifdef TRACE
638 	tprintf("TRACE- recognize()\n");
639 #endif
640 	if (numitems == 1)				/* 1st match */
641 		copyn(extended_name, entry, MAXNAMLEN);
642 	else {					/* 2nd and subsequent matches */
643 		tchar *x, *ent;
644 		int len = 0;
645 
646 		x = extended_name;
647 		for (ent = entry; *x && *x == *ent++; x++, len++)
648 			;
649 		*x = '\0';			/* Shorten at 1st char diff */
650 		if (len == name_length)		/* Ambiguous to prefix? */
651 			return (-1);		/* So stop now and save time */
652 	}
653 	return (0);
654 }
655 
656 /*
657  * Return true if check items initial chars in template
658  * This differs from PWB imatch in that if check is null
659  * it items anything
660  */
661 static int
662 is_prefix(tchar *check, tchar *template)
663 {
664 #ifdef TRACE
665 	tprintf("TRACE- is_prefix()\n");
666 #endif
667 
668 	do
669 		if (*check == 0)
670 			return (TRUE);
671 	while (*check++ == *template++);
672 	return (FALSE);
673 }
674 
675 /*
676  *  Return true if the chars in template appear at the
677  *  end of check, i.e., are its suffix.
678  */
679 static int
680 is_suffix(tchar *check, tchar *template)
681 {
682 	tchar *c, *t;
683 
684 #ifdef TRACE
685 	tprintf("TRACE- is_suffix()\n");
686 #endif
687 	for (c = check; *c++; )
688 		;
689 	for (t = template; *t++; )
690 		;
691 	for (;;) {
692 		if (t == template)
693 			return (TRUE);
694 		if (c == check || *--t != *--c)
695 			return (FALSE);
696 	}
697 }
698 
699 int
700 tenex(tchar *inputline, int inputline_size)
701 {
702 	int numitems, num_read, should_retype;
703 	int i;
704 
705 #ifdef TRACE
706 	tprintf("TRACE- tenex()\n");
707 #endif
708 	setup_tty(ON);
709 	termchars();
710 	num_read = 0;
711 	should_retype = FALSE;
712 	while ((i = read_(SHIN, inputline+num_read, inputline_size-num_read))
713 	    > 0) {
714 		static tchar *delims = S_DELIM /* " '\"\t;&<>()|`" */;
715 		tchar *str_end, *word_start, last_char;
716 		int space_left;
717 		struct termios tty;
718 		COMMAND command;
719 
720 		num_read += i;
721 		inputline[num_read] = '\0';
722 		last_char = inputline[num_read - 1] & TRIM;
723 
724 		/*
725 		 * read_() can return more than requested size if there
726 		 * is multibyte character at the end.
727 		 */
728 		if ((num_read >= inputline_size) || (last_char == '\n'))
729 			break;
730 
731 		str_end = &inputline[num_read];
732 		if (last_char == ESC) {
733 			command = RECOGNIZE;
734 			*--str_end = '\0';	/* wipe out trailing ESC */
735 		} else
736 			command = LIST;
737 
738 		tty = tty_new;
739 		tty.c_lflag &= ~ECHO;
740 		(void) ioctl(SHIN, TCSETSF, (char *)&tty);
741 
742 		if (command == LIST)
743 			printf("\n");
744 		/*
745 		 * Find LAST occurence of a delimiter in the inputline.
746 		 * The word start is one character past it.
747 		 */
748 		for (word_start = str_end; word_start > inputline;
749 		    --word_start) {
750 			if (index_(delims, word_start[-1]) ||
751 			    isauxsp(word_start[-1]))
752 				break;
753 		}
754 		space_left = inputline_size - (word_start - inputline) - 1;
755 		numitems = search2(word_start, command, space_left);
756 
757 		/*
758 		 * Tabs in the input line cause trouble after a pushback.
759 		 * tty driver won't backspace over them because column
760 		 * positions are now incorrect. This is solved by retyping
761 		 * over current line.
762 		 */
763 		if (index_(inputline, '\t')) {	/* tab tchar in input line? */
764 			back_to_col_1();
765 			should_retype = TRUE;
766 		}
767 		if (command == LIST)		/* Always retype after a LIST */
768 			should_retype = TRUE;
769 		if (should_retype)
770 			printprompt();
771 		pushback(inputline, should_retype);
772 		num_read = 0;			/* chars will be reread */
773 		should_retype = FALSE;
774 
775 		/*
776 		 * Avoid a race condition by echoing what we're recognized
777 		 * _after_ pushing back the command line.  This way, if the
778 		 * user waits until seeing this output before typing more
779 		 * stuff, the resulting keystrokes won't race with the STIed
780 		 * input we've pushed back.  (Of course, if the user types
781 		 * ahead, the race still exists and it's quite possible that
782 		 * the pushed back input line will interleave with the
783 		 * keystrokes in unexpected ways.)
784 		 */
785 		if (command == RECOGNIZE) {
786 			/* print from str_end on */
787 			print_recognized_stuff(str_end);
788 			if (numitems != 1)	/* Beep = No match/ambiguous */
789 				beep();
790 		}
791 	}
792 	setup_tty(OFF);
793 	return (num_read);
794 }
795 
796 static int
797 ignored(tchar *entry)
798 {
799 	struct varent *vp;
800 	tchar **cp;
801 
802 #ifdef TRACE
803 	tprintf("TRACE- ignored()\n");
804 #endif
805 	if ((vp = adrof(S_fignore /* "fignore" */)) == NULL ||
806 	    (cp = vp->vec) == NULL)
807 		return (FALSE);
808 	for (; *cp != NULL; cp++)
809 		if (is_suffix(entry, *cp))
810 			return (TRUE);
811 	return (FALSE);
812 }
813 #endif /* FILEC */
814