1 /*
2 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
4 */
5
6 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
7 /* All Rights Reserved */
8
9 /*
10 * Copyright (c) 1980 Regents of the University of California.
11 * All rights reserved. The Berkeley Software License Agreement
12 * specifies the terms and conditions for redistribution.
13 */
14
15 #ifdef FILEC
16 /*
17 * Tenex style file name recognition, .. and more.
18 * History:
19 * Author: Ken Greer, Sept. 1975, CMU.
20 * Finally got around to adding to the Cshell., Ken Greer, Dec. 1981.
21 */
22
23 #include "sh.h"
24 #include <sys/types.h>
25 #include <dirent.h>
26 #include <pwd.h>
27 #include "sh.tconst.h"
28
29 #define TRUE 1
30 #define FALSE 0
31 #define ON 1
32 #define OFF 0
33
34 #define ESC '\033'
35
36 extern DIR *opendir_(tchar *);
37
38 static char *BELL = "\07";
39 static char *CTRLR = "^R\n";
40
41 typedef enum {LIST, RECOGNIZE} COMMAND;
42
43 static jmp_buf osetexit; /* saved setexit() state */
44 static struct termios tty_save; /* saved terminal state */
45 static struct termios tty_new; /* new terminal state */
46
47 static int is_prefix(tchar *, tchar *);
48 static int is_suffix(tchar *, tchar *);
49 static int ignored(tchar *);
50
51 /*
52 * Put this here so the binary can be patched with adb to enable file
53 * completion by default. Filec controls completion, nobeep controls
54 * ringing the terminal bell on incomplete expansions.
55 */
56 bool filec = 0;
57
58 static void
setup_tty(int on)59 setup_tty(int on)
60 {
61 int omask;
62 #ifdef TRACE
63 tprintf("TRACE- setup_tty()\n");
64 #endif
65
66 omask = sigblock(sigmask(SIGINT));
67 if (on) {
68 /*
69 * The shell makes sure that the tty is not in some weird state
70 * and fixes it if it is. But it should be noted that the
71 * tenex routine will not work correctly in CBREAK or RAW mode
72 * so this code below is, therefore, mandatory.
73 *
74 * Also, in order to recognize the ESC (filename-completion)
75 * character, set EOL to ESC. This way, ESC will terminate
76 * the line, but still be in the input stream.
77 * EOT (filename list) will also terminate the line,
78 * but will not appear in the input stream.
79 *
80 * The getexit/setexit contortions ensure that the
81 * tty state will be restored if the user types ^C.
82 */
83 (void) ioctl(SHIN, TCGETS, (char *)&tty_save);
84 getexit(osetexit);
85 if (setjmp(reslab)) {
86 (void) ioctl(SHIN, TCSETSW, (char *)&tty_save);
87 resexit(osetexit);
88 reset();
89 }
90 tty_new = tty_save;
91 tty_new.c_cc[VEOL] = ESC;
92 tty_new.c_iflag |= IMAXBEL | BRKINT | IGNPAR;
93 tty_new.c_lflag |= ICANON;
94 tty_new.c_lflag |= ECHOCTL;
95 tty_new.c_oflag &= ~OCRNL;
96 (void) ioctl(SHIN, TCSETSW, (char *)&tty_new);
97 } else {
98 /*
99 * Reset terminal state to what user had when invoked
100 */
101 (void) ioctl(SHIN, TCSETSW, (char *)&tty_save);
102 resexit(osetexit);
103 }
104 (void) sigsetmask(omask);
105 }
106
107 static void
termchars(void)108 termchars(void)
109 {
110 extern char *tgetstr();
111 char bp[1024];
112 static char area[256];
113 static int been_here = 0;
114 char *ap = area;
115 char *s;
116 char *term;
117
118 #ifdef TRACE
119 tprintf("TRACE- termchars()\n");
120 #endif
121 if (been_here)
122 return;
123 been_here = TRUE;
124
125 if ((term = getenv("TERM")) == NULL)
126 return;
127 if (tgetent(bp, term) != 1)
128 return;
129 if (s = tgetstr("vb", &ap)) /* Visible Bell */
130 BELL = s;
131 }
132
133 /*
134 * Move back to beginning of current line
135 */
136 static void
back_to_col_1(void)137 back_to_col_1(void)
138 {
139 int omask;
140
141 #ifdef TRACE
142 tprintf("TRACE- back_to_col_1()\n");
143 #endif
144 omask = sigblock(sigmask(SIGINT));
145 (void) write(SHOUT, "\r", 1);
146 (void) sigsetmask(omask);
147 }
148
149 /*
150 * Push string contents back into tty queue
151 */
152 static void
pushback(tchar * string,int echoflag)153 pushback(tchar *string, int echoflag)
154 {
155 tchar *p;
156 struct termios tty;
157 int omask, retry = 0;
158
159 #ifdef TRACE
160 tprintf("TRACE- pushback()\n");
161 #endif
162 omask = sigblock(sigmask(SIGINT));
163 tty = tty_new;
164 if (!echoflag)
165 tty.c_lflag &= ~ECHO;
166
167 again:
168 (void) ioctl(SHIN, TCSETSF, (char *)&tty);
169
170 for (p = string; *p; p++) {
171 char mbc[MB_LEN_MAX];
172 int i, j = wctomb(mbc, (wchar_t)*p);
173
174 if (j < 0) {
175 /* Error! But else what can we do? */
176 continue;
177 }
178 for (i = 0; i < j; ++i) {
179 if (ioctl(SHIN, TIOCSTI, mbc + i) != 0 &&
180 errno == EAGAIN) {
181 if (retry++ < 5)
182 goto again;
183 /* probably no worth retrying any more */
184 }
185 }
186 }
187
188 if (tty.c_lflag != tty_new.c_lflag)
189 (void) ioctl(SHIN, TCSETS, (char *)&tty_new);
190 (void) sigsetmask(omask);
191 }
192
193 /*
194 * Concatenate src onto tail of des.
195 * Des is a string whose maximum length is count.
196 * Always null terminate.
197 */
198 void
catn(tchar * des,tchar * src,int count)199 catn(tchar *des, tchar *src, int count)
200 {
201 #ifdef TRACE
202 tprintf("TRACE- catn()\n");
203 #endif
204
205 while (--count >= 0 && *des)
206 des++;
207 while (--count >= 0)
208 if ((*des++ = *src++) == '\0')
209 return;
210 *des = '\0';
211 }
212
213 static int
max(a,b)214 max(a, b)
215 {
216
217 return (a > b ? a : b);
218 }
219
220 /*
221 * Like strncpy but always leave room for trailing \0
222 * and always null terminate.
223 */
224 void
copyn(tchar * des,tchar * src,int count)225 copyn(tchar *des, tchar *src, int count)
226 {
227
228 #ifdef TRACE
229 tprintf("TRACE- copyn()\n");
230 #endif
231 while (--count >= 0)
232 if ((*des++ = *src++) == '\0')
233 return;
234 *des = '\0';
235 }
236
237 /*
238 * For qsort()
239 */
240 static int
fcompare(tchar ** file1,tchar ** file2)241 fcompare(tchar **file1, tchar **file2)
242 {
243
244 #ifdef TRACE
245 tprintf("TRACE- fcompare()\n");
246 #endif
247 return (strcoll_(*file1, *file2));
248 }
249
250 static char
filetype(tchar * dir,tchar * file,int nosym)251 filetype(tchar *dir, tchar *file, int nosym)
252 {
253 tchar path[MAXPATHLEN + 1];
254 struct stat statb;
255
256 #ifdef TRACE
257 tprintf("TRACE- filetype()\n");
258 #endif
259 if (dir) {
260 catn(strcpy_(path, dir), file, MAXPATHLEN);
261 if (nosym) {
262 if (stat_(path, &statb) < 0)
263 return (' ');
264 } else {
265 if (lstat_(path, &statb) < 0)
266 return (' ');
267 }
268 if ((statb.st_mode & S_IFMT) == S_IFLNK)
269 return ('@');
270 if ((statb.st_mode & S_IFMT) == S_IFDIR)
271 return ('/');
272 if (((statb.st_mode & S_IFMT) == S_IFREG) &&
273 (statb.st_mode & 011))
274 return ('*');
275 }
276 return (' ');
277 }
278
279 /*
280 * Print sorted down columns
281 */
282 static void
print_by_column(tchar * dir,tchar * items[],int count,int looking_for_command)283 print_by_column(tchar *dir, tchar *items[], int count, int looking_for_command)
284 {
285 int i, rows, r, c, maxwidth = 0, columns;
286
287 #ifdef TRACE
288 tprintf("TRACE- print_by_column()\n");
289 #endif
290 for (i = 0; i < count; i++)
291 maxwidth = max(maxwidth, tswidth(items[i]));
292
293 /* for the file tag and space */
294 maxwidth += looking_for_command ? 1 : 2;
295 columns = max(78 / maxwidth, 1);
296 rows = (count + (columns - 1)) / columns;
297
298 for (r = 0; r < rows; r++) {
299 for (c = 0; c < columns; c++) {
300 i = c * rows + r;
301 if (i < count) {
302 int w;
303
304 /*
305 * Print filename followed by
306 * '@' or '/' or '*' or ' '
307 */
308 printf("%t", items[i]);
309 w = tswidth(items[i]);
310 if (!looking_for_command) {
311 printf("%c",
312 (tchar) filetype(dir, items[i], 0));
313 w++;
314 }
315 if (c < columns - 1) /* last column? */
316 for (; w < maxwidth; w++)
317 printf(" ");
318 }
319 }
320 printf("\n");
321 }
322 }
323
324 /*
325 * Expand file name with possible tilde usage
326 * ~person/mumble
327 * expands to
328 * home_directory_of_person/mumble
329 */
330 tchar *
tilde(tchar * new,tchar * old)331 tilde(tchar *new, tchar *old)
332 {
333 tchar *o, *p;
334 struct passwd *pw;
335 static tchar person[40];
336 char person_[40]; /* work */
337 tchar *pw_dir; /* work */
338
339 #ifdef TRACE
340 tprintf("TRACE- tilde()\n");
341 #endif
342 if (old[0] != '~')
343 return (strcpy_(new, old));
344
345 for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++)
346 ;
347 *p = '\0';
348 if (person[0] == '\0')
349 (void) strcpy_(new, value(S_home /* "home" */));
350 else {
351 pw = getpwnam(tstostr(person_, person));
352 if (pw == NULL)
353 return (NULL);
354 pw_dir = strtots((tchar *)NULL, pw->pw_dir); /* allocate */
355 (void) strcpy_(new, pw_dir);
356 xfree(pw_dir); /* free it */
357 }
358 (void) strcat_(new, o);
359 return (new);
360 }
361
362 /*
363 * Cause pending line to be printed
364 */
365 static void
sim_retype(void)366 sim_retype(void)
367 {
368 #ifdef notdef
369 struct termios tty_pending;
370
371 #ifdef TRACE
372 tprintf("TRACE- sim_retypr()\n");
373 #endif
374 tty_pending = tty_new;
375 tty_pending.c_lflag |= PENDIN;
376
377 (void) ioctl(SHIN, TCSETS, (char *)&tty_pending);
378 #else
379 #ifdef TRACE
380 tprintf("TRACE- sim_retype()\n");
381 #endif
382 (void) write(SHOUT, CTRLR, strlen(CTRLR));
383 printprompt();
384 #endif
385 }
386
387 static int
beep_outc(int c)388 beep_outc(int c)
389 {
390 char buf[1];
391
392 buf[0] = c;
393
394 (void) write(SHOUT, buf, 1);
395
396 return 0;
397 }
398
399 static void
beep(void)400 beep(void)
401 {
402
403 #ifdef TRACE
404 tprintf("TRACE- beep()\n");
405 #endif
406 if (adrof(S_nobeep /* "nobeep" */) == 0)
407 (void) tputs(BELL, 0, beep_outc);
408 }
409
410 /*
411 * Erase that silly ^[ and print the recognized part of the string.
412 */
413 static void
print_recognized_stuff(tchar * recognized_part)414 print_recognized_stuff(tchar *recognized_part)
415 {
416 int unit = didfds ? 1 : SHOUT;
417
418 #ifdef TRACE
419 tprintf("TRACE- print_recognized_stuff()\n");
420 #endif
421
422 /*
423 * An optimized erasing of that silly ^[
424 *
425 * One would think that line speeds have become fast enough that this
426 * isn't necessary, but it turns out that the visual difference is
427 * quite noticeable.
428 */
429 flush();
430 switch (tswidth(recognized_part)) {
431 case 0:
432 /* erase two characters: ^[ */
433 write(unit, "\b\b \b\b", sizeof "\b\b \b\b" - 1);
434 break;
435
436 case 1:
437 /* overstrike the ^, erase the [ */
438 write(unit, "\b\b", 2);
439 printf("%t", recognized_part);
440 write(unit, " \b\b", 4);
441 break;
442
443 default:
444 /* overstrike both characters ^[ */
445 write(unit, "\b\b", 2);
446 printf("%t", recognized_part);
447 break;
448 }
449 flush();
450 }
451
452 /*
453 * Parse full path in file into 2 parts: directory and file names
454 * Should leave final slash (/) at end of dir.
455 */
456 static void
extract_dir_and_name(tchar * path,tchar * dir,tchar * name)457 extract_dir_and_name(tchar *path, tchar *dir, tchar *name)
458 {
459 tchar *p;
460
461 #ifdef TRACE
462 tprintf("TRACE- extract_dir_and_name()\n");
463 #endif
464 p = rindex_(path, '/');
465 if (p == NOSTR) {
466 copyn(name, path, MAXNAMLEN);
467 dir[0] = '\0';
468 } else {
469 copyn(name, ++p, MAXNAMLEN);
470 copyn(dir, path, p - path);
471 }
472 }
473
474 tchar *
getentry(DIR * dir_fd,int looking_for_lognames)475 getentry(DIR *dir_fd, int looking_for_lognames)
476 {
477 struct passwd *pw;
478 struct dirent *dirp;
479 /*
480 * For char * -> tchar * Conversion
481 */
482 static tchar strbuf[MAXNAMLEN+1];
483
484 #ifdef TRACE
485 tprintf("TRACE- getentry()\n");
486 #endif
487 if (looking_for_lognames) {
488 if ((pw = getpwent()) == NULL)
489 return (NULL);
490 return (strtots(strbuf, pw->pw_name));
491 }
492 if (dirp = readdir(dir_fd))
493 return (strtots(strbuf, dirp->d_name));
494 return (NULL);
495 }
496
497 static void
free_items(tchar ** items)498 free_items(tchar **items)
499 {
500 int i;
501
502 #ifdef TRACE
503 tprintf("TRACE- free_items()\n");
504 #endif
505 for (i = 0; items[i]; i++)
506 xfree(items[i]);
507 xfree((char *)items);
508 }
509
510 #define FREE_ITEMS(items) { \
511 int omask;\
512 \
513 omask = sigblock(sigmask(SIGINT));\
514 free_items(items);\
515 items = NULL;\
516 (void) sigsetmask(omask);\
517 }
518
519 /*
520 * Perform a RECOGNIZE or LIST command on string "word".
521 */
522 static int
search2(tchar * word,COMMAND command,int max_word_length)523 search2(tchar *word, COMMAND command, int max_word_length)
524 {
525 static tchar **items = NULL;
526 DIR *dir_fd;
527 int numitems = 0, ignoring = TRUE, nignored = 0;
528 int name_length, looking_for_lognames;
529 tchar tilded_dir[MAXPATHLEN + 1], dir[MAXPATHLEN + 1];
530 tchar name[MAXNAMLEN + 1], extended_name[MAXNAMLEN+1];
531 tchar *entry;
532 #define MAXITEMS 1024
533 #ifdef TRACE
534 tprintf("TRACE- search2()\n");
535 #endif
536
537 if (items != NULL)
538 FREE_ITEMS(items);
539
540 looking_for_lognames = (*word == '~') && (index_(word, '/') == NULL);
541 if (looking_for_lognames) {
542 (void) setpwent();
543 copyn(name, &word[1], MAXNAMLEN); /* name sans ~ */
544 } else {
545 extract_dir_and_name(word, dir, name);
546 if (tilde(tilded_dir, dir) == 0)
547 return (0);
548 dir_fd = opendir_(*tilded_dir ? tilded_dir : S_DOT /* "." */);
549 if (dir_fd == NULL)
550 return (0);
551 }
552
553 again: /* search for matches */
554 name_length = strlen_(name);
555 for (numitems = 0; entry = getentry(dir_fd, looking_for_lognames); ) {
556 if (!is_prefix(name, entry))
557 continue;
558 /* Don't match . files on null prefix match */
559 if (name_length == 0 && entry[0] == '.' &&
560 !looking_for_lognames)
561 continue;
562 if (command == LIST) {
563 if (numitems >= MAXITEMS) {
564 printf("\nYikes!! Too many %s!!\n",
565 looking_for_lognames ?
566 "names in password file":"files");
567 break;
568 }
569 if (items == NULL)
570 items = (tchar **)xcalloc(sizeof (items[1]),
571 MAXITEMS+1);
572 items[numitems] = (tchar *)xalloc((unsigned)(strlen_(entry) + 1) * sizeof (tchar));
573 copyn(items[numitems], entry, MAXNAMLEN);
574 numitems++;
575 } else { /* RECOGNIZE command */
576 if (ignoring && ignored(entry))
577 nignored++;
578 else if (recognize(extended_name,
579 entry, name_length, ++numitems))
580 break;
581 }
582 }
583 if (ignoring && numitems == 0 && nignored > 0) {
584 ignoring = FALSE;
585 nignored = 0;
586 if (looking_for_lognames)
587 (void) setpwent();
588 else
589 rewinddir(dir_fd);
590 goto again;
591 }
592
593 if (looking_for_lognames)
594 (void) endpwent();
595 else {
596 unsetfd(dir_fd->dd_fd);
597 closedir_(dir_fd);
598 }
599 if (command == RECOGNIZE && numitems > 0) {
600 if (looking_for_lognames)
601 copyn(word, S_TIL /* "~" */, 1);
602 else
603 /* put back dir part */
604 copyn(word, dir, max_word_length);
605 /* add extended name */
606 catn(word, extended_name, max_word_length);
607 return (numitems);
608 }
609 if (command == LIST) {
610 qsort((char *)items, numitems, sizeof (items[1]),
611 (int (*)(const void *, const void *))fcompare);
612 /*
613 * Never looking for commands in this version, so final
614 * argument forced to 0. If command name completion is
615 * reinstated, this must change.
616 */
617 print_by_column(looking_for_lognames ? NULL : tilded_dir,
618 items, numitems, 0);
619 if (items != NULL)
620 FREE_ITEMS(items);
621 }
622 return (0);
623 }
624
625 /*
626 * Object: extend what user typed up to an ambiguity.
627 * Algorithm:
628 * On first match, copy full entry (assume it'll be the only match)
629 * On subsequent matches, shorten extended_name to the first
630 * character mismatch between extended_name and entry.
631 * If we shorten it back to the prefix length, stop searching.
632 */
633 int
recognize(tchar * extended_name,tchar * entry,int name_length,int numitems)634 recognize(tchar *extended_name, tchar *entry, int name_length, int numitems)
635 {
636
637 #ifdef TRACE
638 tprintf("TRACE- recognize()\n");
639 #endif
640 if (numitems == 1) /* 1st match */
641 copyn(extended_name, entry, MAXNAMLEN);
642 else { /* 2nd and subsequent matches */
643 tchar *x, *ent;
644 int len = 0;
645
646 x = extended_name;
647 for (ent = entry; *x && *x == *ent++; x++, len++)
648 ;
649 *x = '\0'; /* Shorten at 1st char diff */
650 if (len == name_length) /* Ambiguous to prefix? */
651 return (-1); /* So stop now and save time */
652 }
653 return (0);
654 }
655
656 /*
657 * Return true if check items initial chars in template
658 * This differs from PWB imatch in that if check is null
659 * it items anything
660 */
661 static int
is_prefix(tchar * check,tchar * template)662 is_prefix(tchar *check, tchar *template)
663 {
664 #ifdef TRACE
665 tprintf("TRACE- is_prefix()\n");
666 #endif
667
668 do
669 if (*check == 0)
670 return (TRUE);
671 while (*check++ == *template++);
672 return (FALSE);
673 }
674
675 /*
676 * Return true if the chars in template appear at the
677 * end of check, i.e., are its suffix.
678 */
679 static int
is_suffix(tchar * check,tchar * template)680 is_suffix(tchar *check, tchar *template)
681 {
682 tchar *c, *t;
683
684 #ifdef TRACE
685 tprintf("TRACE- is_suffix()\n");
686 #endif
687 for (c = check; *c++; )
688 ;
689 for (t = template; *t++; )
690 ;
691 for (;;) {
692 if (t == template)
693 return (TRUE);
694 if (c == check || *--t != *--c)
695 return (FALSE);
696 }
697 }
698
699 int
tenex(tchar * inputline,int inputline_size)700 tenex(tchar *inputline, int inputline_size)
701 {
702 int numitems, num_read, should_retype;
703 int i;
704
705 #ifdef TRACE
706 tprintf("TRACE- tenex()\n");
707 #endif
708 setup_tty(ON);
709 termchars();
710 num_read = 0;
711 should_retype = FALSE;
712 while ((i = read_(SHIN, inputline+num_read, inputline_size-num_read))
713 > 0) {
714 static tchar *delims = S_DELIM /* " '\"\t;&<>()|`" */;
715 tchar *str_end, *word_start, last_char;
716 int space_left;
717 struct termios tty;
718 COMMAND command;
719
720 num_read += i;
721 inputline[num_read] = '\0';
722 last_char = inputline[num_read - 1] & TRIM;
723
724 /*
725 * read_() can return more than requested size if there
726 * is multibyte character at the end.
727 */
728 if ((num_read >= inputline_size) || (last_char == '\n'))
729 break;
730
731 str_end = &inputline[num_read];
732 if (last_char == ESC) {
733 command = RECOGNIZE;
734 *--str_end = '\0'; /* wipe out trailing ESC */
735 } else
736 command = LIST;
737
738 tty = tty_new;
739 tty.c_lflag &= ~ECHO;
740 (void) ioctl(SHIN, TCSETSF, (char *)&tty);
741
742 if (command == LIST)
743 printf("\n");
744 /*
745 * Find LAST occurence of a delimiter in the inputline.
746 * The word start is one character past it.
747 */
748 for (word_start = str_end; word_start > inputline;
749 --word_start) {
750 if (index_(delims, word_start[-1]) ||
751 isauxsp(word_start[-1]))
752 break;
753 }
754 space_left = inputline_size - (word_start - inputline) - 1;
755 numitems = search2(word_start, command, space_left);
756
757 /*
758 * Tabs in the input line cause trouble after a pushback.
759 * tty driver won't backspace over them because column
760 * positions are now incorrect. This is solved by retyping
761 * over current line.
762 */
763 if (index_(inputline, '\t')) { /* tab tchar in input line? */
764 back_to_col_1();
765 should_retype = TRUE;
766 }
767 if (command == LIST) /* Always retype after a LIST */
768 should_retype = TRUE;
769 if (should_retype)
770 printprompt();
771 pushback(inputline, should_retype);
772 num_read = 0; /* chars will be reread */
773 should_retype = FALSE;
774
775 /*
776 * Avoid a race condition by echoing what we're recognized
777 * _after_ pushing back the command line. This way, if the
778 * user waits until seeing this output before typing more
779 * stuff, the resulting keystrokes won't race with the STIed
780 * input we've pushed back. (Of course, if the user types
781 * ahead, the race still exists and it's quite possible that
782 * the pushed back input line will interleave with the
783 * keystrokes in unexpected ways.)
784 */
785 if (command == RECOGNIZE) {
786 /* print from str_end on */
787 print_recognized_stuff(str_end);
788 if (numitems != 1) /* Beep = No match/ambiguous */
789 beep();
790 }
791 }
792 setup_tty(OFF);
793 return (num_read);
794 }
795
796 static int
ignored(tchar * entry)797 ignored(tchar *entry)
798 {
799 struct varent *vp;
800 tchar **cp;
801
802 #ifdef TRACE
803 tprintf("TRACE- ignored()\n");
804 #endif
805 if ((vp = adrof(S_fignore /* "fignore" */)) == NULL ||
806 (cp = vp->vec) == NULL)
807 return (FALSE);
808 for (; *cp != NULL; cp++)
809 if (is_suffix(entry, *cp))
810 return (TRUE);
811 return (FALSE);
812 }
813 #endif /* FILEC */
814