xref: /netbsd-src/bin/csh/file.c (revision ae9172d6cd9432a6a1a56760d86b32c57a66c39c)
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