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; 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 (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 /* XXX: no error recovery provision. */ 180 (void) ioctl(SHIN, TIOCSTI, mbc + i); 181 } 182 } 183 184 if (tty.c_lflag != tty_new.c_lflag) 185 (void) ioctl(SHIN, TCSETS, (char *)&tty_new); 186 (void) sigsetmask(omask); 187 } 188 189 /* 190 * Concatenate src onto tail of des. 191 * Des is a string whose maximum length is count. 192 * Always null terminate. 193 */ 194 void 195 catn(tchar *des, tchar *src, int 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 int 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 void 221 copyn(tchar *des, tchar *src, int count) 222 { 223 224 #ifdef TRACE 225 tprintf("TRACE- copyn()\n"); 226 #endif 227 while (--count >= 0) 228 if ((*des++ = *src++) == '\0') 229 return; 230 *des = '\0'; 231 } 232 233 /* 234 * For qsort() 235 */ 236 static int 237 fcompare(tchar **file1, tchar **file2) 238 { 239 240 #ifdef TRACE 241 tprintf("TRACE- fcompare()\n"); 242 #endif 243 return (strcoll_(*file1, *file2)); 244 } 245 246 static char 247 filetype(tchar *dir, tchar *file, int nosym) 248 { 249 tchar path[MAXPATHLEN + 1]; 250 struct stat statb; 251 252 #ifdef TRACE 253 tprintf("TRACE- filetype()\n"); 254 #endif 255 if (dir) { 256 catn(strcpy_(path, dir), file, MAXPATHLEN); 257 if (nosym) { 258 if (stat_(path, &statb) < 0) 259 return (' '); 260 } else { 261 if (lstat_(path, &statb) < 0) 262 return (' '); 263 } 264 if ((statb.st_mode & S_IFMT) == S_IFLNK) 265 return ('@'); 266 if ((statb.st_mode & S_IFMT) == S_IFDIR) 267 return ('/'); 268 if (((statb.st_mode & S_IFMT) == S_IFREG) && 269 (statb.st_mode & 011)) 270 return ('*'); 271 } 272 return (' '); 273 } 274 275 /* 276 * Print sorted down columns 277 */ 278 static void 279 print_by_column(tchar *dir, tchar *items[], int count, int looking_for_command) 280 { 281 int i, rows, r, c, maxwidth = 0, columns; 282 283 #ifdef TRACE 284 tprintf("TRACE- print_by_column()\n"); 285 #endif 286 for (i = 0; i < count; i++) 287 maxwidth = max(maxwidth, tswidth(items[i])); 288 289 /* for the file tag and space */ 290 maxwidth += looking_for_command ? 1 : 2; 291 columns = max(78 / maxwidth, 1); 292 rows = (count + (columns - 1)) / columns; 293 294 for (r = 0; r < rows; r++) { 295 for (c = 0; c < columns; c++) { 296 i = c * rows + r; 297 if (i < count) { 298 int w; 299 300 /* 301 * Print filename followed by 302 * '@' or '/' or '*' or ' ' 303 */ 304 printf("%t", items[i]); 305 w = tswidth(items[i]); 306 if (!looking_for_command) { 307 printf("%c", 308 (tchar) filetype(dir, items[i], 0)); 309 w++; 310 } 311 if (c < columns - 1) /* last column? */ 312 for (; w < maxwidth; w++) 313 printf(" "); 314 } 315 } 316 printf("\n"); 317 } 318 } 319 320 /* 321 * Expand file name with possible tilde usage 322 * ~person/mumble 323 * expands to 324 * home_directory_of_person/mumble 325 */ 326 tchar * 327 tilde(tchar *new, tchar *old) 328 { 329 tchar *o, *p; 330 struct passwd *pw; 331 static tchar person[40]; 332 char person_[40]; /* work */ 333 tchar *pw_dir; /* work */ 334 335 #ifdef TRACE 336 tprintf("TRACE- tilde()\n"); 337 #endif 338 if (old[0] != '~') 339 return (strcpy_(new, old)); 340 341 for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++) 342 ; 343 *p = '\0'; 344 if (person[0] == '\0') 345 (void) strcpy_(new, value(S_home /*"home"*/)); 346 else { 347 pw = getpwnam(tstostr(person_,person)); 348 if (pw == NULL) 349 return (NULL); 350 pw_dir = strtots((tchar *)NULL, pw->pw_dir); /* allocate */ 351 (void) strcpy_(new, pw_dir); 352 xfree(pw_dir); /* free it */ 353 } 354 (void) strcat_(new, o); 355 return (new); 356 } 357 358 /* 359 * Cause pending line to be printed 360 */ 361 static void 362 sim_retype(void) 363 { 364 #ifdef notdef 365 struct termios tty_pending; 366 367 #ifdef TRACE 368 tprintf("TRACE- sim_retypr()\n"); 369 #endif 370 tty_pending = tty_new; 371 tty_pending.c_lflag |= PENDIN; 372 373 (void) ioctl(SHIN, TCSETS, (char *)&tty_pending); 374 #else 375 #ifdef TRACE 376 tprintf("TRACE- sim_retype()\n"); 377 #endif 378 (void) write(SHOUT, CTRLR, strlen(CTRLR)); 379 printprompt(); 380 #endif 381 } 382 383 static int 384 beep_outc(int c) 385 { 386 char buf[1]; 387 388 buf[0] = c; 389 390 (void) write (SHOUT, buf, 1); 391 392 return 0; 393 } 394 395 static void 396 beep(void) 397 { 398 399 #ifdef TRACE 400 tprintf("TRACE- beep()\n"); 401 #endif 402 if (adrof(S_nobeep /*"nobeep" */) == 0) 403 (void) tputs (BELL, 0, beep_outc); 404 } 405 406 /* 407 * Erase that silly ^[ and print the recognized part of the string. 408 */ 409 static void 410 print_recognized_stuff(tchar *recognized_part) 411 { 412 int unit = didfds ? 1 : SHOUT; 413 414 #ifdef TRACE 415 tprintf("TRACE- print_recognized_stuff()\n"); 416 #endif 417 418 /* 419 * An optimized erasing of that silly ^[ 420 * 421 * One would think that line speeds have become fast enough that this 422 * isn't necessary, but it turns out that the visual difference is 423 * quite noticeable. 424 */ 425 flush(); 426 switch (tswidth(recognized_part)) { 427 case 0: 428 /* erase two characters: ^[ */ 429 write(unit, "\b\b \b\b", sizeof "\b\b \b\b" - 1); 430 break; 431 432 case 1: 433 /* overstrike the ^, erase the [ */ 434 write(unit, "\b\b", 2); 435 printf("%t", recognized_part); 436 write(unit, " \b\b", 4); 437 break; 438 439 default: 440 /* overstrike both characters ^[ */ 441 write(unit, "\b\b", 2); 442 printf("%t", recognized_part); 443 break; 444 } 445 flush(); 446 } 447 448 /* 449 * Parse full path in file into 2 parts: directory and file names 450 * Should leave final slash (/) at end of dir. 451 */ 452 static void 453 extract_dir_and_name(tchar *path, tchar *dir, tchar *name) 454 { 455 tchar *p; 456 457 #ifdef TRACE 458 tprintf("TRACE- extract_dir_and_name()\n"); 459 #endif 460 p = rindex_(path, '/'); 461 if (p == NOSTR) { 462 copyn(name, path, MAXNAMLEN); 463 dir[0] = '\0'; 464 } else { 465 copyn(name, ++p, MAXNAMLEN); 466 copyn(dir, path, p - path); 467 } 468 } 469 470 tchar * 471 getentry(DIR *dir_fd, int looking_for_lognames) 472 { 473 struct passwd *pw; 474 struct dirent *dirp; 475 /* 476 * For char * -> tchar * Conversion 477 */ 478 static tchar strbuf[MAXNAMLEN+1]; 479 480 #ifdef TRACE 481 tprintf("TRACE- getentry()\n"); 482 #endif 483 if (looking_for_lognames) { 484 if ((pw = getpwent ()) == NULL) 485 return (NULL); 486 return (strtots(strbuf,pw->pw_name)); 487 } 488 if (dirp = readdir(dir_fd)) 489 return (strtots(strbuf,dirp->d_name)); 490 return (NULL); 491 } 492 493 static void 494 free_items(tchar **items) 495 { 496 int i; 497 498 #ifdef TRACE 499 tprintf("TRACE- free_items()\n"); 500 #endif 501 for (i = 0; items[i]; i++) 502 free(items[i]); 503 free( (char *)items); 504 } 505 506 #define FREE_ITEMS(items) { \ 507 int omask;\ 508 \ 509 omask = sigblock(sigmask(SIGINT));\ 510 free_items(items);\ 511 items = NULL;\ 512 (void) sigsetmask(omask);\ 513 } 514 515 /* 516 * Perform a RECOGNIZE or LIST command on string "word". 517 */ 518 static int 519 search2(tchar *word, COMMAND command, int max_word_length) 520 { 521 static tchar **items = NULL; 522 DIR *dir_fd; 523 int numitems = 0, ignoring = TRUE, nignored = 0; 524 int name_length, looking_for_lognames; 525 tchar tilded_dir[MAXPATHLEN + 1], dir[MAXPATHLEN + 1]; 526 tchar name[MAXNAMLEN + 1], extended_name[MAXNAMLEN+1]; 527 tchar *entry; 528 #define MAXITEMS 1024 529 #ifdef TRACE 530 tprintf("TRACE- search2()\n"); 531 #endif 532 533 if (items != NULL) 534 FREE_ITEMS(items); 535 536 looking_for_lognames = (*word == '~') && (index_(word, '/') == NULL); 537 if (looking_for_lognames) { 538 (void) setpwent(); 539 copyn(name, &word[1], MAXNAMLEN); /* name sans ~ */ 540 } else { 541 extract_dir_and_name(word, dir, name); 542 if (tilde(tilded_dir, dir) == 0) 543 return (0); 544 dir_fd = opendir_(*tilded_dir ? tilded_dir : S_DOT /*"."*/); 545 if (dir_fd == NULL) 546 return (0); 547 } 548 549 again: /* search for matches */ 550 name_length = strlen_(name); 551 for (numitems = 0; entry = getentry(dir_fd, looking_for_lognames); ) { 552 if (!is_prefix(name, entry)) 553 continue; 554 /* Don't match . files on null prefix match */ 555 if (name_length == 0 && entry[0] == '.' && 556 !looking_for_lognames) 557 continue; 558 if (command == LIST) { 559 if (numitems >= MAXITEMS) { 560 printf ("\nYikes!! Too many %s!!\n", 561 looking_for_lognames ? 562 "names in password file":"files"); 563 break; 564 } 565 if (items == NULL) 566 items = (tchar **) calloc(sizeof (items[1]), 567 MAXITEMS+1); 568 items[numitems] = (tchar *)xalloc((unsigned)(strlen_(entry) + 1)*sizeof(tchar)); 569 copyn(items[numitems], entry, MAXNAMLEN); 570 numitems++; 571 } else { /* RECOGNIZE command */ 572 if (ignoring && ignored(entry)) 573 nignored++; 574 else if (recognize(extended_name, 575 entry, name_length, ++numitems)) 576 break; 577 } 578 } 579 if (ignoring && numitems == 0 && nignored > 0) { 580 ignoring = FALSE; 581 nignored = 0; 582 if (looking_for_lognames) 583 (void)setpwent(); 584 else 585 rewinddir(dir_fd); 586 goto again; 587 } 588 589 if (looking_for_lognames) 590 (void) endpwent(); 591 else { 592 unsetfd(dir_fd->dd_fd); 593 closedir_(dir_fd); 594 } 595 if (command == RECOGNIZE && numitems > 0) { 596 if (looking_for_lognames) 597 copyn(word, S_TIL /*"~" */, 1); 598 else 599 /* put back dir part */ 600 copyn(word, dir, max_word_length); 601 /* add extended name */ 602 catn(word, extended_name, max_word_length); 603 return (numitems); 604 } 605 if (command == LIST) { 606 qsort( (char *)items, numitems, sizeof(items[1]), 607 (int (*)(const void *, const void *))fcompare); 608 /* 609 * Never looking for commands in this version, so final 610 * argument forced to 0. If command name completion is 611 * reinstated, this must change. 612 */ 613 print_by_column(looking_for_lognames ? NULL : tilded_dir, 614 items, numitems, 0); 615 if (items != NULL) 616 FREE_ITEMS(items); 617 } 618 return (0); 619 } 620 621 /* 622 * Object: extend what user typed up to an ambiguity. 623 * Algorithm: 624 * On first match, copy full entry (assume it'll be the only match) 625 * On subsequent matches, shorten extended_name to the first 626 * character mismatch between extended_name and entry. 627 * If we shorten it back to the prefix length, stop searching. 628 */ 629 int 630 recognize(tchar *extended_name, tchar *entry, int name_length, int numitems) 631 { 632 633 #ifdef TRACE 634 tprintf("TRACE- recognize()\n"); 635 #endif 636 if (numitems == 1) /* 1st match */ 637 copyn(extended_name, entry, MAXNAMLEN); 638 else { /* 2nd and subsequent matches */ 639 tchar *x, *ent; 640 int len = 0; 641 642 x = extended_name; 643 for (ent = entry; *x && *x == *ent++; x++, len++) 644 ; 645 *x = '\0'; /* Shorten at 1st char diff */ 646 if (len == name_length) /* Ambiguous to prefix? */ 647 return (-1); /* So stop now and save time */ 648 } 649 return (0); 650 } 651 652 /* 653 * Return true if check items initial chars in template 654 * This differs from PWB imatch in that if check is null 655 * it items anything 656 */ 657 static int 658 is_prefix(tchar *check, tchar *template) 659 { 660 #ifdef TRACE 661 tprintf("TRACE- is_prefix()\n"); 662 #endif 663 664 do 665 if (*check == 0) 666 return (TRUE); 667 while (*check++ == *template++); 668 return (FALSE); 669 } 670 671 /* 672 * Return true if the chars in template appear at the 673 * end of check, i.e., are its suffix. 674 */ 675 static int 676 is_suffix(tchar *check, tchar *template) 677 { 678 tchar *c, *t; 679 680 #ifdef TRACE 681 tprintf("TRACE- is_suffix()\n"); 682 #endif 683 for (c = check; *c++;) 684 ; 685 for (t = template; *t++;) 686 ; 687 for (;;) { 688 if (t == template) 689 return (TRUE); 690 if (c == check || *--t != *--c) 691 return (FALSE); 692 } 693 } 694 695 int 696 tenex(tchar *inputline, int inputline_size) 697 { 698 int numitems, num_read, should_retype; 699 int i; 700 701 #ifdef TRACE 702 tprintf("TRACE- tenex()\n"); 703 #endif 704 setup_tty(ON); 705 termchars(); 706 num_read = 0; 707 should_retype = FALSE; 708 while ((i = read_(SHIN, inputline+num_read, inputline_size-num_read)) 709 > 0) { 710 static tchar *delims = S_DELIM /*" '\"\t;&<>()|`"*/; 711 tchar *str_end, *word_start, last_char; 712 int space_left; 713 struct termios tty; 714 COMMAND command; 715 716 num_read += i; 717 inputline[num_read] = '\0'; 718 last_char = inputline[num_read - 1] & TRIM; 719 720 if ((num_read == inputline_size) || (last_char == '\n')) 721 break; 722 723 str_end = &inputline[num_read]; 724 if (last_char == ESC) { 725 command = RECOGNIZE; 726 *--str_end = '\0'; /* wipe out trailing ESC */ 727 } else 728 command = LIST; 729 730 tty = tty_new; 731 tty.c_lflag &= ~ECHO; 732 (void) ioctl(SHIN, TCSETSF, (char *)&tty); 733 734 if (command == LIST) 735 printf("\n"); 736 /* 737 * Find LAST occurence of a delimiter in the inputline. 738 * The word start is one character past it. 739 */ 740 for (word_start = str_end; word_start > inputline; 741 --word_start) { 742 if (index_(delims, word_start[-1]) || 743 isauxsp(word_start[-1])) 744 break; 745 } 746 space_left = inputline_size - (word_start - inputline) - 1; 747 numitems = search2(word_start, command, space_left); 748 749 /* 750 * Tabs in the input line cause trouble after a pushback. 751 * tty driver won't backspace over them because column 752 * positions are now incorrect. This is solved by retyping 753 * over current line. 754 */ 755 if (index_(inputline, '\t')) { /* tab tchar in input line? */ 756 back_to_col_1(); 757 should_retype = TRUE; 758 } 759 if (command == LIST) /* Always retype after a LIST */ 760 should_retype = TRUE; 761 if (should_retype) 762 printprompt(); 763 pushback(inputline, should_retype); 764 num_read = 0; /* chars will be reread */ 765 should_retype = FALSE; 766 767 /* 768 * Avoid a race condition by echoing what we're recognized 769 * _after_ pushing back the command line. This way, if the 770 * user waits until seeing this output before typing more 771 * stuff, the resulting keystrokes won't race with the STIed 772 * input we've pushed back. (Of course, if the user types 773 * ahead, the race still exists and it's quite possible that 774 * the pushed back input line will interleave with the 775 * keystrokes in unexpected ways.) 776 */ 777 if (command == RECOGNIZE) { 778 /* print from str_end on */ 779 print_recognized_stuff(str_end); 780 if (numitems != 1) /* Beep = No match/ambiguous */ 781 beep(); 782 } 783 } 784 setup_tty(OFF); 785 return (num_read); 786 } 787 788 static int 789 ignored(tchar *entry) 790 { 791 struct varent *vp; 792 tchar **cp; 793 794 #ifdef TRACE 795 tprintf("TRACE- ignored()\n"); 796 #endif 797 if ((vp = adrof(S_fignore /*"fignore"*/)) == NULL || 798 (cp = vp->vec) == NULL) 799 return (FALSE); 800 for (; *cp != NULL; cp++) 801 if (is_suffix(entry, *cp)) 802 return (TRUE); 803 return (FALSE); 804 } 805 #endif /* FILEC */ 806