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 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 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 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 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 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 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 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 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 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 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 * 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 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 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 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 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 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 * 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 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 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 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 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 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 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 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