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