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