1 /* $NetBSD: file.c,v 1.30 2013/07/16 17:47:43 christos Exp $ */
4 * Copyright (c) 1980, 1991, 1993
5 * The Regents of the University of California. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 #include <sys/cdefs.h>
35 static char sccsid
[] = "@(#)file.c 8.2 (Berkeley) 3/19/94";
37 __RCSID("$NetBSD: file.c,v 1.30 2013/07/16 17:47:43 christos Exp $");
43 #include <sys/ioctl.h>
44 #include <sys/param.h>
57 #endif /* SHORT_STRINGS */
63 * Tenex style file name recognition, .. and more.
65 * Author: Ken Greer, Sept. 1975, CMU.
66 * Finally got around to adding to the Cshell., Ken Greer, Dec. 1981.
84 static void setup_tty(int);
85 static void back_to_col_1(void);
86 static int pushback(Char
*);
87 static void catn(Char
*, Char
*, size_t);
88 static void copyn(Char
*, Char
*, size_t);
89 static Char
filetype(Char
*, Char
*);
90 static void print_by_column(Char
*, Char
*[], size_t);
91 static Char
*tilde(Char
*, Char
*);
92 static void retype(void);
93 static void beep(void);
94 static void print_recognized_stuff(Char
*);
95 static void extract_dir_and_name(Char
*, Char
*, Char
*);
96 static Char
*getentry(DIR *, int);
97 static void free_items(Char
**, size_t);
98 static size_t tsearch(Char
*, COMMAND
, size_t);
99 static int recognize(Char
*, Char
*, size_t, size_t);
100 static int is_prefix(Char
*, Char
*);
101 static int is_suffix(Char
*, Char
*);
102 static int ignored(Char
*);
105 * Put this here so the binary can be patched with adb to enable file
106 * completion by default. Filec controls completion, nobeep controls
107 * ringing the terminal bell on incomplete expansions.
114 struct termios tchars
;
116 (void)tcgetattr(SHIN
, &tchars
);
119 tchars
.c_cc
[VEOL
] = ESC
;
120 if (tchars
.c_lflag
& ICANON
)
123 tchars
.c_lflag
|= ICANON
;
128 tchars
.c_cc
[VEOL
] = _POSIX_VDISABLE
;
132 (void)tcsetattr(SHIN
, on
, &tchars
);
136 * Move back to beginning of current line
141 struct termios tty
, tty_normal
;
142 sigset_t nsigset
, osigset
;
144 sigemptyset(&nsigset
);
145 (void)sigaddset(&nsigset
, SIGINT
);
146 (void)sigprocmask(SIG_BLOCK
, &nsigset
, &osigset
);
147 (void)tcgetattr(SHOUT
, &tty
);
149 tty
.c_iflag
&= ~INLCR
;
150 tty
.c_oflag
&= ~ONLCR
;
151 (void)tcsetattr(SHOUT
, TCSADRAIN
, &tty
);
152 (void)write(SHOUT
, "\r", 1);
153 (void)tcsetattr(SHOUT
, TCSADRAIN
, &tty_normal
);
154 (void)sigprocmask(SIG_SETMASK
, &osigset
, NULL
);
158 * Push string contents back into tty queue
161 pushback(Char
*string
)
163 struct termios tty
, tty_normal
;
164 char buf
[64], svchars
[sizeof(buf
)];
165 sigset_t nsigset
, osigset
;
167 size_t bufidx
, i
, len_str
, nbuf
, nsv
, onsv
, retrycnt
;
171 sigemptyset(&nsigset
);
172 (void)sigaddset(&nsigset
, SIGINT
);
173 (void)sigprocmask(SIG_BLOCK
, &nsigset
, &osigset
);
174 (void)tcgetattr(SHOUT
, &tty
);
176 tty
.c_lflag
&= ~(ECHOKE
| ECHO
| ECHOE
| ECHOK
| ECHONL
| ECHOPRT
| ECHOCTL
);
177 /* FIONREAD works only in noncanonical mode. */
178 tty
.c_lflag
&= ~ICANON
;
180 (void)tcsetattr(SHOUT
, TCSADRAIN
, &tty
);
182 for (retrycnt
= 5; ; retrycnt
--) {
184 * Push back characters.
186 for (p
= string
; (c
= (char)*p
) != '\0'; p
++)
187 (void)ioctl(SHOUT
, TIOCSTI
, (ioctl_t
) &c
);
188 for (i
= 0; i
< nsv
; i
++)
189 (void)ioctl(SHOUT
, TIOCSTI
, (ioctl_t
) &svchars
[i
]);
192 break; /* give up salvaging characters */
194 len_str
= (size_t)(p
- string
);
196 if (ioctl(SHOUT
, FIONREAD
, (ioctl_t
) &nbuf
) ||
197 nbuf
<= len_str
+ nsv
|| /* The string fit. */
198 nbuf
> sizeof(buf
)) /* For future binary compatibility
203 * User has typed characters before the pushback finished.
204 * Salvage the characters.
207 /* This read() should be in noncanonical mode. */
208 if (read(SHOUT
, &buf
, nbuf
) != (ssize_t
)nbuf
)
209 continue; /* hangup? */
212 for (bufidx
= 0, i
= 0; bufidx
< nbuf
; bufidx
++, i
++) {
214 if ((i
< len_str
) ? c
!= (char)string
[i
] :
215 (i
< len_str
+ onsv
) ? c
!= svchars
[i
- len_str
] : 1) {
216 /* Salvage a character. */
217 if (nsv
< (int)(sizeof svchars
/ sizeof svchars
[0])) {
219 i
--; /* try this comparison with the next char */
221 break; /* too many */
228 * XXX Is this a bug or a feature of kernel tty driver?
230 * FIONREAD in canonical mode does not return correct byte count
231 * in tty input queue, but this is required to avoid unwanted echo.
233 tty
.c_lflag
|= ICANON
;
234 (void)tcsetattr(SHOUT
, TCSADRAIN
, &tty
);
235 (void)ioctl(SHOUT
, FIONREAD
, (ioctl_t
) &i
);
237 (void)tcsetattr(SHOUT
, TCSADRAIN
, &tty_normal
);
238 (void)sigprocmask(SIG_SETMASK
, &osigset
, NULL
);
244 * Concatenate src onto tail of des.
245 * Des is a string whose maximum length is count.
246 * Always null terminate.
249 catn(Char
*des
, Char
*src
, size_t count
)
251 while (count
-- > 0 && *des
)
254 if ((*des
++ = *src
++) == 0)
260 * Like strncpy but always leave room for trailing \0
261 * and always null terminate.
264 copyn(Char
*des
, Char
*src
, size_t count
)
267 if ((*des
++ = *src
++) == 0)
273 filetype(Char
*dir
, Char
*file
)
276 Char path
[MAXPATHLEN
];
278 catn(Strcpy(path
, dir
), file
, sizeof(path
) / sizeof(Char
));
279 if (lstat(short2str(path
), &statb
) == 0) {
280 switch (statb
.st_mode
& S_IFMT
) {
284 if (stat(short2str(path
), &statb
) == 0 && /* follow it out */
285 S_ISDIR(statb
.st_mode
))
292 if (statb
.st_mode
& 0111)
299 static struct winsize win
;
302 * Print sorted down columns
305 print_by_column(Char
*dir
, Char
*items
[], size_t count
)
307 size_t c
, columns
, i
, maxwidth
, r
, rows
;
311 if (ioctl(SHOUT
, TIOCGWINSZ
, (ioctl_t
) & win
) < 0 || win
.ws_col
== 0)
313 for (i
= 0; i
< count
; i
++)
314 maxwidth
= maxwidth
> (r
= Strlen(items
[i
])) ? maxwidth
: r
;
315 maxwidth
+= 2; /* for the file tag and space */
316 columns
= win
.ws_col
/ maxwidth
;
319 rows
= (count
+ (columns
- 1)) / columns
;
320 for (r
= 0; r
< rows
; r
++) {
321 for (c
= 0; c
< columns
; c
++) {
326 (void)fprintf(cshout
, "%s", vis_str(items
[i
]));
327 (void)fputc(dir
? filetype(dir
, items
[i
]) : ' ', cshout
);
328 if (c
< columns
- 1) { /* last column? */
329 w
= Strlen(items
[i
]) + 1;
330 for (; w
< maxwidth
; w
++)
331 (void) fputc(' ', cshout
);
335 (void)fputc('\r', cshout
);
336 (void)fputc('\n', cshout
);
341 * Expand file name with possible tilde usage
344 * home_directory_of_person/mumble
347 tilde(Char
*new, Char
*old
)
349 static Char person
[40];
354 return (Strcpy(new, old
));
356 for (p
= person
, o
= &old
[1]; *o
&& *o
!= '/'; *p
++ = *o
++)
359 if (person
[0] == '\0')
360 (void)Strcpy(new, value(STRhome
));
362 pw
= getpwnam(short2str(person
));
365 (void)Strcpy(new, str2short(pw
->pw_dir
));
367 (void)Strcat(new, o
);
372 * Cause pending line to be printed
379 (void)tcgetattr(SHOUT
, &tty
);
380 tty
.c_lflag
|= PENDIN
;
381 (void)tcsetattr(SHOUT
, TCSADRAIN
, &tty
);
387 if (adrof(STRnobeep
) == 0)
388 (void)write(SHOUT
, "\007", 1);
392 * Erase that silly ^[ and
393 * print the recognized part of the string
396 print_recognized_stuff(Char
*recognized_part
)
398 /* An optimized erasing of that silly ^[ */
399 (void)fputc('\b', cshout
);
400 (void)fputc('\b', cshout
);
401 switch (Strlen(recognized_part
)) {
402 case 0: /* erase two Characters: ^[ */
403 (void)fputc(' ', cshout
);
404 (void)fputc(' ', cshout
);
405 (void)fputc('\b', cshout
);
406 (void)fputc('\b', cshout
);
408 case 1: /* overstrike the ^, erase the [ */
409 (void)fprintf(cshout
, "%s", vis_str(recognized_part
));
410 (void)fputc(' ', cshout
);
411 (void)fputc('\b', cshout
);
413 default: /* overstrike both Characters ^[ */
414 (void)fprintf(cshout
, "%s", vis_str(recognized_part
));
417 (void)fflush(cshout
);
421 * Parse full path in file into 2 parts: directory and file names
422 * Should leave final slash (/) at end of dir.
425 extract_dir_and_name(Char
*path
, Char
*dir
, Char
*name
)
429 p
= Strrchr(path
, '/');
431 copyn(name
, path
, MAXNAMLEN
);
435 copyn(name
, ++p
, MAXNAMLEN
);
436 copyn(dir
, path
, (size_t)(p
- path
));
441 getentry(DIR *dir_fd
, int looking_for_lognames
)
446 if (looking_for_lognames
) {
447 if ((pw
= getpwent()) == NULL
)
449 return (str2short(pw
->pw_name
));
451 if ((dirp
= readdir(dir_fd
)) != NULL
)
452 return (str2short(dirp
->d_name
));
457 free_items(Char
**items
, size_t numitems
)
461 for (i
= 0; i
< numitems
; i
++)
462 xfree((ptr_t
) items
[i
]);
463 xfree((ptr_t
) items
);
466 #define FREE_ITEMS(items, numitems) { \
467 sigset_t nsigset, osigset;\
469 sigemptyset(&nsigset);\
470 (void) sigaddset(&nsigset, SIGINT);\
471 (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset);\
472 free_items(items, numitems);\
473 (void) sigprocmask(SIG_SETMASK, &osigset, NULL);\
477 * Perform a RECOGNIZE or LIST command on string "word".
480 tsearch(Char
*word
, COMMAND command
, size_t max_word_length
)
482 Char dir
[MAXPATHLEN
+ 1], extended_name
[MAXNAMLEN
+ 1];
483 Char name
[MAXNAMLEN
+ 1], tilded_dir
[MAXPATHLEN
+ 1];
486 int ignoring
, looking_for_lognames
;
487 size_t name_length
, nignored
, numitems
;
495 looking_for_lognames
= (*word
== '~') && (Strchr(word
, '/') == NULL
);
496 if (looking_for_lognames
) {
498 copyn(name
, &word
[1], MAXNAMLEN
); /* name sans ~ */
502 extract_dir_and_name(word
, dir
, name
);
503 if (tilde(tilded_dir
, dir
) == 0)
505 dir_fd
= opendir(*tilded_dir
? short2str(tilded_dir
) : ".");
510 again
: /* search for matches */
511 name_length
= Strlen(name
);
512 for (numitems
= 0; (entry
= getentry(dir_fd
, looking_for_lognames
)) != NULL
;) {
513 if (!is_prefix(name
, entry
))
515 /* Don't match . files on null prefix match */
516 if (name_length
== 0 && entry
[0] == '.' &&
517 !looking_for_lognames
)
519 if (command
== LIST
) {
520 if ((size_t)numitems
>= maxitems
) {
523 items
= xmalloc(sizeof(*items
) * maxitems
);
525 items
= xrealloc((ptr_t
) items
,
526 sizeof(*items
) * maxitems
);
528 items
[numitems
] = xmalloc((size_t) (Strlen(entry
) + 1) *
530 copyn(items
[numitems
], entry
, MAXNAMLEN
);
533 else { /* RECOGNIZE command */
534 if (ignoring
&& ignored(entry
))
536 else if (recognize(extended_name
,
537 entry
, name_length
, ++numitems
))
541 if (ignoring
&& numitems
== 0 && nignored
> 0) {
544 if (looking_for_lognames
)
551 if (looking_for_lognames
)
554 (void)closedir(dir_fd
);
557 if (command
== RECOGNIZE
) {
558 if (looking_for_lognames
)
559 copyn(word
, STRtilde
, 1);
561 /* put back dir part */
562 copyn(word
, dir
, max_word_length
);
563 /* add extended name */
564 catn(word
, extended_name
, max_word_length
);
568 qsort((ptr_t
) items
, numitems
, sizeof(items
[0]),
569 (int (*) (const void *, const void *)) sortscmp
);
570 print_by_column(looking_for_lognames
? NULL
: tilded_dir
,
573 FREE_ITEMS(items
, numitems
);
579 * Object: extend what user typed up to an ambiguity.
581 * On first match, copy full entry (assume it'll be the only match)
582 * On subsequent matches, shorten extended_name to the first
583 * Character mismatch between extended_name and entry.
584 * If we shorten it back to the prefix length, stop searching.
587 recognize(Char
*extended_name
, Char
*entry
, size_t name_length
, size_t numitems
)
589 if (numitems
== 1) /* 1st match */
590 copyn(extended_name
, entry
, MAXNAMLEN
);
591 else { /* 2nd & subsequent matches */
596 for (ent
= entry
; *x
&& *x
== *ent
++; x
++, len
++)
598 *x
= '\0'; /* Shorten at 1st Char diff */
599 if (len
== name_length
) /* Ambiguous to prefix? */
600 return (-1); /* So stop now and save time */
606 * Return true if check matches initial Chars in template.
607 * This differs from PWB imatch in that if check is null
608 * it matches anything.
611 is_prefix(Char
*check
, Char
*template)
616 while (*check
++ == *template++);
621 * Return true if the Chars in template appear at the
622 * end of check, I.e., are its suffix.
625 is_suffix(Char
*check
, Char
*template)
629 for (c
= check
; *c
++;)
631 for (t
= template; *t
++;)
636 if (c
== check
|| *--t
!= *--c
)
642 tenex(Char
*inputline
, size_t inputline_size
)
644 char tinputline
[BUFSIZE
];
650 while ((num_read
= read(SHIN
, tinputline
, BUFSIZE
)) > 0) {
651 size_t i
, nr
= (size_t) num_read
;
654 static Char delims
[] = {' ', '\'', '"', '\t', ';', '&', '<',
655 '>', '(', ')', '|', '^', '%', '\0'};
656 Char
*str_end
, *word_start
, last_Char
, should_retype
;
660 for (i
= 0; i
< nr
; i
++)
661 inputline
[i
] = (unsigned char) tinputline
[i
];
662 last_Char
= inputline
[nr
- 1] & ASCII
;
664 if (last_Char
== '\n' || nr
== inputline_size
)
666 command
= (last_Char
== ESC
) ? RECOGNIZE
: LIST
;
668 (void)fputc('\n', cshout
);
669 str_end
= &inputline
[nr
];
670 if (last_Char
== ESC
)
671 --str_end
; /* wipeout trailing cmd Char */
674 * Find LAST occurence of a delimiter in the inputline. The word start
675 * is one Character past it.
677 for (word_start
= str_end
; word_start
> inputline
; --word_start
)
678 if (Strchr(delims
, word_start
[-1]))
680 space_left
= inputline_size
- (size_t)(word_start
- inputline
) - 1;
681 numitems
= tsearch(word_start
, command
, space_left
);
683 if (command
== RECOGNIZE
) {
684 /* print from str_end on */
685 print_recognized_stuff(str_end
);
686 if (numitems
!= 1) /* Beep = No match/ambiguous */
691 * Tabs in the input line cause trouble after a pushback. tty driver
692 * won't backspace over them because column positions are now
693 * incorrect. This is solved by retyping over current line.
695 should_retype
= FALSE
;
696 if (Strchr(inputline
, '\t')) { /* tab Char in input line? */
698 should_retype
= TRUE
;
700 if (command
== LIST
) /* Always retype after a LIST */
701 should_retype
= TRUE
;
702 if (pushback(inputline
))
703 should_retype
= TRUE
;
705 if (command
== RECOGNIZE
)
706 (void) fputc('\n', cshout
);
722 if ((vp
= adrof(STRfignore
)) == NULL
|| (cp
= vp
->vec
) == NULL
)
724 for (; *cp
!= NULL
; cp
++)
725 if (is_suffix(entry
, *cp
))