1 /*- 2 * Copyright (c) 1980, 1991 The Regents of the University of California. 3 * All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 static char sccsid[] = "@(#)file.c 5.11 (Berkeley) 04/04/91"; 10 #endif /* not lint */ 11 12 #ifdef FILEC 13 /* 14 * Tenex style file name recognition, .. and more. 15 * History: 16 * Author: Ken Greer, Sept. 1975, CMU. 17 * Finally got around to adding to the Cshell., Ken Greer, Dec. 1981. 18 */ 19 20 #include "sh.h" 21 #include <sgtty.h> 22 #include <sys/dir.h> 23 #include <pwd.h> 24 25 #define TRUE 1 26 #define FALSE 0 27 #define ON 1 28 #define OFF 0 29 30 #define ESC '\033' 31 32 typedef enum {LIST, RECOGNIZE} COMMAND; 33 34 int sortscmp(); /* defined in sh.glob.c */ 35 36 /* 37 * Put this here so the binary can be patched with adb to enable file 38 * completion by default. Filec controls completion, nobeep controls 39 * ringing the terminal bell on incomplete expansions. 40 */ 41 bool filec = 0; 42 43 static 44 setup_tty(on) 45 int on; 46 { 47 struct sgttyb sgtty; 48 static struct tchars tchars; /* INT, QUIT, XON, XOFF, EOF, BRK */ 49 50 if (on) { 51 (void) ioctl(SHIN, TIOCGETC, (char *)&tchars); 52 tchars.t_brkc = ESC; 53 (void) ioctl(SHIN, TIOCSETC, (char *)&tchars); 54 /* 55 * This must be done after every command: if 56 * the tty gets into raw or cbreak mode the user 57 * can't even type 'reset'. 58 */ 59 (void) ioctl(SHIN, TIOCGETP, (char *)&sgtty); 60 if (sgtty.sg_flags & (RAW|CBREAK)) { 61 sgtty.sg_flags &= ~(RAW|CBREAK); 62 (void) ioctl(SHIN, TIOCSETP, (char *)&sgtty); 63 } 64 } else { 65 tchars.t_brkc = -1; 66 (void) ioctl(SHIN, TIOCSETC, (char *)&tchars); 67 } 68 } 69 70 /* 71 * Move back to beginning of current line 72 */ 73 static 74 back_to_col_1() 75 { 76 struct sgttyb tty, tty_normal; 77 long omask; 78 79 omask = sigblock(sigmask(SIGINT)); 80 (void) ioctl(SHIN, TIOCGETP, (char *)&tty); 81 tty_normal = tty; 82 tty.sg_flags &= ~CRMOD; 83 (void) ioctl(SHIN, TIOCSETN, (char *)&tty); 84 (void) write(SHOUT, "\r", 1); 85 (void) ioctl(SHIN, TIOCSETN, (char *)&tty_normal); 86 (void) sigsetmask(omask); 87 } 88 89 /* 90 * Push string contents back into tty queue 91 */ 92 static 93 pushback(string) 94 char *string; 95 { 96 register char *p; 97 struct sgttyb tty, tty_normal; 98 long omask; 99 100 omask = sigblock(sigmask(SIGINT)); 101 (void) ioctl(SHOUT, TIOCGETP, (char *)&tty); 102 tty_normal = tty; 103 tty.sg_flags &= ~ECHO; 104 (void) ioctl(SHOUT, TIOCSETN, (char *)&tty); 105 106 for (p = string; *p; p++) 107 (void) ioctl(SHOUT, TIOCSTI, p); 108 (void) ioctl(SHOUT, TIOCSETN, (char *)&tty_normal); 109 (void) sigsetmask(omask); 110 } 111 112 /* 113 * Concatenate src onto tail of des. 114 * Des is a string whose maximum length is count. 115 * Always null terminate. 116 */ 117 static 118 catn(des, src, count) 119 register char *des, *src; 120 register count; 121 { 122 123 while (--count >= 0 && *des) 124 des++; 125 while (--count >= 0) 126 if ((*des++ = *src++) == 0) 127 return; 128 *des = '\0'; 129 } 130 131 /* 132 * Like strncpy but always leave room for trailing \0 133 * and always null terminate. 134 */ 135 static 136 copyn(des, src, count) 137 register char *des, *src; 138 register count; 139 { 140 141 while (--count >= 0) 142 if ((*des++ = *src++) == 0) 143 return; 144 *des = '\0'; 145 } 146 147 static char 148 filetype(dir, file) 149 char *dir, *file; 150 { 151 char path[MAXPATHLEN]; 152 struct stat statb; 153 154 catn(strcpy(path, dir), file, sizeof path); 155 if (lstat(path, &statb) == 0) { 156 switch(statb.st_mode & S_IFMT) { 157 case S_IFDIR: 158 return ('/'); 159 160 case S_IFLNK: 161 if (stat(path, &statb) == 0 && /* follow it out */ 162 (statb.st_mode & S_IFMT) == S_IFDIR) 163 return ('>'); 164 else 165 return ('@'); 166 167 case S_IFSOCK: 168 return ('='); 169 170 default: 171 if (statb.st_mode & 0111) 172 return ('*'); 173 } 174 } 175 return (' '); 176 } 177 178 static struct winsize win; 179 180 /* 181 * Print sorted down columns 182 */ 183 static 184 print_by_column(dir, items, count) 185 char *dir, *items[]; 186 { 187 register int i, rows, r, c, maxwidth = 0, columns; 188 189 if (ioctl(SHOUT, TIOCGWINSZ, (char *)&win) < 0 || win.ws_col == 0) 190 win.ws_col = 80; 191 for (i = 0; i < count; i++) 192 maxwidth = maxwidth > (r = strlen(items[i])) ? maxwidth : r; 193 maxwidth += 2; /* for the file tag and space */ 194 columns = win.ws_col / maxwidth; 195 if (columns == 0) 196 columns = 1; 197 rows = (count + (columns - 1)) / columns; 198 for (r = 0; r < rows; r++) { 199 for (c = 0; c < columns; c++) { 200 i = c * rows + r; 201 if (i < count) { 202 register int w; 203 204 printf("%s", items[i]); 205 cshputchar(dir ? filetype(dir, items[i]) : ' '); 206 if (c < columns - 1) { /* last column? */ 207 w = strlen(items[i]) + 1; 208 for (; w < maxwidth; w++) 209 cshputchar(' '); 210 } 211 } 212 } 213 cshputchar('\n'); 214 } 215 } 216 217 /* 218 * Expand file name with possible tilde usage 219 * ~person/mumble 220 * expands to 221 * home_directory_of_person/mumble 222 */ 223 static char * 224 tilde(new, old) 225 char *new, *old; 226 { 227 register char *o, *p; 228 register struct passwd *pw; 229 static char person[40]; 230 231 if (old[0] != '~') 232 return (strcpy(new, old)); 233 234 for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++) 235 ; 236 *p = '\0'; 237 if (person[0] == '\0') 238 (void) strcpy(new, value("home")); 239 else { 240 pw = getpwnam(person); 241 if (pw == NULL) 242 return (NULL); 243 (void) strcpy(new, pw->pw_dir); 244 } 245 (void) strcat(new, o); 246 return (new); 247 } 248 249 /* 250 * Cause pending line to be printed 251 */ 252 static 253 retype() 254 { 255 int pending_input = LPENDIN; 256 257 (void) ioctl(SHOUT, TIOCLBIS, (char *)&pending_input); 258 } 259 260 static 261 beep() 262 { 263 264 if (adrof("nobeep") == 0) 265 (void) write(SHOUT, "\007", 1); 266 } 267 268 /* 269 * Erase that silly ^[ and 270 * print the recognized part of the string 271 */ 272 static 273 print_recognized_stuff(recognized_part) 274 char *recognized_part; 275 { 276 277 /* An optimized erasing of that silly ^[ */ 278 switch (strlen(recognized_part)) { 279 280 case 0: /* erase two characters: ^[ */ 281 printf("\210\210 \210\210"); 282 break; 283 284 case 1: /* overstrike the ^, erase the [ */ 285 printf("\210\210%s \210", recognized_part); 286 break; 287 288 default: /* overstrike both characters ^[ */ 289 printf("\210\210%s", recognized_part); 290 break; 291 } 292 flush(); 293 } 294 295 /* 296 * Parse full path in file into 2 parts: directory and file names 297 * Should leave final slash (/) at end of dir. 298 */ 299 static 300 extract_dir_and_name(path, dir, name) 301 char *path, *dir, *name; 302 { 303 register char *p; 304 305 p = rindex(path, '/'); 306 if (p == NULL) { 307 copyn(name, path, MAXNAMLEN); 308 dir[0] = '\0'; 309 } else { 310 copyn(name, ++p, MAXNAMLEN); 311 copyn(dir, path, p - path); 312 } 313 } 314 315 static char * 316 getentry(dir_fd, looking_for_lognames) 317 DIR *dir_fd; 318 { 319 register struct passwd *pw; 320 register struct direct *dirp; 321 322 if (looking_for_lognames) { 323 if ((pw = getpwent()) == NULL) 324 return (NULL); 325 return (pw->pw_name); 326 } 327 if (dirp = readdir(dir_fd)) 328 return (dirp->d_name); 329 return (NULL); 330 } 331 332 static 333 free_items(items) 334 register char **items; 335 { 336 register int i; 337 338 for (i = 0; items[i]; i++) 339 free(items[i]); 340 free((char *)items); 341 } 342 343 #define FREE_ITEMS(items) { \ 344 long omask;\ 345 \ 346 omask = sigblock(sigmask(SIGINT));\ 347 free_items(items);\ 348 items = NULL;\ 349 (void) sigsetmask(omask);\ 350 } 351 352 /* 353 * Perform a RECOGNIZE or LIST command on string "word". 354 */ 355 static 356 search(word, command, max_word_length) 357 char *word; 358 COMMAND command; 359 { 360 static char **items = NULL; 361 register DIR *dir_fd; 362 register numitems = 0, ignoring = TRUE, nignored = 0; 363 register name_length, looking_for_lognames; 364 char tilded_dir[MAXPATHLEN + 1], dir[MAXPATHLEN + 1]; 365 char name[MAXNAMLEN + 1], extended_name[MAXNAMLEN+1]; 366 char *entry; 367 static int ignored(), is_prefix(), recognize(); 368 #define MAXITEMS 1024 369 370 if (items != NULL) 371 FREE_ITEMS(items); 372 373 looking_for_lognames = (*word == '~') && (index(word, '/') == NULL); 374 if (looking_for_lognames) { 375 (void) setpwent(); 376 copyn(name, &word[1], MAXNAMLEN); /* name sans ~ */ 377 } else { 378 extract_dir_and_name(word, dir, name); 379 if (tilde(tilded_dir, dir) == 0) 380 return (0); 381 dir_fd = opendir(*tilded_dir ? tilded_dir : "."); 382 if (dir_fd == NULL) 383 return (0); 384 } 385 386 again: /* search for matches */ 387 name_length = strlen(name); 388 for (numitems = 0; entry = getentry(dir_fd, looking_for_lognames); ) { 389 if (!is_prefix(name, entry)) 390 continue; 391 /* Don't match . files on null prefix match */ 392 if (name_length == 0 && entry[0] == '.' && 393 !looking_for_lognames) 394 continue; 395 if (command == LIST) { 396 if (numitems >= MAXITEMS) { 397 printf ("\nYikes!! Too many %s!!\n", 398 looking_for_lognames ? 399 "names in password file":"files"); 400 break; 401 } 402 if (items == NULL) 403 items = (char **) calloc(sizeof (items[1]), 404 MAXITEMS); 405 items[numitems] = xalloc((unsigned)strlen(entry) + 1); 406 copyn(items[numitems], entry, MAXNAMLEN); 407 numitems++; 408 } else { /* RECOGNIZE command */ 409 if (ignoring && ignored(entry)) 410 nignored++; 411 else if (recognize(extended_name, 412 entry, name_length, ++numitems)) 413 break; 414 } 415 } 416 if (ignoring && numitems == 0 && nignored > 0) { 417 ignoring = FALSE; 418 nignored = 0; 419 if (looking_for_lognames) 420 (void) setpwent(); 421 else 422 rewinddir(dir_fd); 423 goto again; 424 } 425 426 if (looking_for_lognames) 427 (void) endpwent(); 428 else 429 closedir(dir_fd); 430 if (numitems == 0) 431 return (0); 432 if (command == RECOGNIZE) { 433 if (looking_for_lognames) 434 copyn(word, "~", 1); 435 else 436 /* put back dir part */ 437 copyn(word, dir, max_word_length); 438 /* add extended name */ 439 catn(word, extended_name, max_word_length); 440 return (numitems); 441 } 442 else { /* LIST */ 443 qsort((char *)items, numitems, sizeof(items[1]), sortscmp); 444 print_by_column(looking_for_lognames ? NULL : tilded_dir, 445 items, numitems); 446 if (items != NULL) 447 FREE_ITEMS(items); 448 } 449 return (0); 450 } 451 452 /* 453 * Object: extend what user typed up to an ambiguity. 454 * Algorithm: 455 * On first match, copy full entry (assume it'll be the only match) 456 * On subsequent matches, shorten extended_name to the first 457 * character mismatch between extended_name and entry. 458 * If we shorten it back to the prefix length, stop searching. 459 */ 460 static 461 recognize(extended_name, entry, name_length, numitems) 462 char *extended_name, *entry; 463 { 464 465 if (numitems == 1) /* 1st match */ 466 copyn(extended_name, entry, MAXNAMLEN); 467 else { /* 2nd & subsequent matches */ 468 register char *x, *ent; 469 register int len = 0; 470 471 x = extended_name; 472 for (ent = entry; *x && *x == *ent++; x++, len++) 473 ; 474 *x = '\0'; /* Shorten at 1st char diff */ 475 if (len == name_length) /* Ambiguous to prefix? */ 476 return (-1); /* So stop now and save time */ 477 } 478 return (0); 479 } 480 481 /* 482 * Return true if check matches initial chars in template. 483 * This differs from PWB imatch in that if check is null 484 * it matches anything. 485 */ 486 static 487 is_prefix(check, template) 488 register char *check, *template; 489 { 490 491 do 492 if (*check == 0) 493 return (TRUE); 494 while (*check++ == *template++); 495 return (FALSE); 496 } 497 498 /* 499 * Return true if the chars in template appear at the 500 * end of check, I.e., are it's suffix. 501 */ 502 static 503 is_suffix(check, template) 504 char *check, *template; 505 { 506 register char *c, *t; 507 508 for (c = check; *c++;) 509 ; 510 for (t = template; *t++;) 511 ; 512 for (;;) { 513 if (t == template) 514 return 1; 515 if (c == check || *--t != *--c) 516 return 0; 517 } 518 } 519 520 tenex(inputline, inputline_size) 521 char *inputline; 522 int inputline_size; 523 { 524 register int numitems, num_read; 525 526 setup_tty(ON); 527 while ((num_read = read(SHIN, inputline, inputline_size)) > 0) { 528 static char *delims = " '\"\t;&<>()|^%"; 529 register char *str_end, *word_start, last_char, should_retype; 530 register int space_left; 531 COMMAND command; 532 533 last_char = inputline[num_read - 1] & 0177; 534 535 if (last_char == '\n' || num_read == inputline_size) 536 break; 537 command = (last_char == ESC) ? RECOGNIZE : LIST; 538 if (command == LIST) 539 cshputchar('\n'); 540 str_end = &inputline[num_read]; 541 if (last_char == ESC) 542 --str_end; /* wipeout trailing cmd char */ 543 *str_end = '\0'; 544 /* 545 * Find LAST occurence of a delimiter in the inputline. 546 * The word start is one character past it. 547 */ 548 for (word_start = str_end; word_start > inputline; --word_start) 549 if (index(delims, word_start[-1])) 550 break; 551 space_left = inputline_size - (word_start - inputline) - 1; 552 numitems = search(word_start, command, space_left); 553 554 if (command == RECOGNIZE) { 555 /* print from str_end on */ 556 print_recognized_stuff(str_end); 557 if (numitems != 1) /* Beep = No match/ambiguous */ 558 beep(); 559 } 560 561 /* 562 * Tabs in the input line cause trouble after a pushback. 563 * tty driver won't backspace over them because column 564 * positions are now incorrect. This is solved by retyping 565 * over current line. 566 */ 567 should_retype = FALSE; 568 if (index(inputline, '\t')) { /* tab char in input line? */ 569 back_to_col_1(); 570 should_retype = TRUE; 571 } 572 if (command == LIST) /* Always retype after a LIST */ 573 should_retype = TRUE; 574 if (should_retype) 575 printprompt(); 576 pushback(inputline); 577 if (should_retype) 578 retype(); 579 } 580 setup_tty(OFF); 581 return (num_read); 582 } 583 584 static 585 ignored(entry) 586 register char *entry; 587 { 588 struct varent *vp; 589 register char **cp; 590 591 if ((vp = adrof("fignore")) == NULL || (cp = vp->vec) == NULL) 592 return (FALSE); 593 for (; *cp != NULL; cp++) 594 if (is_suffix(entry, *cp)) 595 return (TRUE); 596 return (FALSE); 597 } 598 #endif FILEC 599