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