1 /*- 2 * Copyright (c) 1980, 1991, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 static char sccsid[] = "@(#)file.c 8.4 (Berkeley) 05/06/95"; 10 #endif /* not lint */ 11 12 #ifdef FILEC 13 14 #include <sys/param.h> 15 #include <sys/ioctl.h> 16 #include <sys/stat.h> 17 #include <termios.h> 18 #include <dirent.h> 19 #include <pwd.h> 20 #include <stdlib.h> 21 #include <unistd.h> 22 #ifndef SHORT_STRINGS 23 #include <string.h> 24 #endif /* SHORT_STRINGS */ 25 #if __STDC__ 26 # include <stdarg.h> 27 #else 28 # include <varargs.h> 29 #endif 30 31 #include "csh.h" 32 #include "extern.h" 33 34 /* 35 * Tenex style file name recognition, .. and more. 36 * History: 37 * Author: Ken Greer, Sept. 1975, CMU. 38 * Finally got around to adding to the Cshell., Ken Greer, Dec. 1981. 39 */ 40 41 #define ON 1 42 #define OFF 0 43 #ifndef TRUE 44 #define TRUE 1 45 #endif 46 #ifndef FALSE 47 #define FALSE 0 48 #endif 49 50 #define ESC '\033' 51 52 typedef enum { 53 LIST, RECOGNIZE 54 } COMMAND; 55 56 static void setup_tty __P((int)); 57 static void back_to_col_1 __P((void)); 58 static void pushback __P((Char *)); 59 static void catn __P((Char *, Char *, int)); 60 static void copyn __P((Char *, Char *, int)); 61 static Char filetype __P((Char *, Char *)); 62 static void print_by_column __P((Char *, Char *[], int)); 63 static Char *tilde __P((Char *, Char *)); 64 static void retype __P((void)); 65 static void beep __P((void)); 66 static void print_recognized_stuff __P((Char *)); 67 static void extract_dir_and_name __P((Char *, Char *, Char *)); 68 static Char *getentry __P((DIR *, int)); 69 static void free_items __P((Char **)); 70 static int tsearch __P((Char *, COMMAND, int)); 71 static int recognize __P((Char *, Char *, int, int)); 72 static int is_prefix __P((Char *, Char *)); 73 static int is_suffix __P((Char *, Char *)); 74 static int ignored __P((Char *)); 75 76 /* 77 * Put this here so the binary can be patched with adb to enable file 78 * completion by default. Filec controls completion, nobeep controls 79 * ringing the terminal bell on incomplete expansions. 80 */ 81 bool filec = 0; 82 83 static void 84 setup_tty(on) 85 int on; 86 { 87 static struct termios tchars; 88 89 (void) tcgetattr(SHIN, &tchars); 90 91 if (on) { 92 tchars.c_cc[VEOL] = ESC; 93 if (tchars.c_lflag & ICANON) 94 on = TCSANOW; 95 else { 96 on = TCSAFLUSH; 97 tchars.c_lflag |= ICANON; 98 } 99 } 100 else { 101 tchars.c_cc[VEOL] = _POSIX_VDISABLE; 102 on = TCSANOW; 103 } 104 105 (void) tcsetattr(SHIN, TCSANOW, &tchars); 106 } 107 108 /* 109 * Move back to beginning of current line 110 */ 111 static void 112 back_to_col_1() 113 { 114 struct termios tty, tty_normal; 115 sigset_t sigset, osigset; 116 117 sigemptyset(&sigset); 118 sigaddset(&sigset, SIGINT); 119 sigprocmask(SIG_BLOCK, &sigset, &osigset); 120 (void) tcgetattr(SHOUT, &tty); 121 tty_normal = tty; 122 tty.c_iflag &= ~INLCR; 123 tty.c_oflag &= ~ONLCR; 124 (void) tcsetattr(SHOUT, TCSANOW, &tty); 125 (void) write(SHOUT, "\r", 1); 126 (void) tcsetattr(SHOUT, TCSANOW, &tty_normal); 127 sigprocmask(SIG_SETMASK, &osigset, NULL); 128 } 129 130 /* 131 * Push string contents back into tty queue 132 */ 133 static void 134 pushback(string) 135 Char *string; 136 { 137 register Char *p; 138 struct termios tty, tty_normal; 139 sigset_t sigset, osigset; 140 char c; 141 142 sigemptyset(&sigset); 143 sigaddset(&sigset, SIGINT); 144 sigprocmask(SIG_BLOCK, &sigset, &osigset); 145 (void) tcgetattr(SHOUT, &tty); 146 tty_normal = tty; 147 tty.c_lflag &= ~(ECHOKE | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOCTL); 148 (void) tcsetattr(SHOUT, TCSANOW, &tty); 149 150 for (p = string; (c = *p) != '\0'; p++) 151 (void) ioctl(SHOUT, TIOCSTI, (ioctl_t) & c); 152 (void) tcsetattr(SHOUT, TCSANOW, &tty_normal); 153 sigprocmask(SIG_SETMASK, &osigset, NULL); 154 } 155 156 /* 157 * Concatenate src onto tail of des. 158 * Des is a string whose maximum length is count. 159 * Always null terminate. 160 */ 161 static void 162 catn(des, src, count) 163 register Char *des, *src; 164 register int count; 165 { 166 while (--count >= 0 && *des) 167 des++; 168 while (--count >= 0) 169 if ((*des++ = *src++) == 0) 170 return; 171 *des = '\0'; 172 } 173 174 /* 175 * Like strncpy but always leave room for trailing \0 176 * and always null terminate. 177 */ 178 static void 179 copyn(des, src, count) 180 register Char *des, *src; 181 register int count; 182 { 183 while (--count >= 0) 184 if ((*des++ = *src++) == 0) 185 return; 186 *des = '\0'; 187 } 188 189 static Char 190 filetype(dir, file) 191 Char *dir, *file; 192 { 193 Char path[MAXPATHLEN]; 194 struct stat statb; 195 196 catn(Strcpy(path, dir), file, sizeof(path) / sizeof(Char)); 197 if (lstat(short2str(path), &statb) == 0) { 198 switch (statb.st_mode & S_IFMT) { 199 case S_IFDIR: 200 return ('/'); 201 202 case S_IFLNK: 203 if (stat(short2str(path), &statb) == 0 && /* follow it out */ 204 S_ISDIR(statb.st_mode)) 205 return ('>'); 206 else 207 return ('@'); 208 209 case S_IFSOCK: 210 return ('='); 211 212 default: 213 if (statb.st_mode & 0111) 214 return ('*'); 215 } 216 } 217 return (' '); 218 } 219 220 static struct winsize win; 221 222 /* 223 * Print sorted down columns 224 */ 225 static void 226 print_by_column(dir, items, count) 227 Char *dir, *items[]; 228 int count; 229 { 230 register int i, rows, r, c, maxwidth = 0, columns; 231 232 if (ioctl(SHOUT, TIOCGWINSZ, (ioctl_t) & win) < 0 || win.ws_col == 0) 233 win.ws_col = 80; 234 for (i = 0; i < count; i++) 235 maxwidth = maxwidth > (r = Strlen(items[i])) ? maxwidth : r; 236 maxwidth += 2; /* for the file tag and space */ 237 columns = win.ws_col / maxwidth; 238 if (columns == 0) 239 columns = 1; 240 rows = (count + (columns - 1)) / columns; 241 for (r = 0; r < rows; r++) { 242 for (c = 0; c < columns; c++) { 243 i = c * rows + r; 244 if (i < count) { 245 register int w; 246 247 (void) fprintf(cshout, "%s", vis_str(items[i])); 248 (void) fputc(dir ? filetype(dir, items[i]) : ' ', cshout); 249 if (c < columns - 1) { /* last column? */ 250 w = Strlen(items[i]) + 1; 251 for (; w < maxwidth; w++) 252 (void) fputc(' ', cshout); 253 } 254 } 255 } 256 (void) fputc('\r', cshout); 257 (void) fputc('\n', cshout); 258 } 259 } 260 261 /* 262 * Expand file name with possible tilde usage 263 * ~person/mumble 264 * expands to 265 * home_directory_of_person/mumble 266 */ 267 static Char * 268 tilde(new, old) 269 Char *new, *old; 270 { 271 register Char *o, *p; 272 register struct passwd *pw; 273 static Char person[40]; 274 275 if (old[0] != '~') 276 return (Strcpy(new, old)); 277 278 for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++) 279 continue; 280 *p = '\0'; 281 if (person[0] == '\0') 282 (void) Strcpy(new, value(STRhome)); 283 else { 284 pw = getpwnam(short2str(person)); 285 if (pw == NULL) 286 return (NULL); 287 (void) Strcpy(new, str2short(pw->pw_dir)); 288 } 289 (void) Strcat(new, o); 290 return (new); 291 } 292 293 /* 294 * Cause pending line to be printed 295 */ 296 static void 297 retype() 298 { 299 struct termios tty; 300 301 (void) tcgetattr(SHOUT, &tty); 302 tty.c_lflag |= PENDIN; 303 (void) tcsetattr(SHOUT, TCSANOW, &tty); 304 } 305 306 static void 307 beep() 308 { 309 if (adrof(STRnobeep) == 0) 310 (void) write(SHOUT, "\007", 1); 311 } 312 313 /* 314 * Erase that silly ^[ and 315 * print the recognized part of the string 316 */ 317 static void 318 print_recognized_stuff(recognized_part) 319 Char *recognized_part; 320 { 321 /* An optimized erasing of that silly ^[ */ 322 (void) fputc('\b', cshout); 323 (void) fputc('\b', cshout); 324 switch (Strlen(recognized_part)) { 325 326 case 0: /* erase two Characters: ^[ */ 327 (void) fputc(' ', cshout); 328 (void) fputc(' ', cshout); 329 (void) fputc('\b', cshout); 330 (void) fputc('\b', cshout); 331 break; 332 333 case 1: /* overstrike the ^, erase the [ */ 334 (void) fprintf(cshout, "%s", vis_str(recognized_part)); 335 (void) fputc(' ', cshout); 336 (void) fputc('\b', cshout); 337 break; 338 339 default: /* overstrike both Characters ^[ */ 340 (void) fprintf(cshout, "%s", vis_str(recognized_part)); 341 break; 342 } 343 (void) fflush(cshout); 344 } 345 346 /* 347 * Parse full path in file into 2 parts: directory and file names 348 * Should leave final slash (/) at end of dir. 349 */ 350 static void 351 extract_dir_and_name(path, dir, name) 352 Char *path, *dir, *name; 353 { 354 register Char *p; 355 356 p = Strrchr(path, '/'); 357 if (p == NULL) { 358 copyn(name, path, MAXNAMLEN); 359 dir[0] = '\0'; 360 } 361 else { 362 copyn(name, ++p, MAXNAMLEN); 363 copyn(dir, path, p - path); 364 } 365 } 366 367 static Char * 368 getentry(dir_fd, looking_for_lognames) 369 DIR *dir_fd; 370 int looking_for_lognames; 371 { 372 register struct passwd *pw; 373 register struct dirent *dirp; 374 375 if (looking_for_lognames) { 376 if ((pw = getpwent()) == NULL) 377 return (NULL); 378 return (str2short(pw->pw_name)); 379 } 380 if ((dirp = readdir(dir_fd)) != NULL) 381 return (str2short(dirp->d_name)); 382 return (NULL); 383 } 384 385 static void 386 free_items(items) 387 register Char **items; 388 { 389 register int i; 390 391 for (i = 0; items[i]; i++) 392 xfree((ptr_t) items[i]); 393 xfree((ptr_t) items); 394 } 395 396 #define FREE_ITEMS(items) { \ 397 sigset_t sigset, osigset;\ 398 \ 399 sigemptyset(&sigset);\ 400 sigaddset(&sigset, SIGINT);\ 401 sigprocmask(SIG_BLOCK, &sigset, &osigset);\ 402 free_items(items);\ 403 items = NULL;\ 404 sigprocmask(SIG_SETMASK, &osigset, NULL);\ 405 } 406 407 /* 408 * Perform a RECOGNIZE or LIST command on string "word". 409 */ 410 static int 411 tsearch(word, command, max_word_length) 412 Char *word; 413 COMMAND command; 414 int max_word_length; 415 { 416 static Char **items = NULL; 417 register DIR *dir_fd; 418 register numitems = 0, ignoring = TRUE, nignored = 0; 419 register name_length, looking_for_lognames; 420 Char tilded_dir[MAXPATHLEN + 1], dir[MAXPATHLEN + 1]; 421 Char name[MAXNAMLEN + 1], extended_name[MAXNAMLEN + 1]; 422 Char *entry; 423 424 #define MAXITEMS 1024 425 426 if (items != NULL) 427 FREE_ITEMS(items); 428 429 looking_for_lognames = (*word == '~') && (Strchr(word, '/') == NULL); 430 if (looking_for_lognames) { 431 (void) setpwent(); 432 copyn(name, &word[1], MAXNAMLEN); /* name sans ~ */ 433 dir_fd = NULL; 434 } 435 else { 436 extract_dir_and_name(word, dir, name); 437 if (tilde(tilded_dir, dir) == 0) 438 return (0); 439 dir_fd = opendir(*tilded_dir ? short2str(tilded_dir) : "."); 440 if (dir_fd == NULL) 441 return (0); 442 } 443 444 again: /* search for matches */ 445 name_length = Strlen(name); 446 for (numitems = 0; (entry = getentry(dir_fd, looking_for_lognames)) != NULL;) { 447 if (!is_prefix(name, entry)) 448 continue; 449 /* Don't match . files on null prefix match */ 450 if (name_length == 0 && entry[0] == '.' && 451 !looking_for_lognames) 452 continue; 453 if (command == LIST) { 454 if (numitems >= MAXITEMS) { 455 (void) fprintf(csherr, "\nYikes!! Too many %s!!\n", 456 looking_for_lognames ? 457 "names in password file" : "files"); 458 break; 459 } 460 if (items == NULL) 461 items = (Char **) xcalloc(sizeof(items[0]), MAXITEMS); 462 items[numitems] = (Char *) xmalloc((size_t) (Strlen(entry) + 1) * 463 sizeof(Char)); 464 copyn(items[numitems], entry, MAXNAMLEN); 465 numitems++; 466 } 467 else { /* RECOGNIZE command */ 468 if (ignoring && ignored(entry)) 469 nignored++; 470 else if (recognize(extended_name, 471 entry, name_length, ++numitems)) 472 break; 473 } 474 } 475 if (ignoring && numitems == 0 && nignored > 0) { 476 ignoring = FALSE; 477 nignored = 0; 478 if (looking_for_lognames) 479 (void) setpwent(); 480 else 481 rewinddir(dir_fd); 482 goto again; 483 } 484 485 if (looking_for_lognames) 486 (void) endpwent(); 487 else 488 (void) closedir(dir_fd); 489 if (numitems == 0) 490 return (0); 491 if (command == RECOGNIZE) { 492 if (looking_for_lognames) 493 copyn(word, STRtilde, 1); 494 else 495 /* put back dir part */ 496 copyn(word, dir, max_word_length); 497 /* add extended name */ 498 catn(word, extended_name, max_word_length); 499 return (numitems); 500 } 501 else { /* LIST */ 502 qsort((ptr_t) items, numitems, sizeof(items[0]), 503 (int (*) __P((const void *, const void *))) sortscmp); 504 print_by_column(looking_for_lognames ? NULL : tilded_dir, 505 items, numitems); 506 if (items != NULL) 507 FREE_ITEMS(items); 508 } 509 return (0); 510 } 511 512 /* 513 * Object: extend what user typed up to an ambiguity. 514 * Algorithm: 515 * On first match, copy full entry (assume it'll be the only match) 516 * On subsequent matches, shorten extended_name to the first 517 * Character mismatch between extended_name and entry. 518 * If we shorten it back to the prefix length, stop searching. 519 */ 520 static int 521 recognize(extended_name, entry, name_length, numitems) 522 Char *extended_name, *entry; 523 int name_length, numitems; 524 { 525 if (numitems == 1) /* 1st match */ 526 copyn(extended_name, entry, MAXNAMLEN); 527 else { /* 2nd & subsequent matches */ 528 register Char *x, *ent; 529 register int len = 0; 530 531 x = extended_name; 532 for (ent = entry; *x && *x == *ent++; x++, len++) 533 continue; 534 *x = '\0'; /* Shorten at 1st Char diff */ 535 if (len == name_length) /* Ambiguous to prefix? */ 536 return (-1); /* So stop now and save time */ 537 } 538 return (0); 539 } 540 541 /* 542 * Return true if check matches initial Chars in template. 543 * This differs from PWB imatch in that if check is null 544 * it matches anything. 545 */ 546 static int 547 is_prefix(check, template) 548 register Char *check, *template; 549 { 550 do 551 if (*check == 0) 552 return (TRUE); 553 while (*check++ == *template++); 554 return (FALSE); 555 } 556 557 /* 558 * Return true if the Chars in template appear at the 559 * end of check, I.e., are it's suffix. 560 */ 561 static int 562 is_suffix(check, template) 563 Char *check, *template; 564 { 565 register Char *c, *t; 566 567 for (c = check; *c++;) 568 continue; 569 for (t = template; *t++;) 570 continue; 571 for (;;) { 572 if (t == template) 573 return 1; 574 if (c == check || *--t != *--c) 575 return 0; 576 } 577 } 578 579 int 580 tenex(inputline, inputline_size) 581 Char *inputline; 582 int inputline_size; 583 { 584 register int numitems, num_read; 585 char tinputline[BUFSIZ]; 586 587 588 setup_tty(ON); 589 590 while ((num_read = read(SHIN, tinputline, BUFSIZ)) > 0) { 591 int i; 592 static Char delims[] = {' ', '\'', '"', '\t', ';', '&', '<', 593 '>', '(', ')', '|', '^', '%', '\0'}; 594 register Char *str_end, *word_start, last_Char, should_retype; 595 register int space_left; 596 COMMAND command; 597 598 for (i = 0; i < num_read; i++) 599 inputline[i] = (unsigned char) tinputline[i]; 600 last_Char = inputline[num_read - 1] & ASCII; 601 602 if (last_Char == '\n' || num_read == inputline_size) 603 break; 604 command = (last_Char == ESC) ? RECOGNIZE : LIST; 605 if (command == LIST) 606 (void) fputc('\n', cshout); 607 str_end = &inputline[num_read]; 608 if (last_Char == ESC) 609 --str_end; /* wipeout trailing cmd Char */ 610 *str_end = '\0'; 611 /* 612 * Find LAST occurence of a delimiter in the inputline. The word start 613 * is one Character past it. 614 */ 615 for (word_start = str_end; word_start > inputline; --word_start) 616 if (Strchr(delims, word_start[-1])) 617 break; 618 space_left = inputline_size - (word_start - inputline) - 1; 619 numitems = tsearch(word_start, command, space_left); 620 621 if (command == RECOGNIZE) { 622 /* print from str_end on */ 623 print_recognized_stuff(str_end); 624 if (numitems != 1) /* Beep = No match/ambiguous */ 625 beep(); 626 } 627 628 /* 629 * Tabs in the input line cause trouble after a pushback. tty driver 630 * won't backspace over them because column positions are now 631 * incorrect. This is solved by retyping over current line. 632 */ 633 should_retype = FALSE; 634 if (Strchr(inputline, '\t')) { /* tab Char in input line? */ 635 back_to_col_1(); 636 should_retype = TRUE; 637 } 638 if (command == LIST) /* Always retype after a LIST */ 639 should_retype = TRUE; 640 if (should_retype) 641 printprompt(); 642 pushback(inputline); 643 if (should_retype) 644 retype(); 645 } 646 setup_tty(OFF); 647 return (num_read); 648 } 649 650 static int 651 ignored(entry) 652 register Char *entry; 653 { 654 struct varent *vp; 655 register Char **cp; 656 657 if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL) 658 return (FALSE); 659 for (; *cp != NULL; cp++) 660 if (is_suffix(entry, *cp)) 661 return (TRUE); 662 return (FALSE); 663 } 664 #endif /* FILEC */ 665