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