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