/*	Copyright (c) 1990 UNIX System Laboratories, Inc.	*/
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF     	*/
/*	UNIX System Laboratories, Inc.                     	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)csh:sh.file.c	1.4.3.1"

/*******************************************************************

		PROPRIETARY NOTICE (Combined)

This source code is unpublished proprietary information
constituting, or derived under license from AT&T's UNIX(r) System V.
In addition, portions of such source code were derived from Berkeley
4.3 BSD under license from the Regents of the University of
California.



		Copyright Notice 

Notice of copyright on this source code product does not indicate 
publication.

	(c) 1986,1987,1988,1989  Sun Microsystems, Inc
	(c) 1983,1984,1985,1986,1987,1988,1989  AT&T.
	          All rights reserved.
********************************************************************/ 

/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved.  The Berkeley Software License Agreement
 * specifies the terms and conditions for redistribution.
 */

#ifdef FILEC
/*
 * Tenex style file name recognition, .. and more.
 * History:
 *	Author: Ken Greer, Sept. 1975, CMU.
 *	Finally got around to adding to the Cshell., Ken Greer, Dec. 1981.
 */

#include "sh.h"
#include <sys/types.h>
#include <dirent.h>
/*
#include <sys/fs/ufs_fsdir.h>
#include "dir2.h"
*/
#include <pwd.h>
#include "sh.tconst.h"

#define TRUE	1
#define FALSE	0
#define ON	1
#define OFF	0

#define ESC	'\033'

/* extern putchar(); */
extern DIR *opendir_();

static char *BELL = "\07";
static char *CTRLR = "^R\n";

typedef enum {LIST, RECOGNIZE} COMMAND;

static jmp_buf osetexit;		/* saved setexit() state */
static struct termios  tty_save;	/* saved terminal state */
static struct termios  tty_new;		/* new terminal state */

/*
 * Put this here so the binary can be patched with adb to enable file
 * completion by default.  Filec controls completion, nobeep controls
 * ringing the terminal bell on incomplete expansions.
 */
bool filec = 0;

static
setup_tty(on)
	int on;
{
	int omask;
#ifdef TRACE
	tprintf("TRACE- setup_tty()\n");
#endif

	omask = sigblock(sigmask(SIGINT));
	if (on) {
		/*
		 * The shell makes sure that the tty is not in some weird state
		 * and fixes it if it is.  But it should be noted that the
		 * tenex routine will not work correctly in CBREAK or RAW mode
		 * so this code below is, therefore, mandatory.
		 *
		 * Also, in order to recognize the ESC (filename-completion)
		 * character, set EOL to ESC.  This way, ESC will terminate
		 * the line, but still be in the input stream.
		 * EOT (filename list) will also terminate the line,
		 * but will not appear in the input stream.
		 *
		 * The getexit/setexit contortions ensure that the
		 * tty state will be restored if the user types ^C.
		 */
		(void) ioctl(SHIN, TCGETS,  (char *)&tty_save);
		getexit(osetexit);
		if (setjmp(reslab)) {
			(void) ioctl(SHIN, TCSETSW,  (char *)&tty_save);
			resexit(osetexit);
			reset();
		}
		tty_new = tty_save;
		tty_new.c_cc[VEOL] = ESC;
		tty_new.c_iflag |= IMAXBEL | BRKINT | IGNPAR;
		tty_new.c_lflag |= ICANON;
		tty_new.c_oflag &= ~OCRNL;
		(void) ioctl(SHIN, TCSETSW,  (char *)&tty_new);
	} else {
		/*
		 * Reset terminal state to what user had when invoked
		 */
		(void) ioctl(SHIN, TCSETSW,  (char *)&tty_save);
		resexit(osetexit);
	}
	(void) sigsetmask(omask);
}

static
termchars()
{
	extern char *tgetstr();
	 char bp[1024];
	static char area[256];
	static int been_here = 0;
	 char *ap = area;
	register char *s;
	 char *term;

#ifdef TRACE
	tprintf("TRACE- termchars()\n");
#endif
	if (been_here)
		return;
	been_here = TRUE;

	if ((term = getenv("TERM")) == NULL)
		return;
	if (tgetent(bp, term) != 1)
		return;
	if (s = tgetstr("vb", &ap))		/* Visible Bell */
		BELL = s;
}

/*
 * Move back to beginning of current line
 */
