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