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