static
back_to_col_1()
{
	int omask;

#ifdef TRACE
	tprintf("TRACE- back_to_col_1()\n");
#endif
	omask = sigblock(sigmask(SIGINT));
	(void) write(SHOUT, "\r", 1);
	(void) sigsetmask(omask);
}

/*
 * Push string contents back into tty queue
 */
static
pushback(string, echoflag)
	tchar *string;
	int echoflag;
{
	register tchar *p;
	struct termios tty;
	int omask;


#ifdef TRACE
	tprintf("TRACE- puchback()\n");
#endif
	omask = sigblock(sigmask(SIGINT));
	tty = tty_new;
	if (!echoflag)
		tty.c_lflag &= ~ECHO;
	(void) ioctl(SHIN, TCSETSF,  (char *)&tty);


	for (p = string; *p; p++){
		char	mbc[MB_LEN_MAX];
		int	i, j=wctomb(mbc, (wchar_t)*p);
		
		if(j<0) continue; /* Error! But else what can we do? */
		for(i=0;i<j;++i)
		    (void) ioctl(SHIN, TIOCSTI, mbc+i);
	}

	if (tty.c_lflag != tty_new.c_lflag)
		(void) ioctl(SHIN, TCSETS,  (char *)&tty_new);
	(void) sigsetmask(omask);
}

/*
 * Concatenate src onto tail of des.
 * Des is a string whose maximum length is count.
 * Always null terminate.
 */
catn(des, src, count)
	register tchar *des, *src;
	register count;
{
#ifdef TRACE
	tprintf("TRACE- catn()\n");
#endif

	while (--count >= 0 && *des)
		des++;
	while (--count >= 0)
		if ((*des++ = *src++) == '\0')
			return;
	*des = '\0';
}

static
max(a, b)
{

	return (a > b ? a : b);
}

/*
 * Like strncpy but always leave room for trailing \0
 * and always null terminate.
 */
copyn(des, src, count)
	register tchar *des, *src;
	register count;
{

#ifdef TRACE
	tprintf("TRACE- copyn()\n");
#endif
	while (--count >= 0)
		if ((*des++ = *src++) == '\0')
			return;
	*des = '\0';
}

/*
 * For qsort()
 */
static
fcompare(file1, file2)
	tchar **file1, **file2;
{

#ifdef TRACE
	tprintf("TRACE- fcompare()\n");
#endif
	return (strcmp_(*file1, *file2));
}

static char
filetype(dir, file, nosym)
	tchar *dir, *file;
	int nosym;
{
	tchar path[MAXPATHLEN + 1];
	struct stat statb;

#ifdef TRACE
	tprintf("TRACE- filetype()\n");
#endif
	if (dir) {
		catn(strcpy_(path, dir), file, MAXPATHLEN);
		if (nosym) {
			if (stat_(path, &statb) < 0)
				return (' ');
		} else {
			if (lstat_(path, &statb) < 0)
				return (' ');
		}
		if ((statb.st_mode & S_IFMT) == S_IFLNK)
			return ('@');
		if ((statb.st_mode & S_IFMT) == S_IFDIR)
			return ('/');
		if (((statb.st_mode & S_IFMT) == S_IFREG) &&
		    (statb.st_mode & 011))
			return ('*');
	}
	return (' ');
}

/*
 * Print sorted down columns
 */
static
print_by_column(dir, items, count, looking_for_command)
	tchar *dir, *items[];
	int looking_for_command;
{
	register int i, rows, r, c, maxwidth = 0, columns;

#ifdef TRACE
	tprintf("TRACE- print_by_column()\n");
#endif
	for (i = 0; i < count; i++)
		maxwidth = max(maxwidth, tswidth(items[i]));

	/* for the file tag and space */
	maxwidth += looking_for_command ? 1 : 2;
	columns = 78 / maxwidth;
	rows = (count + (columns - 1)) / columns;

	for (r = 0; r < rows; r++) {
		for (c = 0; c < columns; c++) {
			i = c * rows + r;
			if (i < count) {
				register int w;

				printf("%t", items[i]);
				w = tswidth(items[i]);
				/*
				 * Print filename followed by
				 * '@' or '/' or '*' or ' '
				 */
				if (!looking_for_command) {
					printf("%t",(tchar)filetype(dir, items[i], 0));
					w++;
				}
				if (c < columns - 1)	/* last column? */
					for (; w < maxwidth; w++)
						printf(" ");
			}
		}
		printf("\n");
	}
}

