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