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