/*
 * Expand file name with possible tilde usage
 *	~person/mumble
 * expands to
 *	home_directory_of_person/mumble
 */
tchar *
tilde(new, old)
	tchar *new, *old;
{
	register tchar *o, *p;
	register struct passwd *pw;
	static tchar person[40];
	char person_[40];		/* work */
	tchar *pw_dir;			/* work */

#ifdef TRACE
	tprintf("TRACE- tilde()\n");
#endif
	if (old[0] != '~')
		return (strcpy_(new, old));

	for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++)
		;
	*p = '\0';
	if (person[0] == '\0')
		(void) strcpy_(new, value(S_home /*"home"*/));
	else {
		pw = getpwnam(tstostr(person_,person));
		if (pw == NULL)
			return (NULL);
		pw_dir = strtots((tchar *)NULL, pw->pw_dir);	/* allocate */
		(void) strcpy_(new, pw_dir);
		xfree(pw_dir);					/* free it */
	}
	(void) strcat_(new, o);
	return (new);
}

/*
 * Cause pending line to be printed
 */
static
sim_retype()
{
#ifdef notdef
	struct termios tty_pending;
	
#ifdef TRACE
	tprintf("TRACE- sim_retypr()\n");
#endif
	tty_pending = tty_new;
	tty_pending.c_lflag |= PENDIN;

	(void) ioctl(SHIN, TCSETS,  (char *)&tty_pending);
#else
#ifdef TRACE
	tprintf("TRACE- sim_retype()\n");
#endif
	(void) write(SHOUT, CTRLR, strlen(CTRLR));
	printprompt();
#endif
}

static
beep()
{

#ifdef TRACE
	tprintf("TRACE- beep()\n");
#endif
	if (adrof(S_nobeep /*"nobeep" */) == 0)
		(void) write(SHOUT, BELL, strlen(BELL));
}

/*
 * Erase that silly ^[ and
 * print the recognized part of the string
 */
static
print_recognized_stuff(recognized_part)
	tchar *recognized_part;
{

	int unit;
	unit = didfds ? 1 : SHOUT;
#ifdef TRACE
	tprintf("TRACE- print_recognized_stuff()\n");
#endif
	/* An optimized erasing of that silly ^[ */
	flush();	
	switch (tswidth(recognized_part)) {

	case 0:				/* erase two characters: ^[ */
/*
		write(unit, "\010\010  \010\010", strlen("\010\010  \010\010"));
*/
		break;

	case 1:				/* overstrike the ^, erase the [ */
/*
		write(unit, "\010\010", 2);
*/
		printf("%t", recognized_part);
		write(unit, "  \010\010", 4);
		break;

	default:			/* overstrike both characters ^[ */
/*
		write(unit, "\010\010", 2);
*/
		printf("%t", recognized_part);
		break;
	}
	flush();
}

/*
 * Parse full path in file into 2 parts: directory and file names
 * Should leave final slash (/) at end of dir.
 */
static
extract_dir_and_name(path, dir, name)
	tchar *path, *dir, *name;
{
	register tchar  *p;

#ifdef TRACE
	tprintf("TRACE- extract_dir_and_name()\n");
#endif
	p = rindex_(path, '/');
	if (p == NOSTR) {
		copyn(name, path, MAXNAMLEN);
		dir[0] = '\0';
	} else {
		copyn(name, ++p, MAXNAMLEN);
		copyn(dir, path, p - path);
	}
}

tchar *
getentry(dir_fd, looking_for_lognames)
	DIR *dir_fd;
{
	register struct passwd *pw;
	register struct dirent *dirp;
	/*
	 * For char * -> tchar * Conversion
	 */
	static tchar strbuf[MAXNAMLEN+1];

#ifdef TRACE
	tprintf("TRACE- getentry()\n");
#endif
	if (looking_for_lognames) {
		if ((pw = getpwent ()) == NULL)
			return (NULL);
		return (strtots(strbuf,pw->pw_name));
	}
	if (dirp = readdir(dir_fd))
		return (strtots(strbuf,dirp->d_name));
	return (NULL);
}

static
free_items(items)
	register tchar **items;
{
	register int i;

#ifdef TRACE
	tprintf("TRACE- free_items()\n");
#endif
	for (i = 0; items[i]; i++)
		free(items[i]);
	free( (char *)items);
}

