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