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