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