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