#define FREE_ITEMS(items) { \
	int omask;\
\
	omask = sigblock(sigmask(SIGINT));\
	free_items(items);\
	items = NULL;\
	(void) sigsetmask(omask);\
}

/*
 * Perform a RECOGNIZE or LIST command on string "word".
 */
static
search2(word, command, max_word_length)
	tchar *word;
	COMMAND command;
{
	static tchar **items = NULL;
	register DIR *dir_fd;
	register numitems = 0, ignoring = TRUE, nignored = 0;
	register name_length, looking_for_lognames;
	tchar tilded_dir[MAXPATHLEN + 1], dir[MAXPATHLEN + 1];
	tchar name[MAXNAMLEN + 1], extended_name[MAXNAMLEN+1];
	tchar *entry;
#define MAXITEMS 1024
#ifdef TRACE
	tprintf("TRACE- search2()\n");
#endif

	if (items != NULL)
		FREE_ITEMS(items);

	looking_for_lognames = (*word == '~') && (index_(word, '/') == NULL);
	if (looking_for_lognames) {
		(void) setpwent();
		copyn(name, &word[1], MAXNAMLEN);	/* name sans ~ */
	} else {
		extract_dir_and_name(word, dir, name);
		if (tilde(tilded_dir, dir) == 0)
			return (0);
		dir_fd = opendir_(*tilded_dir ? tilded_dir : S_DOT /*"."*/);
		if (dir_fd == NULL)
			return (0);
	}

again:	/* search for matches */
	name_length = strlen_(name);
	for (numitems = 0; entry = getentry(dir_fd, looking_for_lognames); ) {
		if (!is_prefix(name, entry))
			continue;
		/* Don't match . files on null prefix match */
		if (name_length == 0 && entry[0] == '.' &&
		    !looking_for_lognames)
			continue;
		if (command == LIST) {
			if (numitems >= MAXITEMS) {
				printf ("\nYikes!! Too many %s!!\n",
				    looking_for_lognames ?
					"names in password file":"files");
				break;
			}
			if (items == NULL)
				items =  (tchar **) calloc(sizeof (items[1]),
				    MAXITEMS);
			items[numitems] = (tchar *)xalloc((unsigned)(strlen_(entry) + 1)*sizeof(tchar));
			copyn(items[numitems], entry, MAXNAMLEN);
			numitems++;
		} else {			/* RECOGNIZE command */
			if (ignoring && ignored(entry))
				nignored++;
			else if (recognize(extended_name,
			    entry, name_length, ++numitems))
				break;
		}
	}
	if (ignoring && numitems == 0 && nignored > 0) {
		ignoring = FALSE;
		nignored = 0;
		if (looking_for_lognames)
			(void)setpwent();
		else
			rewinddir(dir_fd);
		goto again;
	}

	if (looking_for_lognames)
		(void) endpwent();
	else
		closedir(dir_fd);
	if (command == RECOGNIZE && numitems > 0) {
		if (looking_for_lognames)
			 copyn(word, S_TIL /*"~" */, 1);
		else
			/* put back dir part */
			copyn(word, dir, max_word_length);
		/* add extended name */
		catn(word, extended_name, max_word_length);
		return (numitems);
	}
	if (command == LIST) {
		qsort( (tchar *)items, numitems, sizeof(items[1]), fcompare);
		/*
		 * Never looking for commands in this version, so final
		 * argument forced to 0.  If command name completion is
		 * reinstated, this must change.
		 */
		print_by_column(looking_for_lognames ? NULL : tilded_dir,
		    items, numitems, 0);
		if (items != NULL)
			FREE_ITEMS(items);
	}
	return (0);
}

/*
 * Object: extend what user typed up to an ambiguity.
 * Algorithm:
 * On first match, copy full entry (assume it'll be the only match) 
 * On subsequent matches, shorten extended_name to the first
 * character mismatch between extended_name and entry.
 * If we shorten it back to the prefix length, stop searching.
 */
