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