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