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