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