1 /* $NetBSD: file.c,v 1.22 2003/02/08 19:05:19 christos 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.22 2003/02/08 19:05:19 christos 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 **, size_t); 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 nsigset, osigset; 147 148 sigemptyset(&nsigset); 149 (void)sigaddset(&nsigset, SIGINT); 150 (void)sigprocmask(SIG_BLOCK, &nsigset, &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 nsigset, osigset; 170 Char *p; 171 int bufidx, i, len_str, nbuf, nsv, onsv, retrycnt; 172 char c; 173 174 nsv = 0; 175 sigemptyset(&nsigset); 176 (void)sigaddset(&nsigset, SIGINT); 177 (void)sigprocmask(SIG_BLOCK, &nsigset, &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, size_t numitems) 462 { 463 size_t i; 464 465 for (i = 0; i < numitems; i++) 466 xfree((ptr_t) items[i]); 467 xfree((ptr_t) items); 468 } 469 470 #define FREE_ITEMS(items, numitems) { \ 471 sigset_t nsigset, osigset;\ 472 \ 473 sigemptyset(&nsigset);\ 474 (void) sigaddset(&nsigset, SIGINT);\ 475 (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset);\ 476 free_items(items, numitems);\ 477 (void) sigprocmask(SIG_SETMASK, &osigset, NULL);\ 478 } 479 480 /* 481 * Perform a RECOGNIZE or LIST command on string "word". 482 */ 483 static int 484 tsearch(Char *word, COMMAND command, int max_word_length) 485 { 486 Char dir[MAXPATHLEN + 1], extended_name[MAXNAMLEN + 1]; 487 Char name[MAXNAMLEN + 1], tilded_dir[MAXPATHLEN + 1]; 488 DIR *dir_fd; 489 Char *entry; 490 int ignoring, looking_for_lognames, name_length, nignored, numitems; 491 Char **items = NULL; 492 size_t maxitems = 0; 493 494 numitems = 0; 495 ignoring = TRUE; 496 nignored = 0; 497 498 looking_for_lognames = (*word == '~') && (Strchr(word, '/') == NULL); 499 if (looking_for_lognames) { 500 (void)setpwent(); 501 copyn(name, &word[1], MAXNAMLEN); /* name sans ~ */ 502 dir_fd = NULL; 503 } 504 else { 505 extract_dir_and_name(word, dir, name); 506 if (tilde(tilded_dir, dir) == 0) 507 return (0); 508 dir_fd = opendir(*tilded_dir ? short2str(tilded_dir) : "."); 509 if (dir_fd == NULL) 510 return (0); 511 } 512 513 again: /* search for matches */ 514 name_length = Strlen(name); 515 for (numitems = 0; (entry = getentry(dir_fd, looking_for_lognames)) != NULL;) { 516 if (!is_prefix(name, entry)) 517 continue; 518 /* Don't match . files on null prefix match */ 519 if (name_length == 0 && entry[0] == '.' && 520 !looking_for_lognames) 521 continue; 522 if (command == LIST) { 523 if (numitems >= maxitems) { 524 maxitems += 1024; 525 if (items == NULL) 526 items = (Char **) xmalloc(sizeof(*items) * maxitems); 527 else 528 items = (Char **) xrealloc((ptr_t) items, 529 sizeof(*items) * maxitems); 530 } 531 items[numitems] = (Char *)xmalloc((size_t) (Strlen(entry) + 1) * 532 sizeof(Char)); 533 copyn(items[numitems], entry, MAXNAMLEN); 534 numitems++; 535 } 536 else { /* RECOGNIZE command */ 537 if (ignoring && ignored(entry)) 538 nignored++; 539 else if (recognize(extended_name, 540 entry, name_length, ++numitems)) 541 break; 542 } 543 } 544 if (ignoring && numitems == 0 && nignored > 0) { 545 ignoring = FALSE; 546 nignored = 0; 547 if (looking_for_lognames) 548 (void)setpwent(); 549 else 550 rewinddir(dir_fd); 551 goto again; 552 } 553 554 if (looking_for_lognames) 555 (void)endpwent(); 556 else 557 (void)closedir(dir_fd); 558 if (numitems == 0) 559 return (0); 560 if (command == RECOGNIZE) { 561 if (looking_for_lognames) 562 copyn(word, STRtilde, 1); 563 else 564 /* put back dir part */ 565 copyn(word, dir, max_word_length); 566 /* add extended name */ 567 catn(word, extended_name, max_word_length); 568 return (numitems); 569 } 570 else { /* LIST */ 571 qsort((ptr_t) items, numitems, sizeof(items[0]), 572 (int (*) (const void *, const void *)) sortscmp); 573 print_by_column(looking_for_lognames ? NULL : tilded_dir, 574 items, numitems); 575 if (items != NULL) 576 FREE_ITEMS(items, numitems); 577 } 578 return (0); 579 } 580 581 /* 582 * Object: extend what user typed up to an ambiguity. 583 * Algorithm: 584 * On first match, copy full entry (assume it'll be the only match) 585 * On subsequent matches, shorten extended_name to the first 586 * Character mismatch between extended_name and entry. 587 * If we shorten it back to the prefix length, stop searching. 588 */ 589 static int 590 recognize(Char *extended_name, Char *entry, int name_length, int numitems) 591 { 592 if (numitems == 1) /* 1st match */ 593 copyn(extended_name, entry, MAXNAMLEN); 594 else { /* 2nd & subsequent matches */ 595 Char *ent, *x; 596 int len = 0; 597 598 x = extended_name; 599 for (ent = entry; *x && *x == *ent++; x++, len++) 600 continue; 601 *x = '\0'; /* Shorten at 1st Char diff */ 602 if (len == name_length) /* Ambiguous to prefix? */ 603 return (-1); /* So stop now and save time */ 604 } 605 return (0); 606 } 607 608 /* 609 * Return true if check matches initial Chars in template. 610 * This differs from PWB imatch in that if check is null 611 * it matches anything. 612 */ 613 static int 614 is_prefix(Char *check, Char *template) 615 { 616 do 617 if (*check == 0) 618 return (TRUE); 619 while (*check++ == *template++); 620 return (FALSE); 621 } 622 623 /* 624 * Return true if the Chars in template appear at the 625 * end of check, I.e., are it's suffix. 626 */ 627 static int 628 is_suffix(Char *check, Char *template) 629 { 630 Char *c, *t; 631 632 for (c = check; *c++;) 633 continue; 634 for (t = template; *t++;) 635 continue; 636 for (;;) { 637 if (t == template) 638 return 1; 639 if (c == check || *--t != *--c) 640 return 0; 641 } 642 } 643 644 int 645 tenex(Char *inputline, int inputline_size) 646 { 647 char tinputline[BUFSIZE]; 648 int num_read, numitems; 649 650 setup_tty(ON); 651 652 while ((num_read = read(SHIN, tinputline, BUFSIZE)) > 0) { 653 int i; 654 655 static Char delims[] = {' ', '\'', '"', '\t', ';', '&', '<', 656 '>', '(', ')', '|', '^', '%', '\0'}; 657 Char *str_end, *word_start, last_Char, should_retype; 658 int space_left; 659 COMMAND command; 660 661 for (i = 0; i < num_read; i++) 662 inputline[i] = (unsigned char) tinputline[i]; 663 last_Char = inputline[num_read - 1] & ASCII; 664 665 if (last_Char == '\n' || num_read == inputline_size) 666 break; 667 command = (last_Char == ESC) ? RECOGNIZE : LIST; 668 if (command == LIST) 669 (void)fputc('\n', cshout); 670 str_end = &inputline[num_read]; 671 if (last_Char == ESC) 672 --str_end; /* wipeout trailing cmd Char */ 673 *str_end = '\0'; 674 /* 675 * Find LAST occurence of a delimiter in the inputline. The word start 676 * is one Character past it. 677 */ 678 for (word_start = str_end; word_start > inputline; --word_start) 679 if (Strchr(delims, word_start[-1])) 680 break; 681 space_left = inputline_size - (word_start - inputline) - 1; 682 numitems = tsearch(word_start, command, space_left); 683 684 if (command == RECOGNIZE) { 685 /* print from str_end on */ 686 print_recognized_stuff(str_end); 687 if (numitems != 1) /* Beep = No match/ambiguous */ 688 beep(); 689 } 690 691 /* 692 * Tabs in the input line cause trouble after a pushback. tty driver 693 * won't backspace over them because column positions are now 694 * incorrect. This is solved by retyping over current line. 695 */ 696 should_retype = FALSE; 697 if (Strchr(inputline, '\t')) { /* tab Char in input line? */ 698 back_to_col_1(); 699 should_retype = TRUE; 700 } 701 if (command == LIST) /* Always retype after a LIST */ 702 should_retype = TRUE; 703 if (pushback(inputline)) 704 should_retype = TRUE; 705 if (should_retype) { 706 if (command == RECOGNIZE) 707 (void) fputc('\n', cshout); 708 printprompt(); 709 } 710 if (should_retype) 711 retype(); 712 } 713 setup_tty(OFF); 714 return (num_read); 715 } 716 717 static int 718 ignored(Char *entry) 719 { 720 struct varent *vp; 721 Char **cp; 722 723 if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL) 724 return (FALSE); 725 for (; *cp != NULL; cp++) 726 if (is_suffix(entry, *cp)) 727 return (TRUE); 728 return (FALSE); 729 } 730 #endif /* FILEC */ 731