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