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