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