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