2.9BSD/usr/contrib/newcsh/src/tenex.c
static char *RCSid =
"$Header: /usr/local/src/cmd/tcsh/tenex.c,v 1.9 83/10/05 21:56:27 kg Exp $";
/*
* 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.
*
* Search and recognition of command names (in addition to file names)
* by Mike Ellis, Fairchild A.I. Labs, Sept 1983.
*
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sgtty.h>
#include <dir.h>
#include <signal.h>
#include <pwd.h>
/* Don't include stdio.h! Csh doesn't like it!! */
#ifdef TEST
#include <stdio.h>
#include "dir.h"
#define flush() fflush(stdout)
#endif
#define TRUE 1
#define FALSE 0
#define ON 1
#define OFF 0
#define FILSIZ 512 /* Max reasonable file name length */
#define ESC '\033'
#define equal(a, b) (strcmp(a, b) == 0)
#define is_set(var) adrof(var)
#define BUILTINS "/usr/new/lib/builtins/" /* fake builtin bin */
extern short SHIN, SHOUT;
extern char *getenv ();
extern putchar ();
typedef enum {LIST, RECOGNIZE} COMMAND;
static char
*BELL = "\07";
static
setup_tty (on)
{
static struct tchars tchars; /* INT, QUIT, XON, XOFF, EOF, BRK */
static char save_t_brkc = -1; /* Save user's break character */
sigignore (SIGINT);
if (on)
{
struct sgttyb sgtty;
ioctl (SHIN, TIOCGETC, &tchars); /* Get current break character*/
save_t_brkc = tchars.t_brkc; /* Current break char, if any */
if (save_t_brkc != ESC) /* If it's not already ESCAPE */
{
tchars.t_brkc = ESC; /* Set break char to ESCAPE */
ioctl (SHIN, TIOCSETC, &tchars);
}
/*
* This is a useful feature in it's own right...
* 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.
*/
ioctl (SHIN, TIOCGETP, &sgtty);
if ((sgtty.sg_flags & (RAW | CBREAK)) ||
((sgtty.sg_flags & ECHO) == 0)) /* not manditory, but nice */
{
sgtty.sg_flags &= ~(RAW | CBREAK);
sgtty.sg_flags |= ECHO;
ioctl (SHIN, TIOCSETP, &sgtty);
}
}
else
{
/*
* Reset break character to what user had when invoked
* (providing it is different from current one)
*/
if (save_t_brkc != tchars.t_brkc)
{
tchars.t_brkc = save_t_brkc;
ioctl (SHIN, TIOCSETC, &tchars);
}
}
sigrelse (SIGINT);
}
static
termchars ()
{
extern char *tgetstr ();
char bp[1024];
static char area[256];
static int been_here = 0;
char *ap = area;
register char *s;
if (been_here)
return;
been_here = TRUE;
if (tgetent (bp, getenv ("TERM")) != 1)
return;
if (s = tgetstr ("vb", &ap)) /* Visible Bell */
BELL = s;
return;
}
/*
* Move back to beginning of current line
*/
static
back_to_col_1 ()
{
struct sgttyb tty, tty_normal;
sigignore (SIGINT);
ioctl (SHIN, TIOCGETP, &tty);
tty_normal = tty;
tty.sg_flags &= ~CRMOD;
ioctl (SHIN, TIOCSETN, &tty);
(void) write (SHOUT, "\r", 1);
ioctl (SHIN, TIOCSETN, &tty_normal);
sigrelse (SIGINT);
}
/*
* Push string contents back into tty queue
*/
static
pushback (string)
char *string;
{
register char *p;
struct sgttyb tty, tty_normal;
sigignore (SIGINT);
ioctl (SHOUT, TIOCGETP, &tty);
tty_normal = tty;
tty.sg_flags &= ~ECHO;
ioctl (SHOUT, TIOCSETN, &tty);
for (p = string; *p; p++)
ioctl (SHOUT, TIOCSTI, p);
ioctl (SHOUT, TIOCSETN, &tty_normal);
sigrelse (SIGINT);
}
/*
* Concatonate src onto tail of des.
* Des is a string whose maximum length is count.
* Always null terminate.
*/
catn (des, src, count)
register char *des, *src;
register count;
{
while (--count >= 0 && *des)
des++;
while (--count >= 0)
if ((*des++ = *src++) == 0)
return;
*des = '\0';
}
static
max (a, b)
{
if (a > b)
return (a);
return (b);
}
/*
* like strncpy but always leave room for trailing \0
* and always null terminate.
*/
copyn (des, src, count)
register char *des, *src;
register count;
{
while (--count >= 0)
if ((*des++ = *src++) == 0)
return;
*des = '\0';
}
/*
* For qsort()
*/
static
fcompare (file1, file2)
char **file1, **file2;
{
return (strcmp (*file1, *file2));
}
static char
filetype (dir, file)
char *dir, *file;
{
if (dir)
{
char path[512];
struct stat statb;
strcpy (path, dir);
catn (path, file, sizeof path);
if (stat (path, &statb) >= 0)
{
if (statb.st_mode & S_IFDIR)
return ('/');
if (statb.st_mode & 0111)
return ('*');
}
}
return (' ');
}
/*
* Print sorted down columns
*/
static
print_by_column (dir, items, count, l_for_command)
register char *dir, *items[];
{
register int i, rows, r, c, maxwidth = 0, columns;
for (i = 0; i < count; i++)
maxwidth = max (maxwidth, strlen (items[i]));
maxwidth += l_for_command ? 1:2; /* for the file tag and space */
columns = 80 / 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("%s", items[i]);
w = strlen (items[i]);
/* Print filename followed by '/' or '*' or ' ' */
if (!l_for_command)
putchar (filetype (dir, items[i])), w++;
if (c < (columns - 1)) /* Not last column? */
for (; w < maxwidth; w++)
putchar (' ');
}
}
printf ("\n");
}
}
/*
* expand "old" file name with possible tilde usage
* ~person/mumble
* expands to
* home_directory_of_person/mumble
* into string "new".
*/
char *
tilde (new, old)
char *new, *old;
{
extern struct passwd *getpwuid (), *getpwnam ();
register char *o, *p;
register struct passwd *pw;
static char person[40] = {0};
if (old[0] != '~')
{
strcpy (new, old);
return (new);
}
for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++);
*p = '\0';
if (person[0] == '\0') /* then use current uid */
pw = getpwuid (getuid ());
else
pw = getpwnam (person);
if (pw == NULL)
return (NULL);
strcpy (new, pw -> pw_dir);
(void) strcat (new, o);
return (new);
}
/*
* Cause pending line to be printed
*/
static
retype ()
{
int pending_input = LPENDIN;
ioctl (SHOUT, TIOCLBIS, &pending_input);
}
static
beep ()
{
(void) write (SHOUT, BELL, strlen(BELL));
}
/*
* parse full path in file into 2 parts: directory and file names
* Should leave final slash (/) at end of dir.
*/
static
dir_name (path, dir, name)
char *path, *dir, *name;
{
extern char *rindex ();
register char *p;
p = rindex (path, '/');
if (p == NULL)
{
copyn (name, path, MAXNAMLEN);
dir[0] = '\0';
}
else
{
p++;
copyn (name, p, MAXNAMLEN);
copyn (dir, path, p - path);
}
}
char *
getentry (dir_fd, l_for_lognames)
DIR *dir_fd;
{
if (l_for_lognames) /* Is it login names we want? */
{
extern struct passwd *getpwent ();
register struct passwd *pw;
if ((pw = getpwent ()) == NULL)
return (NULL);
return (pw -> pw_name);
}
else /* It's a dir entry we want */
{
register struct direct *dirp;
if (dirp = readdir (dir_fd))
return (dirp -> d_name);
return (NULL);
}
}
static
free_items (items)
register char **items;
{
register int i;
for (i = 0; items[i]; i++)
free (items[i]);
free (items);
}
#define FREE_ITEMS(items)\
{\
sighold (SIGINT);\
free_items (items);\
items = NULL;\
sigrelse (SIGINT);\
}
#define FREE_DIR(fd)\
{\
sighold (SIGINT);\
closedir (fd);\
fd = NULL;\
sigrelse (SIGINT);\
}
static int dirctr; /* -1 0 1 2 ... */
static char dirflag[5]; /* ' nn\0' - dir #s - . 1 2 ... */
/*
* Strip next directory from path; return ptr to next unstripped directory.
*/
char *dir_f_path (path, dir)
char *path, dir[];
{
register char *d = dir;
while (*path && (*path == ' ' || *path == ':')) path++;
while (*path && (*path != ' ' && *path != ':')) *(d++) = *(path++);
while (*path && (*path == ' ' || *path == ':')) path++;
++dirctr;
if (*dir == '.')
strcpy (dirflag, " .");
else
{
dirflag[0] = ' ';
if (dirctr <= 9)
{
dirflag[1] = '0' + dirctr;
dirflag[2] = '\0';
}
else
{
dirflag[1] = '0' + dirctr / 10;
dirflag[2] = '0' + dirctr % 10;
dirflag[3] = '\0';
}
}
*(d++) = '/';
*d = 0;
return path;
}
/*
* Perform a RECOGNIZE or LIST command on string "word".
*/
static
search (word, wp, command, routine, max_word_length, l_for_command)
char *word,
*wp; /* original end-of-word */
COMMAND command;
int (*routine) ();
{
# define MAXITEMS 2048
register numitems,
name_length, /* Length of prefix (file name) */
l_for_lognames; /* True if looking for login names */
int showpathn; /* True if we want path number */
struct stat
dot_statb, /* Stat buffer for "." */
curdir_statb; /* Stat buffer for current directory */
int dot_scan, /* True if scanning "." */
dot_got; /* True if have scanned dot already */
char tilded_dir[FILSIZ + 1], /* dir after ~ expansion */
dir[FILSIZ + 1], /* /x/y/z/ part in /x/y/z/f */
name[MAXNAMLEN + 1], /* f part in /d/d/d/f */
extended_name[MAXNAMLEN+1], /* the recognized (extended) name */
*entry, /* single directory entry or logname */
*path; /* hacked PATH environment variable */
static DIR
*dir_fd = NULL;
static char
**items = NULL; /* file names when doing a LIST */
if (items != NULL)
FREE_ITEMS (items);
if (dir_fd != NULL)
FREE_DIR (dir_fd);
l_for_lognames = (*word == '~') && (index (word, '/') == NULL);
l_for_command &= (*word != '~') && (index (word, '/') == NULL);
if (l_for_command)
{
copyn (name, word, MAXNAMLEN);
if ((path = getenv ("PATH")) == NULL)
path = "";
/* setup builtins as 1st to search before PATH */
copyn (dir, BUILTINS, sizeof dir);
dirctr = -1; /* BUILTINS -1 */
dirflag[0] = 0;
}
numitems = 0;
dot_got = FALSE;
stat (".", &dot_statb);
cmdloop: /* One loop per directory in PATH, if l_for_command */
if (l_for_lognames) /* Looking for login names? */
{
setpwent (); /* Open passwd file */
copyn (name, &word[1], MAXNAMLEN); /* name sans ~ */
}
else
{ /* Open directory */
if (!l_for_command)
dir_name (word, dir, name);
if ((tilde (tilded_dir, dir) == 0) || /* expand ~user/... stuff */
((dir_fd = opendir (*tilded_dir ? tilded_dir : ".")) == NULL))
{
if (l_for_command)
goto try_next_path;
else
return (0);
}
dot_scan = FALSE;
if (l_for_command)
{
/*
* Are we searching "."?
*/
fstat (dir_fd->dd_fd, &curdir_statb);
if (curdir_statb.st_dev == dot_statb.st_dev &&
curdir_statb.st_ino == dot_statb.st_ino)
{
if (dot_got) /* Second time in PATH? */
goto try_next_path;
dot_scan = TRUE;
dot_got = TRUE;
}
}
}
name_length = strlen (name);
showpathn = l_for_command && is_set("listpathnum");
while (entry = getentry (dir_fd, l_for_lognames))
{
if (!is_prefix (name, entry))
continue;
/*
* Don't match . files on null prefix match
*/
if (name_length == 0 && entry[0] == '.' && !l_for_lognames)
continue;
/*
* Skip non-executables if looking for commands:
* Only done for directory "." for speed.
* (Benchmarked with and without:
* With filetype check, a full search took 10 seconds.
* Without filetype check, a full search took 1 second.)
* -Ken Greer
*/
if (l_for_command && dot_scan && filetype (dir, entry) != '*')
continue;
if (command == LIST) /* LIST command */
{
extern char *malloc ();
register int length;
if (numitems >= MAXITEMS)
{
printf ("\nYikes!! Too many %s!!\n",
l_for_lognames ? "names in password file":"files");
break;
}
if (items == NULL)
{
items = (char **) calloc (sizeof (items[1]), MAXITEMS + 1);
if (items == NULL)
break;
}
length = strlen(entry) + 1;
if (showpathn)
length += strlen(dirflag);
if ((items[numitems] = malloc (length)) == NULL)
{
printf ("out of mem\n");
break;
}
copyn (items[numitems], entry, MAXNAMLEN);
if (showpathn)
catn (items[numitems], dirflag, MAXNAMLEN);
numitems++;
}
else /* RECOGNIZE command */
if (recognize (extended_name, entry, name_length, ++numitems))
break;
}
if (l_for_lognames)
endpwent ();
else
FREE_DIR (dir_fd);
try_next_path:
if (l_for_command && *path &&
(path = dir_f_path (path, dir), dir))
goto cmdloop;
if (command == RECOGNIZE && numitems > 0)
{
if (l_for_lognames)
copyn (word, "~", 1);
else if (l_for_command)
word[0] = 0;
else
copyn (word, dir, max_word_length); /* put back dir part */
catn (word, extended_name, max_word_length); /* add extended name */
while (*wp) (*routine) (*wp++);
return (numitems);
}
if (command == LIST)
{
qsort (items, numitems, sizeof (items[1]), fcompare);
print_by_column (l_for_lognames ? NULL:tilded_dir, items,
numitems, l_for_command);
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)
char *extended_name, *entry;
{
if (numitems == 1) /* 1st match */
copyn (extended_name, entry, MAXNAMLEN);
else /* 2nd and subsequent matches */
{
register char *x, *ent;
register int len = 0;
for (x = extended_name, 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)
char *check,
*template;
{
register char *check_char,
*templ_char;
check_char = check;
templ_char = template;
do
if (*check_char == 0)
return (TRUE);
while (*check_char++ == *templ_char++);
return (FALSE);
}
starting_a_command (wordstart, inputline)
register char *wordstart, *inputline;
{
static char
*cmdstart = ";&(|`",
*cmdalive = " \t'\"";
while (--wordstart >= inputline)
{
if (index (cmdstart, *wordstart))
break;
if (!index (cmdalive, *wordstart))
return (FALSE);
}
if (wordstart > inputline && *wordstart == '&') /* Look for >& */
{
while (wordstart > inputline &&
(*--wordstart == ' ' || *wordstart == '\t'));
if (*wordstart == '>')
return (FALSE);
}
return (TRUE);
}
tenematch (inputline, inline_size, num_read, command, command_routine)
char *inputline; /* match string prefix */
int inline_size; /* max size of string */
int num_read; /* # actually in inputline */
COMMAND command; /* LIST or RECOGNIZE */
int (*command_routine) (); /* either append char or display char */
{
static char
*delims = " '\"\t;&<>()|^%";
char word [FILSIZ + 1];
register char *str_end, *word_start, *cmd_start, *wp;
int space_left;
int is_a_cmd; /* UNIX command rather than filename */
str_end = &inputline[num_read];
/*
* 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]))
break;
space_left = inline_size - (word_start - inputline) - 1;
is_a_cmd = starting_a_command (word_start, inputline);
for (cmd_start = word_start, wp = word; cmd_start < str_end;
*wp++ = *cmd_start++);
*wp = 0;
return search (word, wp, command, command_routine, space_left, is_a_cmd);
}
char *CharPtr;
static
CharAppend (c)
{
putchar (c);
*CharPtr++ = c;
*CharPtr = 0;
}
tenex (inputline, inline_size)
char *inputline;
int inline_size;
{
register int numitems, num_read;
setup_tty (ON);
termchars ();
while((num_read = read (SHIN, inputline, inline_size)) > 0)
{
register char *str_end, last_char, should_retype;
COMMAND command;
int tty_local = 0; /* tty "local mode" bits */
last_char = inputline[num_read - 1] & 0177;
if (last_char == '\n' || num_read == inline_size)
break;
ioctl (SHIN, TIOCLGET, &tty_local);
if (last_char == ESC) /* RECOGNIZE */
{
if (tty_local & LCTLECH)
printf ("\210\210 \210\210"); /* Erase ^[ */
/*
if (num_read == 1)
{
num_read = tenedit (inputline, inline_size, "");
break;
}
else
*/
command = RECOGNIZE;
num_read--;
}
else /* LIST */
command = LIST,
putchar ('\n');
CharPtr = str_end = &inputline[num_read];
*str_end = '\0';
numitems = tenematch (inputline, inline_size, num_read, command,
command == LIST ? putchar : CharAppend);
flush ();
if (command == RECOGNIZE)
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.
*/
should_retype = FALSE;
if (index (inputline, '\t') /* tab in input line? */
|| (tty_local & LCTLECH) == 0) /* Control chars don't echo? */
{
back_to_col_1 ();
should_retype = TRUE;
}
if (command == LIST) /* Always retype after LIST */
should_retype = TRUE;
if (should_retype)
printprompt ();
pushback (inputline);
if (should_retype)
retype ();
}
setup_tty (OFF);
return (num_read);
}
#ifdef TEST
short SHIN = 0, SHOUT = 1;
printprompt ()
{
(void) write (SHOUT, "-> ", 3);
return (1);
}
main (argc, argv)
char **argv;
{
char string[128];
int n;
while (printprompt () && (n = tenex (string, 127)) > 0)
{
string[n] = '\0';
printf ("Tenex returns \"%s\"\n", string);
}
}
#endif