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