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