1/* $NetBSD: file.c,v 1.34 2024/04/24 15:49:03 nia 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.34 2024/04/24 15:49:03 nia 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 *, size_t); 88static void copyn(Char *, Char *, size_t); 89static Char filetype(Char *, Char *); 90static void print_by_column(Char *, Char *[], size_t); 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 size_t tsearch(Char *, COMMAND, size_t); 99static int recognize(Char *, Char *, size_t, size_t); 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 = (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 */ 248static void 249catn(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 */ 263static void 264copyn(Char *des, Char *src, size_t 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[], 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 */ 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, (size_t)(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 free(items[i]); 463 free(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 size_t 480tsearch(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 510again: /* 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 items = xreallocarray(items, sizeof(*items), maxitems); 523 } 524 items[numitems] = xreallocarray(NULL, 525 (size_t) (Strlen(entry) + 1), sizeof(Char)); 526 copyn(items[numitems], entry, MAXNAMLEN); 527 numitems++; 528 } 529 else { /* RECOGNIZE command */ 530 if (ignoring && ignored(entry)) 531 nignored++; 532 else if (recognize(extended_name, 533 entry, name_length, ++numitems)) 534 break; 535 } 536 } 537 if (ignoring && numitems == 0 && nignored > 0) { 538 ignoring = FALSE; 539 nignored = 0; 540 if (looking_for_lognames) 541 (void)setpwent(); 542 else 543 rewinddir(dir_fd); 544 goto again; 545 } 546 547 if (looking_for_lognames) 548 (void)endpwent(); 549 else 550 (void)closedir(dir_fd); 551 if (numitems == 0) 552 return (0); 553 if (command == RECOGNIZE) { 554 if (looking_for_lognames) 555 copyn(word, STRtilde, 1); 556 else 557 /* put back dir part */ 558 copyn(word, dir, max_word_length); 559 /* add extended name */ 560 catn(word, extended_name, max_word_length); 561 return (numitems); 562 } 563 else { /* LIST */ 564 qsort(items, numitems, sizeof(items[0]), 565 (int (*) (const void *, const void *)) sortscmp); 566 print_by_column(looking_for_lognames ? NULL : tilded_dir, 567 items, numitems); 568 if (items != NULL) 569 FREE_ITEMS(items, numitems); 570 } 571 return (0); 572} 573 574/* 575 * Object: extend what user typed up to an ambiguity. 576 * Algorithm: 577 * On first match, copy full entry (assume it'll be the only match) 578 * On subsequent matches, shorten extended_name to the first 579 * Character mismatch between extended_name and entry. 580 * If we shorten it back to the prefix length, stop searching. 581 */ 582static int 583recognize(Char *extended_name, Char *entry, size_t name_length, size_t numitems) 584{ 585 if (numitems == 1) /* 1st match */ 586 copyn(extended_name, entry, MAXNAMLEN); 587 else { /* 2nd & subsequent matches */ 588 Char *ent, *x; 589 size_t len = 0; 590 591 x = extended_name; 592 for (ent = entry; *x && *x == *ent++; x++, len++) 593 continue; 594 *x = '\0'; /* Shorten at 1st Char diff */ 595 if (len == name_length) /* Ambiguous to prefix? */ 596 return (-1); /* So stop now and save time */ 597 } 598 return (0); 599} 600 601/* 602 * Return true if check matches initial Chars in template. 603 * This differs from PWB imatch in that if check is null 604 * it matches anything. 605 */ 606static int 607is_prefix(Char *check, Char *template) 608{ 609 do 610 if (*check == 0) 611 return (TRUE); 612 while (*check++ == *template++); 613 return (FALSE); 614} 615 616/* 617 * Return true if the Chars in template appear at the 618 * end of check, I.e., are its suffix. 619 */ 620static int 621is_suffix(Char *check, Char *template) 622{ 623 Char *c, *t; 624 625 for (c = check; *c++;) 626 continue; 627 for (t = template; *t++;) 628 continue; 629 for (;;) { 630 if (t == template) 631 return 1; 632 if (c == check || *--t != *--c) 633 return 0; 634 } 635} 636 637ssize_t 638tenex(Char *inputline, size_t inputline_size) 639{ 640 char tinputline[BUFSIZE]; 641 ssize_t num_read; 642 size_t numitems; 643 644 setup_tty(ON); 645 646 while ((num_read = read(SHIN, tinputline, BUFSIZE)) > 0) { 647 size_t i, nr = (size_t) num_read; 648 649 650 static Char delims[] = {' ', '\'', '"', '\t', ';', '&', '<', 651 '>', '(', ')', '|', '^', '%', '\0'}; 652 Char *str_end, *word_start, last_Char, should_retype; 653 size_t space_left; 654 COMMAND command; 655 656 for (i = 0; i < nr; i++) 657 inputline[i] = (unsigned char) tinputline[i]; 658 last_Char = inputline[nr - 1] & ASCII; 659 660 if (last_Char == '\n' || nr == inputline_size) 661 break; 662 command = (last_Char == ESC) ? RECOGNIZE : LIST; 663 if (command == LIST) 664 (void)fputc('\n', cshout); 665 str_end = &inputline[nr]; 666 if (last_Char == ESC) 667 --str_end; /* wipeout trailing cmd Char */ 668 *str_end = '\0'; 669 /* 670 * Find LAST occurrence of a delimiter in the inputline. The word start 671 * is one Character past it. 672 */ 673 for (word_start = str_end; word_start > inputline; --word_start) 674 if (Strchr(delims, word_start[-1])) 675 break; 676 space_left = inputline_size - (size_t)(word_start - inputline) - 1; 677 numitems = tsearch(word_start, command, space_left); 678 679 if (command == RECOGNIZE) { 680 /* print from str_end on */ 681 print_recognized_stuff(str_end); 682 if (numitems != 1) /* Beep = No match/ambiguous */ 683 beep(); 684 } 685 686 /* 687 * Tabs in the input line cause trouble after a pushback. tty driver 688 * won't backspace over them because column positions are now 689 * incorrect. This is solved by retyping over current line. 690 */ 691 should_retype = FALSE; 692 if (Strchr(inputline, '\t')) { /* tab Char in input line? */ 693 back_to_col_1(); 694 should_retype = TRUE; 695 } 696 if (command == LIST) /* Always retype after a LIST */ 697 should_retype = TRUE; 698 if (pushback(inputline)) 699 should_retype = TRUE; 700 if (should_retype) { 701 if (command == RECOGNIZE) 702 (void) fputc('\n', cshout); 703 printprompt(); 704 } 705 if (should_retype) 706 retype(); 707 } 708 setup_tty(OFF); 709 return num_read; 710} 711 712static int 713ignored(Char *entry) 714{ 715 struct varent *vp; 716 Char **cp; 717 718 if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL) 719 return (FALSE); 720 for (; *cp != NULL; cp++) 721 if (is_suffix(entry, *cp)) 722 return (TRUE); 723 return (FALSE); 724} 725#endif /* FILEC */ 726