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