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