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