1/* $NetBSD: file.c,v 1.28 2009/02/14 07:12:29 lukem 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 35static char sccsid[] = "@(#)file.c 8.2 (Berkeley) 3/19/94"; 36#else 37__RCSID("$NetBSD: file.c,v 1.28 2009/02/14 07:12:29 lukem 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 80typedef enum { 81 LIST, RECOGNIZE 82} COMMAND; 83 84static void setup_tty(int); 85static void back_to_col_1(void); 86static int pushback(Char *); 87static void catn(Char *, Char *, int); 88static void copyn(Char *, Char *, int); 89static Char filetype(Char *, Char *); 90static void print_by_column(Char *, Char *[], int); 91static Char *tilde(Char *, Char *); 92static void retype(void); 93static void beep(void); 94static void print_recognized_stuff(Char *); 95static void extract_dir_and_name(Char *, Char *, Char *); 96static Char *getentry(DIR *, int); 97static void free_items(Char **, size_t); 98static int tsearch(Char *, COMMAND, int); 99static int recognize(Char *, Char *, int, int); 100static int is_prefix(Char *, Char *); 101static int is_suffix(Char *, Char *); 102static 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 */ 109int filec = 0; 110 111static void 112setup_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 */ 138static void 139back_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 */ 160static int 161pushback(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 = *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 = 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 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 */ 248static void 249catn(Char *des, Char *src, int 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 */ 263static void 264copyn(Char *des, Char *src, int count) 265{ 266 while (--count >= 0) 267 if ((*des++ = *src++) == 0) 268 return; 269 *des = '\0'; 270} 271 272static Char 273filetype(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 299static struct winsize win; 300 301/* 302 * Print sorted down columns 303 */ 304static void 305print_by_column(Char *dir, Char *items[], int count) 306{ 307 int 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 int 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 */ 346static Char * 347tilde(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 */ 374static void 375retype(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 384static void 385beep(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 */ 395static void 396print_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 */ 424static void 425extract_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, p - path); 437 } 438} 439 440static Char * 441getentry(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 456static void 457free_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 */ 479static int 480tsearch(Char *word, COMMAND command, int 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, name_length, nignored, numitems; 487 Char **items = NULL; 488 size_t maxitems = 0; 489 490 numitems = 0; 491 ignoring = TRUE; 492 nignored = 0; 493 494 looking_for_lognames = (*word == '~') && (Strchr(word, '/') == NULL); 495 if (looking_for_lognames) { 496 (void)setpwent(); 497 copyn(name, &word[1], MAXNAMLEN); /* name sans ~ */ 498 dir_fd = NULL; 499 } 500 else { 501 extract_dir_and_name(word, dir, name); 502 if (tilde(tilded_dir, dir) == 0) 503 return (0); 504 dir_fd = opendir(*tilded_dir ? short2str(tilded_dir) : "."); 505 if (dir_fd == NULL) 506 return (0); 507 } 508 509again: /* search for matches */ 510 name_length = Strlen(name); 511 for (numitems = 0; (entry = getentry(dir_fd, looking_for_lognames)) != NULL;) { 512 if (!is_prefix(name, entry)) 513 continue; 514 /* Don't match . files on null prefix match */ 515 if (name_length == 0 && entry[0] == '.' && 516 !looking_for_lognames) 517 continue; 518 if (command == LIST) { 519 if ((size_t)numitems >= maxitems) { 520 maxitems += 1024; 521 if (items == NULL) 522 items = (Char **) xmalloc(sizeof(*items) * maxitems); 523 else 524 items = (Char **) xrealloc((ptr_t) items, 525 sizeof(*items) * maxitems); 526 } 527 items[numitems] = (Char *)xmalloc((size_t) (Strlen(entry) + 1) * 528 sizeof(Char)); 529 copyn(items[numitems], entry, MAXNAMLEN); 530 numitems++; 531 } 532 else { /* RECOGNIZE command */ 533 if (ignoring && ignored(entry)) 534 nignored++; 535 else if (recognize(extended_name, 536 entry, name_length, ++numitems)) 537 break; 538 } 539 } 540 if (ignoring && numitems == 0 && nignored > 0) { 541 ignoring = FALSE; 542 nignored = 0; 543 if (looking_for_lognames) 544 (void)setpwent(); 545 else 546 rewinddir(dir_fd); 547 goto again; 548 } 549 550 if (looking_for_lognames) 551 (void)endpwent(); 552 else 553 (void)closedir(dir_fd); 554 if (numitems == 0) 555 return (0); 556 if (command == RECOGNIZE) { 557 if (looking_for_lognames) 558 copyn(word, STRtilde, 1); 559 else 560 /* put back dir part */ 561 copyn(word, dir, max_word_length); 562 /* add extended name */ 563 catn(word, extended_name, max_word_length); 564 return (numitems); 565 } 566 else { /* LIST */ 567 qsort((ptr_t) items, numitems, sizeof(items[0]), 568 (int (*) (const void *, const void *)) sortscmp); 569 print_by_column(looking_for_lognames ? NULL : tilded_dir, 570 items, numitems); 571 if (items != NULL) 572 FREE_ITEMS(items, numitems); 573 } 574 return (0); 575} 576 577/* 578 * Object: extend what user typed up to an ambiguity. 579 * Algorithm: 580 * On first match, copy full entry (assume it'll be the only match) 581 * On subsequent matches, shorten extended_name to the first 582 * Character mismatch between extended_name and entry. 583 * If we shorten it back to the prefix length, stop searching. 584 */ 585static int 586recognize(Char *extended_name, Char *entry, int name_length, int numitems) 587{ 588 if (numitems == 1) /* 1st match */ 589 copyn(extended_name, entry, MAXNAMLEN); 590 else { /* 2nd & subsequent matches */ 591 Char *ent, *x; 592 int len = 0; 593 594 x = extended_name; 595 for (ent = entry; *x && *x == *ent++; x++, len++) 596 continue; 597 *x = '\0'; /* Shorten at 1st Char diff */ 598 if (len == name_length) /* Ambiguous to prefix? */ 599 return (-1); /* So stop now and save time */ 600 } 601 return (0); 602} 603 604/* 605 * Return true if check matches initial Chars in template. 606 * This differs from PWB imatch in that if check is null 607 * it matches anything. 608 */ 609static int 610is_prefix(Char *check, Char *template) 611{ 612 do 613 if (*check == 0) 614 return (TRUE); 615 while (*check++ == *template++); 616 return (FALSE); 617} 618 619/* 620 * Return true if the Chars in template appear at the 621 * end of check, I.e., are its suffix. 622 */ 623static int 624is_suffix(Char *check, Char *template) 625{ 626 Char *c, *t; 627 628 for (c = check; *c++;) 629 continue; 630 for (t = template; *t++;) 631 continue; 632 for (;;) { 633 if (t == template) 634 return 1; 635 if (c == check || *--t != *--c) 636 return 0; 637 } 638} 639 640int 641tenex(Char *inputline, int inputline_size) 642{ 643 char tinputline[BUFSIZE]; 644 int num_read, numitems; 645 646 setup_tty(ON); 647 648 while ((num_read = read(SHIN, tinputline, BUFSIZE)) > 0) { 649 int i; 650 651 static Char delims[] = {' ', '\'', '"', '\t', ';', '&', '<', 652 '>', '(', ')', '|', '^', '%', '\0'}; 653 Char *str_end, *word_start, last_Char, should_retype; 654 int space_left; 655 COMMAND command; 656 657 for (i = 0; i < num_read; i++) 658 inputline[i] = (unsigned char) tinputline[i]; 659 last_Char = inputline[num_read - 1] & ASCII; 660 661 if (last_Char == '\n' || num_read == inputline_size) 662 break; 663 command = (last_Char == ESC) ? RECOGNIZE : LIST; 664 if (command == LIST) 665 (void)fputc('\n', cshout); 666 str_end = &inputline[num_read]; 667 if (last_Char == ESC) 668 --str_end; /* wipeout trailing cmd Char */ 669 *str_end = '\0'; 670 /* 671 * Find LAST occurence of a delimiter in the inputline. The word start 672 * is one Character past it. 673 */ 674 for (word_start = str_end; word_start > inputline; --word_start) 675 if (Strchr(delims, word_start[-1])) 676 break; 677 space_left = inputline_size - (word_start - inputline) - 1; 678 numitems = tsearch(word_start, command, space_left); 679 680 if (command == RECOGNIZE) { 681 /* print from str_end on */ 682 print_recognized_stuff(str_end); 683 if (numitems != 1) /* Beep = No match/ambiguous */ 684 beep(); 685 } 686 687 /* 688 * Tabs in the input line cause trouble after a pushback. tty driver 689 * won't backspace over them because column positions are now 690 * incorrect. This is solved by retyping over current line. 691 */ 692 should_retype = FALSE; 693 if (Strchr(inputline, '\t')) { /* tab Char in input line? */ 694 back_to_col_1(); 695 should_retype = TRUE; 696 } 697 if (command == LIST) /* Always retype after a LIST */ 698 should_retype = TRUE; 699 if (pushback(inputline)) 700 should_retype = TRUE; 701 if (should_retype) { 702 if (command == RECOGNIZE) 703 (void) fputc('\n', cshout); 704 printprompt(); 705 } 706 if (should_retype) 707 retype(); 708 } 709 setup_tty(OFF); 710 return (num_read); 711} 712 713static int 714ignored(Char *entry) 715{ 716 struct varent *vp; 717 Char **cp; 718 719 if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL) 720 return (FALSE); 721 for (; *cp != NULL; cp++) 722 if (is_suffix(entry, *cp)) 723 return (TRUE); 724 return (FALSE); 725} 726#endif /* FILEC */ 727