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