1 | /* $NetBSD: file.c,v 1.32 2019/01/05 16:54:00 christos 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. 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. |
18 | * |
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 |
29 | * SUCH DAMAGE. |
30 | */ |
31 | |
32 | #include <sys/cdefs.h> |
33 | #ifndef lint |
34 | #if 0 |
35 | static char sccsid[] = "@(#)file.c 8.2 (Berkeley) 3/19/94" ; |
36 | #else |
37 | __RCSID("$NetBSD: file.c,v 1.32 2019/01/05 16:54:00 christos Exp $" ); |
38 | #endif |
39 | #endif /* not lint */ |
40 | |
41 | #ifdef FILEC |
42 | |
43 | #include <sys/ioctl.h> |
44 | #include <sys/param.h> |
45 | #include <sys/stat.h> |
46 | #include <sys/tty.h> |
47 | |
48 | #include <dirent.h> |
49 | #include <pwd.h> |
50 | #include <termios.h> |
51 | #include <stdarg.h> |
52 | #include <stdlib.h> |
53 | #include <unistd.h> |
54 | |
55 | #ifndef SHORT_STRINGS |
56 | #include <string.h> |
57 | #endif /* SHORT_STRINGS */ |
58 | |
59 | #include "csh.h" |
60 | #include "extern.h" |
61 | |
62 | /* |
63 | * Tenex style file name recognition, .. and more. |
64 | * History: |
65 | * Author: Ken Greer, Sept. 1975, CMU. |
66 | * Finally got around to adding to the Cshell., Ken Greer, Dec. 1981. |
67 | */ |
68 | |
69 | #define ON 1 |
70 | #define OFF 0 |
71 | #ifndef TRUE |
72 | #define TRUE 1 |
73 | #endif |
74 | #ifndef FALSE |
75 | #define FALSE 0 |
76 | #endif |
77 | |
78 | #define ESC '\033' |
79 | |
80 | typedef enum { |
81 | LIST, RECOGNIZE |
82 | } COMMAND; |
83 | |
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 *); |
103 | |
104 | /* |
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. |
108 | */ |
109 | int filec = 0; |
110 | |
111 | static void |
112 | setup_tty(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(void) |
140 | { |
141 | struct termios tty, tty_normal; |
142 | sigset_t nsigset, osigset; |
143 | |
144 | sigemptyset(&nsigset); |
145 | (void)sigaddset(&nsigset, SIGINT); |
146 | (void)sigprocmask(SIG_BLOCK, &nsigset, &osigset); |
147 | (void)tcgetattr(SHOUT, &tty); |
148 | tty_normal = 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); |
155 | } |
156 | |
157 | /* |
158 | * Push string contents back into tty queue |
159 | */ |
160 | static int |
161 | pushback(Char *string) |
162 | { |
163 | struct termios tty, tty_normal; |
164 | char buf[64], svchars[sizeof(buf)]; |
165 | sigset_t nsigset, osigset; |
166 | Char *p; |
167 | size_t bufidx, i, len_str, nbuf, nsv, onsv, retrycnt; |
168 | char c; |
169 | |
170 | nsv = 0; |
171 | sigemptyset(&nsigset); |
172 | (void)sigaddset(&nsigset, SIGINT); |
173 | (void)sigprocmask(SIG_BLOCK, &nsigset, &osigset); |
174 | (void)tcgetattr(SHOUT, &tty); |
175 | tty_normal = tty; |
176 | tty.c_lflag &= ~(ECHOKE | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOCTL); |
177 | /* FIONREAD works only in noncanonical mode. */ |
178 | tty.c_lflag &= ~ICANON; |
179 | tty.c_cc[VMIN] = 0; |
180 | (void)tcsetattr(SHOUT, TCSADRAIN, &tty); |
181 | |
182 | for (retrycnt = 5; ; retrycnt--) { |
183 | /* |
184 | * Push back characters. |
185 | */ |
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]); |
190 | |
191 | if (retrycnt == 0) |
192 | break; /* give up salvaging characters */ |
193 | |
194 | len_str = (size_t)(p - string); |
195 | |
196 | if (ioctl(SHOUT, FIONREAD, (ioctl_t) &nbuf) || |
197 | nbuf <= len_str + nsv || /* The string fit. */ |
198 | nbuf > sizeof(buf)) /* For future binary compatibility |
199 | (and safety). */ |
200 | break; |
201 | |
202 | /* |
203 | * User has typed characters before the pushback finished. |
204 | * Salvage the characters. |
205 | */ |
206 | |
207 | /* This read() should be in noncanonical mode. */ |
208 | if (read(SHOUT, &buf, nbuf) != (ssize_t)nbuf) |
209 | continue; /* hangup? */ |
210 | |
211 | onsv = nsv; |
212 | for (bufidx = 0, i = 0; bufidx < nbuf; bufidx++, i++) { |
213 | c = buf[bufidx]; |
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])) { |
218 | svchars[nsv++] = c; |
219 | i--; /* try this comparison with the next char */ |
220 | } else |
221 | break; /* too many */ |
222 | } |
223 | } |
224 | } |
225 | |
226 | #if 1 |
227 | /* |
228 | * XXX Is this a bug or a feature of kernel tty driver? |
229 | * |
230 | * FIONREAD in canonical mode does not return correct byte count |
231 | * in tty input queue, but this is required to avoid unwanted echo. |
232 | */ |
233 | tty.c_lflag |= ICANON; |
234 | (void)tcsetattr(SHOUT, TCSADRAIN, &tty); |
235 | (void)ioctl(SHOUT, FIONREAD, (ioctl_t) &i); |
236 | #endif |
237 | (void)tcsetattr(SHOUT, TCSADRAIN, &tty_normal); |
238 | (void)sigprocmask(SIG_SETMASK, &osigset, NULL); |
239 | |
240 | return (int)nsv; |
241 | } |
242 | |
243 | /* |
244 | * Concatenate src onto tail of des. |
245 | * Des is a string whose maximum length is count. |
246 | * Always null terminate. |
247 | */ |
248 | static void |
249 | catn(Char *des, Char *src, size_t count) |
250 | { |
251 | while (count-- > 0 && *des) |
252 | des++; |
253 | while (count-- > 0) |
254 | if ((*des++ = *src++) == 0) |
255 | return; |
256 | *des = '\0'; |
257 | } |
258 | |
259 | /* |
260 | * Like strncpy but always leave room for trailing \0 |
261 | * and always null terminate. |
262 | */ |
263 | static void |
264 | copyn(Char *des, Char *src, size_t count) |
265 | { |
266 | while (count-- > 0) |
267 | if ((*des++ = *src++) == 0) |
268 | return; |
269 | *des = '\0'; |
270 | } |
271 | |
272 | static Char |
273 | filetype(Char *dir, Char *file) |
274 | { |
275 | struct stat statb; |
276 | Char path[MAXPATHLEN]; |
277 | |
278 | catn(Strcpy(path, dir), file, sizeof(path) / sizeof(Char)); |
279 | if (lstat(short2str(path), &statb) == 0) { |
280 | switch (statb.st_mode & S_IFMT) { |
281 | case S_IFDIR: |
282 | return ('/'); |
283 | case S_IFLNK: |
284 | if (stat(short2str(path), &statb) == 0 && /* follow it out */ |
285 | S_ISDIR(statb.st_mode)) |
286 | return ('>'); |
287 | else |
288 | return ('@'); |
289 | case S_IFSOCK: |
290 | return ('='); |
291 | default: |
292 | if (statb.st_mode & 0111) |
293 | return ('*'); |
294 | } |
295 | } |
296 | return (' '); |
297 | } |
298 | |
299 | static struct winsize win; |
300 | |
301 | /* |
302 | * Print sorted down columns |
303 | */ |
304 | static void |
305 | print_by_column(Char *dir, Char *items[], size_t count) |
306 | { |
307 | size_t c, columns, i, maxwidth, r, rows; |
308 | |
309 | maxwidth = 0; |
310 | |
311 | if (ioctl(SHOUT, TIOCGWINSZ, (ioctl_t) & win) < 0 || win.ws_col == 0) |
312 | win.ws_col = 80; |
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; |
317 | if (columns == 0) |
318 | columns = 1; |
319 | rows = (count + (columns - 1)) / columns; |
320 | for (r = 0; r < rows; r++) { |
321 | for (c = 0; c < columns; c++) { |
322 | i = c * rows + r; |
323 | if (i < count) { |
324 | size_t w; |
325 | |
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); |
332 | } |
333 | } |
334 | } |
335 | (void)fputc('\r', cshout); |
336 | (void)fputc('\n', cshout); |
337 | } |
338 | } |
339 | |
340 | /* |
341 | * Expand file name with possible tilde usage |
342 | * ~person/mumble |
343 | * expands to |
344 | * home_directory_of_person/mumble |
345 | */ |
346 | static Char * |
347 | tilde(Char *new, Char *old) |
348 | { |
349 | static Char person[40]; |
350 | struct passwd *pw; |
351 | Char *o, *p; |
352 | |
353 | if (old[0] != '~') |
354 | return (Strcpy(new, old)); |
355 | |
356 | for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++) |
357 | continue; |
358 | *p = '\0'; |
359 | if (person[0] == '\0') |
360 | (void)Strcpy(new, value(STRhome)); |
361 | else { |
362 | pw = getpwnam(short2str(person)); |
363 | if (pw == NULL) |
364 | return (NULL); |
365 | (void)Strcpy(new, str2short(pw->pw_dir)); |
366 | } |
367 | (void)Strcat(new, o); |
368 | return (new); |
369 | } |
370 | |
371 | /* |
372 | * Cause pending line to be printed |
373 | */ |
374 | static void |
375 | retype(void) |
376 | { |
377 | struct termios tty; |
378 | |
379 | (void)tcgetattr(SHOUT, &tty); |
380 | tty.c_lflag |= PENDIN; |
381 | (void)tcsetattr(SHOUT, TCSADRAIN, &tty); |
382 | } |
383 | |
384 | static void |
385 | beep(void) |
386 | { |
387 | if (adrof(STRnobeep) == 0) |
388 | (void)write(SHOUT, "\007" , 1); |
389 | } |
390 | |
391 | /* |
392 | * Erase that silly ^[ and |
393 | * print the recognized part of the string |
394 | */ |
395 | static void |
396 | print_recognized_stuff(Char *recognized_part) |
397 | { |
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); |
407 | break; |
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); |
412 | break; |
413 | default: /* overstrike both Characters ^[ */ |
414 | (void)fprintf(cshout, "%s" , vis_str(recognized_part)); |
415 | break; |
416 | } |
417 | (void)fflush(cshout); |
418 | } |
419 | |
420 | /* |
421 | * Parse full path in file into 2 parts: directory and file names |
422 | * Should leave final slash (/) at end of dir. |
423 | */ |
424 | static void |
425 | extract_dir_and_name(Char *path, Char *dir, Char *name) |
426 | { |
427 | Char *p; |
428 | |
429 | p = Strrchr(path, '/'); |
430 | if (p == NULL) { |
431 | copyn(name, path, MAXNAMLEN); |
432 | dir[0] = '\0'; |
433 | } |
434 | else { |
435 | copyn(name, ++p, MAXNAMLEN); |
436 | copyn(dir, path, (size_t)(p - path)); |
437 | } |
438 | } |
439 | |
440 | static Char * |
441 | getentry(DIR *dir_fd, int looking_for_lognames) |
442 | { |
443 | struct dirent *dirp; |
444 | struct passwd *pw; |
445 | |
446 | if (looking_for_lognames) { |
447 | if ((pw = getpwent()) == NULL) |
448 | return (NULL); |
449 | return (str2short(pw->pw_name)); |
450 | } |
451 | if ((dirp = readdir(dir_fd)) != NULL) |
452 | return (str2short(dirp->d_name)); |
453 | return (NULL); |
454 | } |
455 | |
456 | static void |
457 | free_items(Char **items, size_t numitems) |
458 | { |
459 | size_t i; |
460 | |
461 | for (i = 0; i < numitems; i++) |
462 | free(items[i]); |
463 | free(items); |
464 | } |
465 | |
466 | #define FREE_ITEMS(items, numitems) { \ |
467 | sigset_t nsigset, osigset;\ |
468 | \ |
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);\ |
474 | } |
475 | |
476 | /* |
477 | * Perform a RECOGNIZE or LIST command on string "word". |
478 | */ |
479 | static size_t |
480 | tsearch(Char *word, COMMAND command, size_t max_word_length) |
481 | { |
482 | Char dir[MAXPATHLEN + 1], extended_name[MAXNAMLEN + 1]; |
483 | Char name[MAXNAMLEN + 1], tilded_dir[MAXPATHLEN + 1]; |
484 | DIR *dir_fd; |
485 | Char *entry; |
486 | int ignoring, looking_for_lognames; |
487 | size_t name_length, nignored, numitems; |
488 | Char **items = NULL; |
489 | size_t maxitems = 0; |
490 | |
491 | numitems = 0; |
492 | ignoring = TRUE; |
493 | nignored = 0; |
494 | |
495 | looking_for_lognames = (*word == '~') && (Strchr(word, '/') == NULL); |
496 | if (looking_for_lognames) { |
497 | (void)setpwent(); |
498 | copyn(name, &word[1], MAXNAMLEN); /* name sans ~ */ |
499 | dir_fd = NULL; |
500 | } |
501 | else { |
502 | extract_dir_and_name(word, dir, name); |
503 | if (tilde(tilded_dir, dir) == 0) |
504 | return (0); |
505 | dir_fd = opendir(*tilded_dir ? short2str(tilded_dir) : "." ); |
506 | if (dir_fd == NULL) |
507 | return (0); |
508 | } |
509 | |
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)) |
514 | continue; |
515 | /* Don't match . files on null prefix match */ |
516 | if (name_length == 0 && entry[0] == '.' && |
517 | !looking_for_lognames) |
518 | continue; |
519 | if (command == LIST) { |
520 | if ((size_t)numitems >= maxitems) { |
521 | maxitems += 1024; |
522 | if (items == NULL) |
523 | items = xmalloc(sizeof(*items) * maxitems); |
524 | else |
525 | items = xrealloc(items, sizeof(*items) * maxitems); |
526 | } |
527 | items[numitems] = xmalloc((size_t) (Strlen(entry) + 1) * |
528 | sizeof(Char)); |
529 | copyn(items[numitems], entry, MAXNAMLEN); |
530 | numitems++; |
531 | } |
532 | else { /* RECOGNIZE command */ |
533 | if (ignoring && ignored(entry)) |
534 | nignored++; |
535 | else if (recognize(extended_name, |
536 | entry, name_length, ++numitems)) |
537 | break; |
538 | } |
539 | } |
540 | if (ignoring && numitems == 0 && nignored > 0) { |
541 | ignoring = FALSE; |
542 | nignored = 0; |
543 | if (looking_for_lognames) |
544 | (void)setpwent(); |
545 | else |
546 | rewinddir(dir_fd); |
547 | goto again; |
548 | } |
549 | |
550 | if (looking_for_lognames) |
551 | (void)endpwent(); |
552 | else |
553 | (void)closedir(dir_fd); |
554 | if (numitems == 0) |
555 | return (0); |
556 | if (command == RECOGNIZE) { |
557 | if (looking_for_lognames) |
558 | copyn(word, STRtilde, 1); |
559 | else |
560 | /* put back dir part */ |
561 | copyn(word, dir, max_word_length); |
562 | /* add extended name */ |
563 | catn(word, extended_name, max_word_length); |
564 | return (numitems); |
565 | } |
566 | else { /* LIST */ |
567 | qsort(items, numitems, sizeof(items[0]), |
568 | (int (*) (const void *, const void *)) sortscmp); |
569 | print_by_column(looking_for_lognames ? NULL : tilded_dir, |
570 | items, numitems); |
571 | if (items != NULL) |
572 | FREE_ITEMS(items, numitems); |
573 | } |
574 | return (0); |
575 | } |
576 | |
577 | /* |
578 | * Object: extend what user typed up to an ambiguity. |
579 | * Algorithm: |
580 | * On first match, copy full entry (assume it'll be the only match) |
581 | * On subsequent matches, shorten extended_name to the first |
582 | * Character mismatch between extended_name and entry. |
583 | * If we shorten it back to the prefix length, stop searching. |
584 | */ |
585 | static int |
586 | recognize(Char *extended_name, Char *entry, size_t name_length, size_t numitems) |
587 | { |
588 | if (numitems == 1) /* 1st match */ |
589 | copyn(extended_name, entry, MAXNAMLEN); |
590 | else { /* 2nd & subsequent matches */ |
591 | Char *ent, *x; |
592 | size_t len = 0; |
593 | |
594 | x = extended_name; |
595 | for (ent = entry; *x && *x == *ent++; x++, len++) |
596 | continue; |
597 | *x = '\0'; /* Shorten at 1st Char diff */ |
598 | if (len == name_length) /* Ambiguous to prefix? */ |
599 | return (-1); /* So stop now and save time */ |
600 | } |
601 | return (0); |
602 | } |
603 | |
604 | /* |
605 | * Return true if check matches initial Chars in template. |
606 | * This differs from PWB imatch in that if check is null |
607 | * it matches anything. |
608 | */ |
609 | static int |
610 | is_prefix(Char *check, Char *template) |
611 | { |
612 | do |
613 | if (*check == 0) |
614 | return (TRUE); |
615 | while (*check++ == *template++); |
616 | return (FALSE); |
617 | } |
618 | |
619 | /* |
620 | * Return true if the Chars in template appear at the |
621 | * end of check, I.e., are its suffix. |
622 | */ |
623 | static int |
624 | is_suffix(Char *check, Char *template) |
625 | { |
626 | Char *c, *t; |
627 | |
628 | for (c = check; *c++;) |
629 | continue; |
630 | for (t = template; *t++;) |
631 | continue; |
632 | for (;;) { |
633 | if (t == template) |
634 | return 1; |
635 | if (c == check || *--t != *--c) |
636 | return 0; |
637 | } |
638 | } |
639 | |
640 | ssize_t |
641 | tenex(Char *inputline, size_t inputline_size) |
642 | { |
643 | char tinputline[BUFSIZE]; |
644 | ssize_t num_read; |
645 | size_t numitems; |
646 | |
647 | setup_tty(ON); |
648 | |
649 | while ((num_read = read(SHIN, tinputline, BUFSIZE)) > 0) { |
650 | size_t i, nr = (size_t) num_read; |
651 | |
652 | |
653 | static Char delims[] = {' ', '\'', '"', '\t', ';', '&', '<', |
654 | '>', '(', ')', '|', '^', '%', '\0'}; |
655 | Char *str_end, *word_start, last_Char, should_retype; |
656 | size_t space_left; |
657 | COMMAND command; |
658 | |
659 | for (i = 0; i < nr; i++) |
660 | inputline[i] = (unsigned char) tinputline[i]; |
661 | last_Char = inputline[nr - 1] & ASCII; |
662 | |
663 | if (last_Char == '\n' || nr == inputline_size) |
664 | break; |
665 | command = (last_Char == ESC) ? RECOGNIZE : LIST; |
666 | if (command == LIST) |
667 | (void)fputc('\n', cshout); |
668 | str_end = &inputline[nr]; |
669 | if (last_Char == ESC) |
670 | --str_end; /* wipeout trailing cmd Char */ |
671 | *str_end = '\0'; |
672 | /* |
673 | * Find LAST occurence of a delimiter in the inputline. The word start |
674 | * is one Character past it. |
675 | */ |
676 | for (word_start = str_end; word_start > inputline; --word_start) |
677 | if (Strchr(delims, word_start[-1])) |
678 | break; |
679 | space_left = inputline_size - (size_t)(word_start - inputline) - 1; |
680 | numitems = tsearch(word_start, command, space_left); |
681 | |
682 | if (command == RECOGNIZE) { |
683 | /* print from str_end on */ |
684 | print_recognized_stuff(str_end); |
685 | if (numitems != 1) /* Beep = No match/ambiguous */ |
686 | beep(); |
687 | } |
688 | |
689 | /* |
690 | * Tabs in the input line cause trouble after a pushback. tty driver |
691 | * won't backspace over them because column positions are now |
692 | * incorrect. This is solved by retyping over current line. |
693 | */ |
694 | should_retype = FALSE; |
695 | if (Strchr(inputline, '\t')) { /* tab Char in input line? */ |
696 | back_to_col_1(); |
697 | should_retype = TRUE; |
698 | } |
699 | if (command == LIST) /* Always retype after a LIST */ |
700 | should_retype = TRUE; |
701 | if (pushback(inputline)) |
702 | should_retype = TRUE; |
703 | if (should_retype) { |
704 | if (command == RECOGNIZE) |
705 | (void) fputc('\n', cshout); |
706 | printprompt(); |
707 | } |
708 | if (should_retype) |
709 | retype(); |
710 | } |
711 | setup_tty(OFF); |
712 | return num_read; |
713 | } |
714 | |
715 | static int |
716 | ignored(Char *entry) |
717 | { |
718 | struct varent *vp; |
719 | Char **cp; |
720 | |
721 | if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL) |
722 | return (FALSE); |
723 | for (; *cp != NULL; cp++) |
724 | if (is_suffix(entry, *cp)) |
725 | return (TRUE); |
726 | return (FALSE); |
727 | } |
728 | #endif /* FILEC */ |
729 | |