|
|
1.1 root 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 = "@(#)sh.file.c 5.8 (Berkeley) 5/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
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.