recognize(extended_name, entry, name_length, numitems)
	tchar *extended_name, *entry;
{

#ifdef TRACE
	tprintf("TRACE- recognize()\n");
#endif
	if (numitems == 1)				/* 1st match */
		copyn(extended_name, entry, MAXNAMLEN);
	else {					/* 2nd and subsequent matches */
		register tchar *x, *ent;
		register int len = 0;

		x = extended_name;
		for (ent = entry; *x && *x == *ent++; x++, len++)
			;
		*x = '\0';			/* Shorten at 1st char diff */
		if (len == name_length)		/* Ambiguous to prefix? */
			return (-1);		/* So stop now and save time */
	}
	return (0);
}

/*
 * Return true if check items initial chars in template
 * This differs from PWB imatch in that if check is null
 * it items anything
 */
static
is_prefix(check, template)
	register tchar *check, *template;
{
#ifdef TRACE
	tprintf("TRACE- is_prefix()\n");
#endif

	do
		if (*check == 0)
			return (TRUE);
	while (*check++ == *template++);
	return (FALSE);
}

/*
 *  Return true if the chars in template appear at the
 *  end of check, i.e., are its suffix.
 */
static
is_suffix(check, template)
	tchar *check, *template;
{
	register tchar *c, *t;

#ifdef TRACE
	tprintf("TRACE- is_suffix()\n");
#endif
	for (c = check; *c++;)
		;
	for (t = template; *t++;)
		;
	for (;;) {
		if (t == template)
			return (TRUE);
		if (c == check || *--t != *--c)
			return (FALSE);
	}
}

tenex(inputline, inputline_size)
	tchar *inputline;
	int inputline_size;
{
	register int numitems, num_read, should_retype;
	int i;

#ifdef TRACE
	tprintf("TRACE- tenex()\n");
#endif
	setup_tty(ON);
	termchars();
	num_read = 0;
	should_retype = FALSE;
	while ((i = read_(SHIN, inputline+num_read, inputline_size-num_read))
	    > 0) {
		static tchar *delims = S_DELIM /*" '\"\t;&<>()|`"*/;
		register tchar *str_end, *word_start, last_char;
		register int space_left;
		struct termios tty;
		COMMAND command;

		num_read += i;
		inputline[num_read] = '\0';
		last_char = inputline[num_read - 1] & TRIM;

		if ((num_read == inputline_size) || (last_char == '\n'))
			break;

		str_end = &inputline[num_read];
		if (last_char == ESC) {
			command = RECOGNIZE;
			*--str_end = '\0';	/* wipe out trailing ESC */
		} else {
			if (num_read == 1) {	/* EOT is first character */
				num_read = 0;	/* simulate EOF on read */
				break;
			} else
				command = LIST;
		}

		tty = tty_new;
		tty.c_lflag &= ~ECHO;
		(void) ioctl(SHIN, TCSETSF,  (char *)&tty);

		if (command == LIST)
			printf("\n");
		/*
		 * Find LAST occurence of a delimiter in the inputline.
		 * The word start is one character past it.
		 */
		for (word_start = str_end; word_start > inputline; --word_start)
			if (index_(delims, word_start[-1])||isauxsp(word_start[-1]))
				break;
		space_left = inputline_size - (word_start - inputline) - 1;
		numitems = search2(word_start, command, space_left);

		if (command == RECOGNIZE) {
			/* print from str_end on */
			print_recognized_stuff(str_end);
			if (numitems != 1)	/* Beep = No match/ambiguous */
				beep();
		}

		/*
		 * Tabs in the input line cause trouble after a pushback.
		 * tty driver won't backspace over them because column
		 * positions are now incorrect. This is solved by retyping
		 * over current line.
		 */
		if (index_(inputline, '\t')) {	/* tab tchar in input line? */
			back_to_col_1();
			should_retype = TRUE;
		}
		if (command == LIST)		/* Always retype after a LIST */
			should_retype = TRUE;
		if (should_retype)
			printprompt();
		pushback(inputline, should_retype);
		num_read = 0;			/* chars will be reread */
		should_retype = FALSE;
	}
	setup_tty(OFF);
	return (num_read);
}

static
ignored(entry)
	register tchar *entry;
{
	struct varent *vp;
	register tchar **cp;

#ifdef TRACE
	tprintf("TRACE- ignored()\n");
#endif
	if ((vp = adrof(S_fignore /*"fignore"*/)) == NULL || (cp = vp->vec) == NULL)
		return (FALSE);
	for (; *cp != NULL; cp++)
		if (is_suffix(entry, *cp))
			return (TRUE);
	return (FALSE);
}
#endif FILEC
