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