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: #if !defined(lint) && defined(DOSCCS) 8: static char *sccsid = "@(#)sh.file.c 5.6 (Berkeley) 5/18/86"; 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: putchar(dir ? filetype(dir, items[i]) : ' '); 205: if (c < columns - 1) { /* last column? */ 206: w = strlen(items[i]) + 1; 207: for (; w < maxwidth; w++) 208: putchar(' '); 209: } 210: } 211: } 212: putchar('\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: putchar('\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