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