xref: /original-bsd/bin/csh/file.c (revision 2301fdfb)
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.8 (Berkeley) 05/19/88";
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 #define MAXITEMS 1024
367 
368 	if (items != NULL)
369 		FREE_ITEMS(items);
370 
371 	looking_for_lognames = (*word == '~') && (index(word, '/') == NULL);
372 	if (looking_for_lognames) {
373 		(void) setpwent();
374 		copyn(name, &word[1], MAXNAMLEN);	/* name sans ~ */
375 	} else {
376 		extract_dir_and_name(word, dir, name);
377 		if (tilde(tilded_dir, dir) == 0)
378 			return (0);
379 		dir_fd = opendir(*tilded_dir ? tilded_dir : ".");
380 		if (dir_fd == NULL)
381 			return (0);
382 	}
383 
384 again:	/* search for matches */
385 	name_length = strlen(name);
386 	for (numitems = 0; entry = getentry(dir_fd, looking_for_lognames); ) {
387 		if (!is_prefix(name, entry))
388 			continue;
389 		/* Don't match . files on null prefix match */
390 		if (name_length == 0 && entry[0] == '.' &&
391 		    !looking_for_lognames)
392 			continue;
393 		if (command == LIST) {
394 			if (numitems >= MAXITEMS) {
395 				printf ("\nYikes!! Too many %s!!\n",
396 				    looking_for_lognames ?
397 					"names in password file":"files");
398 				break;
399 			}
400 			if (items == NULL)
401 				items = (char **) calloc(sizeof (items[1]),
402 				    MAXITEMS);
403 			items[numitems] = xalloc((unsigned)strlen(entry) + 1);
404 			copyn(items[numitems], entry, MAXNAMLEN);
405 			numitems++;
406 		} else {			/* RECOGNIZE command */
407 			if (ignoring && ignored(entry))
408 				nignored++;
409 			else if (recognize(extended_name,
410 			    entry, name_length, ++numitems))
411 				break;
412 		}
413 	}
414 	if (ignoring && numitems == 0 && nignored > 0) {
415 		ignoring = FALSE;
416 		nignored = 0;
417 		if (looking_for_lognames)
418 			(void) setpwent();
419 		else
420 			rewinddir(dir_fd);
421 		goto again;
422 	}
423 
424 	if (looking_for_lognames)
425 		(void) endpwent();
426 	else
427 		closedir(dir_fd);
428 	if (numitems == 0)
429 		return (0);
430 	if (command == RECOGNIZE) {
431 		if (looking_for_lognames)
432 			 copyn(word, "~", 1);
433 		else
434 			/* put back dir part */
435 			copyn(word, dir, max_word_length);
436 		/* add extended name */
437 		catn(word, extended_name, max_word_length);
438 		return (numitems);
439 	}
440 	else { 				/* LIST */
441 		qsort((char *)items, numitems, sizeof(items[1]), sortscmp);
442 		print_by_column(looking_for_lognames ? NULL : tilded_dir,
443 		    items, numitems);
444 		if (items != NULL)
445 			FREE_ITEMS(items);
446 	}
447 	return (0);
448 }
449 
450 /*
451  * Object: extend what user typed up to an ambiguity.
452  * Algorithm:
453  * On first match, copy full entry (assume it'll be the only match)
454  * On subsequent matches, shorten extended_name to the first
455  * character mismatch between extended_name and entry.
456  * If we shorten it back to the prefix length, stop searching.
457  */
458 static
459 recognize(extended_name, entry, name_length, numitems)
460 	char *extended_name, *entry;
461 {
462 
463 	if (numitems == 1)			/* 1st match */
464 		copyn(extended_name, entry, MAXNAMLEN);
465 	else {					/* 2nd & subsequent matches */
466 		register char *x, *ent;
467 		register int len = 0;
468 
469 		x = extended_name;
470 		for (ent = entry; *x && *x == *ent++; x++, len++)
471 			;
472 		*x = '\0';			/* Shorten at 1st char diff */
473 		if (len == name_length)		/* Ambiguous to prefix? */
474 			return (-1);		/* So stop now and save time */
475 	}
476 	return (0);
477 }
478 
479 /*
480  * Return true if check matches initial chars in template.
481  * This differs from PWB imatch in that if check is null
482  * it matches anything.
483  */
484 static
485 is_prefix(check, template)
486 	register char *check, *template;
487 {
488 
489 	do
490 		if (*check == 0)
491 			return (TRUE);
492 	while (*check++ == *template++);
493 	return (FALSE);
494 }
495 
496 /*
497  *  Return true if the chars in template appear at the
498  *  end of check, I.e., are it's suffix.
499  */
500 static
501 is_suffix(check, template)
502 	char *check, *template;
503 {
504 	register char *c, *t;
505 
506 	for (c = check; *c++;)
507 		;
508 	for (t = template; *t++;)
509 		;
510 	for (;;) {
511 		if (t == template)
512 			return 1;
513 		if (c == check || *--t != *--c)
514 			return 0;
515 	}
516 }
517 
518 tenex(inputline, inputline_size)
519 	char *inputline;
520 	int inputline_size;
521 {
522 	register int numitems, num_read;
523 
524 	setup_tty(ON);
525 	while ((num_read = read(SHIN, inputline, inputline_size)) > 0) {
526 		static char *delims = " '\"\t;&<>()|^%";
527 		register char *str_end, *word_start, last_char, should_retype;
528 		register int space_left;
529 		COMMAND command;
530 
531 		last_char = inputline[num_read - 1] & 0177;
532 
533 		if (last_char == '\n' || num_read == inputline_size)
534 			break;
535 		command = (last_char == ESC) ? RECOGNIZE : LIST;
536 		if (command == LIST)
537 			cshputchar('\n');
538 		str_end = &inputline[num_read];
539 		if (last_char == ESC)
540 			--str_end;		/* wipeout trailing cmd char */
541 		*str_end = '\0';
542 		/*
543 		 * Find LAST occurence of a delimiter in the inputline.
544 		 * The word start is one character past it.
545 		 */
546 		for (word_start = str_end; word_start > inputline; --word_start)
547 			if (index(delims, word_start[-1]))
548 				break;
549 		space_left = inputline_size - (word_start - inputline) - 1;
550 		numitems = search(word_start, command, space_left);
551 
552 		if (command == RECOGNIZE) {
553 			/* print from str_end on */
554 			print_recognized_stuff(str_end);
555 			if (numitems != 1)	/* Beep = No match/ambiguous */
556 				beep();
557 		}
558 
559 		/*
560 		 * Tabs in the input line cause trouble after a pushback.
561 		 * tty driver won't backspace over them because column
562 		 * positions are now incorrect. This is solved by retyping
563 		 * over current line.
564 		 */
565 		should_retype = FALSE;
566 		if (index(inputline, '\t')) {	/* tab char in input line? */
567 			back_to_col_1();
568 			should_retype = TRUE;
569 		}
570 		if (command == LIST)		/* Always retype after a LIST */
571 			should_retype = TRUE;
572 		if (should_retype)
573 			printprompt();
574 		pushback(inputline);
575 		if (should_retype)
576 			retype();
577 	}
578 	setup_tty(OFF);
579 	return (num_read);
580 }
581 
582 static
583 ignored(entry)
584 	register char *entry;
585 {
586 	struct varent *vp;
587 	register char **cp;
588 
589 	if ((vp = adrof("fignore")) == NULL || (cp = vp->vec) == NULL)
590 		return (FALSE);
591 	for (; *cp != NULL; cp++)
592 		if (is_suffix(entry, *cp))
593 			return (TRUE);
594 	return (FALSE);
595 }
596 #endif FILEC
597