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