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