Commit | Line | Data |
---|---|---|
b79f4fa9 DF |
1 | /* |
2 | * Copyright (c) 1980 Regents of the University of California. | |
094e80ed | 3 | * All rights reserved. The Berkeley Software License Agreement |
b79f4fa9 DF |
4 | * specifies the terms and conditions for redistribution. |
5 | */ | |
6 | ||
5b182e7a | 7 | #ifndef lint |
094e80ed EW |
8 | static char *sccsid = "@(#)file.c 5.2 (Berkeley) %G%"; |
9 | #endif | |
d8bd96dd | 10 | |
35371dec | 11 | #ifdef FILEC |
d8bd96dd KL |
12 | /* |
13 | * Tenex style file name recognition, .. and more. | |
14 | * History: | |
15 | * Author: Ken Greer, Sept. 1975, CMU. | |
16 | * Finally got around to adding to the Cshell., Ken Greer, Dec. 1981. | |
17 | */ | |
18 | ||
35371dec | 19 | #include "sh.h" |
d8bd96dd KL |
20 | #include <sgtty.h> |
21 | #include <sys/dir.h> | |
d8bd96dd KL |
22 | #include <pwd.h> |
23 | ||
d8bd96dd KL |
24 | #define TRUE 1 |
25 | #define FALSE 0 | |
26 | #define ON 1 | |
27 | #define OFF 0 | |
d8bd96dd KL |
28 | |
29 | #define ESC '\033' | |
30 | ||
31 | typedef enum {LIST, RECOGNIZE} COMMAND; | |
32 | ||
d8bd96dd KL |
33 | static struct tchars tchars; /* INT, QUIT, XON, XOFF, EOF, BRK */ |
34 | ||
35371dec EW |
35 | /* |
36 | * Put this here so the binary can be patched with adb to enable file | |
37 | * completion by default. | |
38 | */ | |
39 | bool filec = 0; | |
40 | ||
d8bd96dd | 41 | static |
5b182e7a SL |
42 | setup_tty(on) |
43 | int on; | |
d8bd96dd | 44 | { |
d8bd96dd | 45 | struct sgttyb sgtty; |
5b182e7a SL |
46 | int omask; |
47 | ||
48 | omask = sigblock(sigmask(SIGINT)); | |
49 | if (on) { | |
35371dec | 50 | (void) ioctl(SHIN, TIOCGETC, (char *)&tchars); |
5b182e7a | 51 | tchars.t_brkc = ESC; |
35371dec | 52 | (void) ioctl(SHIN, TIOCSETC, (char *)&tchars); |
5b182e7a SL |
53 | /* |
54 | * This is a useful feature in it's own right... | |
55 | * The shell makes sure that the tty is not in some weird state | |
56 | * and fixes it if it is. But it should be noted that the | |
57 | * tenex routine will not work correctly in CBREAK or RAW mode | |
58 | * so this code below is, therefore, mandatory. | |
59 | */ | |
35371dec | 60 | (void) ioctl(SHIN, TIOCGETP, (char *)&sgtty); |
5b182e7a SL |
61 | if (sgtty.sg_flags & (RAW|CBREAK)) { |
62 | sgtty.sg_flags &= ~(RAW|CBREAK); | |
35371dec | 63 | (void) ioctl(SHIN, TIOCSETP, (char *)&sgtty); |
5b182e7a SL |
64 | } |
65 | } else { | |
66 | tchars.t_brkc = -1; | |
35371dec | 67 | (void) ioctl(SHIN, TIOCSETC, (char *)&tchars); |
d8bd96dd | 68 | } |
35371dec | 69 | (void) sigsetmask (omask); |
d8bd96dd KL |
70 | } |
71 | ||
72 | /* | |
73 | * Move back to beginning of current line | |
74 | */ | |
75 | static | |
5b182e7a | 76 | back_to_col_1() |
d8bd96dd | 77 | { |
5b182e7a SL |
78 | struct sgttyb tty, tty_normal; |
79 | int omask; | |
80 | ||
81 | omask = sigblock(sigmask(SIGINT)); | |
35371dec | 82 | (void) ioctl(SHIN, TIOCGETP, (char *)&tty); |
5b182e7a SL |
83 | tty_normal = tty; |
84 | tty.sg_flags &= ~CRMOD; | |
35371dec | 85 | (void) ioctl(SHIN, TIOCSETN, (char *)&tty); |
5b182e7a | 86 | (void) write(SHOUT, "\r", 1); |
35371dec EW |
87 | (void) ioctl(SHIN, TIOCSETN, (char *)&tty_normal); |
88 | (void) sigsetmask(omask); | |
d8bd96dd KL |
89 | } |
90 | ||
91 | /* | |
92 | * Push string contents back into tty queue | |
93 | */ | |
94 | static | |
5b182e7a SL |
95 | pushback(string) |
96 | char *string; | |
d8bd96dd | 97 | { |
5b182e7a SL |
98 | register char *p; |
99 | struct sgttyb tty, tty_normal; | |
100 | int omask; | |
101 | ||
102 | omask = sigblock(sigmask(SIGINT)); | |
35371dec | 103 | (void) ioctl(SHOUT, TIOCGETP, (char *)&tty); |
5b182e7a SL |
104 | tty_normal = tty; |
105 | tty.sg_flags &= ~ECHO; | |
35371dec | 106 | (void) ioctl(SHOUT, TIOCSETN, (char *)&tty); |
5b182e7a SL |
107 | |
108 | for (p = string; *p; p++) | |
35371dec EW |
109 | (void) ioctl(SHOUT, TIOCSTI, p); |
110 | (void) ioctl(SHOUT, TIOCSETN, (char *)&tty_normal); | |
111 | (void) sigsetmask(omask); | |
d8bd96dd KL |
112 | } |
113 | ||
114 | /* | |
5b182e7a | 115 | * Concatenate src onto tail of des. |
d8bd96dd KL |
116 | * Des is a string whose maximum length is count. |
117 | * Always null terminate. | |
118 | */ | |
5b182e7a SL |
119 | catn(des, src, count) |
120 | register char *des, *src; | |
121 | register count; | |
d8bd96dd | 122 | { |
5b182e7a SL |
123 | |
124 | while (--count >= 0 && *des) | |
125 | des++; | |
126 | while (--count >= 0) | |
127 | if ((*des++ = *src++) == 0) | |
128 | return; | |
129 | *des = '\0'; | |
d8bd96dd KL |
130 | } |
131 | ||
132 | static | |
5b182e7a | 133 | max(a, b) |
d8bd96dd | 134 | { |
5b182e7a SL |
135 | |
136 | return (a > b ? a : b); | |
d8bd96dd KL |
137 | } |
138 | ||
139 | /* | |
5b182e7a | 140 | * Like strncpy but always leave room for trailing \0 |
d8bd96dd KL |
141 | * and always null terminate. |
142 | */ | |
5b182e7a SL |
143 | copyn(des, src, count) |
144 | register char *des, *src; | |
145 | register count; | |
d8bd96dd | 146 | { |
5b182e7a SL |
147 | |
148 | while (--count >= 0) | |
149 | if ((*des++ = *src++) == 0) | |
150 | return; | |
151 | *des = '\0'; | |
d8bd96dd KL |
152 | } |
153 | ||
154 | /* | |
155 | * For qsort() | |
156 | */ | |
157 | static | |
5b182e7a SL |
158 | fcompare(file1, file2) |
159 | char **file1, **file2; | |
d8bd96dd | 160 | { |
5b182e7a SL |
161 | |
162 | return (strcmp(*file1, *file2)); | |
d8bd96dd KL |
163 | } |
164 | ||
165 | static char | |
5b182e7a SL |
166 | filetype(dir, file) |
167 | char *dir, *file; | |
d8bd96dd | 168 | { |
d8bd96dd KL |
169 | char path[512]; |
170 | struct stat statb; | |
5b182e7a SL |
171 | |
172 | if (dir) { | |
35371dec | 173 | catn(strcpy(path, dir), file, sizeof path); |
5b182e7a SL |
174 | if (stat(path, &statb) >= 0) { |
175 | if (statb.st_mode & S_IFDIR) | |
176 | return ('/'); | |
177 | if (statb.st_mode & 0111) | |
178 | return ('*'); | |
179 | } | |
d8bd96dd | 180 | } |
5b182e7a | 181 | return (' '); |
d8bd96dd KL |
182 | } |
183 | ||
184 | /* | |
185 | * Print sorted down columns | |
186 | */ | |
187 | static | |
5b182e7a SL |
188 | print_by_column(dir, items, count) |
189 | char *dir, *items[]; | |
d8bd96dd | 190 | { |
5b182e7a SL |
191 | register int i, rows, r, c, maxwidth = 0, columns; |
192 | ||
193 | for (i = 0; i < count; i++) | |
194 | maxwidth = max(maxwidth, strlen(items[i])); | |
195 | maxwidth += 2; /* for the file tag and space */ | |
196 | columns = 78 / maxwidth; | |
197 | rows = (count + (columns - 1)) / columns; | |
198 | for (r = 0; r < rows; r++) { | |
199 | for (c = 0; c < columns; c++) { | |
200 | i = c * rows + r; | |
201 | if (i < count) { | |
202 | register int w; | |
203 | ||
204 | printf("%s", items[i]); | |
205 | putchar(filetype(dir, items[i])); | |
206 | if (c < columns - 1) { /* last column? */ | |
207 | w = strlen(items[i]) + 1; | |
208 | for (; w < maxwidth; w++) | |
209 | printf(" "); | |
210 | } | |
211 | } | |
212 | } | |
213 | printf ("\n"); | |
d8bd96dd | 214 | } |
d8bd96dd KL |
215 | } |
216 | ||
217 | /* | |
5b182e7a SL |
218 | * Expand file name with possible tilde usage |
219 | * ~person/mumble | |
d8bd96dd | 220 | * expands to |
5b182e7a | 221 | * home_directory_of_person/mumble |
d8bd96dd | 222 | */ |
d8bd96dd | 223 | char * |
5b182e7a SL |
224 | tilde(new, old) |
225 | char *new, *old; | |
d8bd96dd | 226 | { |
5b182e7a SL |
227 | register char *o, *p; |
228 | register struct passwd *pw; | |
229 | static char person[40]; | |
5b182e7a SL |
230 | |
231 | if (old[0] != '~') | |
232 | return (strcpy(new, old)); | |
233 | ||
234 | for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++) | |
235 | ; | |
236 | *p = '\0'; | |
6f04d0da EW |
237 | if (person[0] == '\0') |
238 | (void) strcpy(new, value("home")); | |
239 | else { | |
5b182e7a | 240 | pw = getpwnam(person); |
6f04d0da EW |
241 | if (pw == NULL) |
242 | return (NULL); | |
243 | (void) strcpy(new, pw->pw_dir); | |
244 | } | |
5b182e7a SL |
245 | (void) strcat(new, o); |
246 | return (new); | |
d8bd96dd KL |
247 | } |
248 | ||
249 | /* | |
250 | * Cause pending line to be printed | |
251 | */ | |
252 | static | |
5b182e7a | 253 | retype() |
d8bd96dd | 254 | { |
5b182e7a SL |
255 | int pending_input = LPENDIN; |
256 | ||
35371dec | 257 | (void) ioctl(SHOUT, TIOCLBIS, (char *)&pending_input); |
d8bd96dd KL |
258 | } |
259 | ||
260 | static | |
5b182e7a | 261 | beep() |
d8bd96dd | 262 | { |
5b182e7a SL |
263 | |
264 | (void) write(SHOUT, "\07", 1); | |
d8bd96dd KL |
265 | } |
266 | ||
267 | /* | |
268 | * Erase that silly ^[ and | |
269 | * print the recognized part of the string | |
270 | */ | |
271 | static | |
5b182e7a SL |
272 | print_recognized_stuff(recognized_part) |
273 | char *recognized_part; | |
d8bd96dd | 274 | { |
5b182e7a SL |
275 | |
276 | /* An optimized erasing of that silly ^[ */ | |
277 | switch (strlen(recognized_part)) { | |
278 | ||
d8bd96dd | 279 | case 0: /* erase two characters: ^[ */ |
5b182e7a SL |
280 | printf("\210\210 \210\210"); |
281 | break; | |
282 | ||
d8bd96dd | 283 | case 1: /* overstrike the ^, erase the [ */ |
5b182e7a SL |
284 | printf("\210\210%s \210", recognized_part); |
285 | break; | |
286 | ||
d8bd96dd | 287 | default: /* overstrike both characters ^[ */ |
5b182e7a SL |
288 | printf("\210\210%s", recognized_part); |
289 | break; | |
290 | } | |
291 | flush(); | |
d8bd96dd KL |
292 | } |
293 | ||
294 | /* | |
5b182e7a | 295 | * Parse full path in file into 2 parts: directory and file names |
d8bd96dd KL |
296 | * Should leave final slash (/) at end of dir. |
297 | */ | |
298 | static | |
5b182e7a SL |
299 | extract_dir_and_name(path, dir, name) |
300 | char *path, *dir, *name; | |
d8bd96dd | 301 | { |
5b182e7a | 302 | register char *p; |
5b182e7a | 303 | |
35371dec | 304 | p = rindex(path, '/'); |
5b182e7a SL |
305 | if (p == NULL) { |
306 | copyn(name, path, MAXNAMLEN); | |
307 | dir[0] = '\0'; | |
308 | } else { | |
309 | copyn(name, ++p, MAXNAMLEN); | |
310 | copyn(dir, path, p - path); | |
311 | } | |
d8bd96dd KL |
312 | } |
313 | ||
d8bd96dd | 314 | char * |
5b182e7a SL |
315 | getentry(dir_fd, looking_for_lognames) |
316 | DIR *dir_fd; | |
d8bd96dd | 317 | { |
d8bd96dd | 318 | register struct passwd *pw; |
d8bd96dd | 319 | register struct direct *dirp; |
5b182e7a SL |
320 | |
321 | if (looking_for_lognames) { | |
322 | if ((pw = getpwent ()) == NULL) | |
323 | return (NULL); | |
324 | return (pw->pw_name); | |
325 | } | |
326 | if (dirp = readdir(dir_fd)) | |
327 | return (dirp->d_name); | |
d8bd96dd | 328 | return (NULL); |
d8bd96dd KL |
329 | } |
330 | ||
331 | static | |
5b182e7a SL |
332 | free_items(items) |
333 | register char **items; | |
d8bd96dd | 334 | { |
5b182e7a SL |
335 | register int i; |
336 | ||
337 | for (i = 0; items[i]; i++) | |
338 | free(items[i]); | |
35371dec | 339 | free((char *)items); |
d8bd96dd KL |
340 | } |
341 | ||
5b182e7a SL |
342 | #define FREE_ITEMS(items) { \ |
343 | int omask;\ | |
9208cf61 | 344 | \ |
5b182e7a SL |
345 | omask = sigblock(sigmask(SIGINT));\ |
346 | free_items(items);\ | |
347 | items = NULL;\ | |
35371dec | 348 | (void) sigsetmask(omask);\ |
d8bd96dd KL |
349 | } |
350 | ||
351 | /* | |
352 | * Perform a RECOGNIZE or LIST command on string "word". | |
353 | */ | |
354 | static | |
5b182e7a SL |
355 | search(word, command, max_word_length) |
356 | char *word; | |
357 | COMMAND command; | |
d8bd96dd | 358 | { |
5b182e7a SL |
359 | static char **items = NULL; |
360 | register DIR *dir_fd; | |
361 | register numitems, name_length, looking_for_lognames; | |
35371dec | 362 | char tilded_dir[MAXPATHLEN + 1], dir[MAXPATHLEN + 1]; |
9208cf61 | 363 | char name[MAXNAMLEN + 1], extended_name[MAXNAMLEN+1]; |
5b182e7a SL |
364 | char *entry; |
365 | #define MAXITEMS 1024 | |
d8bd96dd | 366 | |
5b182e7a SL |
367 | if (items != NULL) |
368 | FREE_ITEMS(items); | |
369 | ||
370 | looking_for_lognames = (*word == '~') && (index(word, '/') == NULL); | |
371 | if (looking_for_lognames) { | |
35371dec | 372 | (void) setpwent(); |
5b182e7a SL |
373 | copyn(name, &word[1], MAXNAMLEN); /* name sans ~ */ |
374 | } else { | |
375 | extract_dir_and_name(word, dir, name); | |
376 | if (tilde(tilded_dir, dir) == 0) | |
377 | return (0); | |
378 | dir_fd = opendir(*tilded_dir ? tilded_dir : "."); | |
379 | if (dir_fd == NULL) | |
380 | return (0); | |
381 | } | |
382 | name_length = strlen(name); | |
383 | for (numitems = 0; entry = getentry(dir_fd, looking_for_lognames); ) { | |
384 | if (!is_prefix(name, entry)) | |
385 | continue; | |
386 | /* Don't match . files on null prefix match */ | |
387 | if (name_length == 0 && entry[0] == '.' && | |
388 | !looking_for_lognames) | |
389 | continue; | |
390 | if (command == LIST) { | |
5b182e7a SL |
391 | if (numitems >= MAXITEMS) { |
392 | printf ("\nYikes!! Too many %s!!\n", | |
393 | looking_for_lognames ? | |
394 | "names in password file":"files"); | |
395 | break; | |
396 | } | |
e3f1ec6b | 397 | if (items == NULL) |
5b182e7a SL |
398 | items = (char **) calloc(sizeof (items[1]), |
399 | MAXITEMS); | |
e3f1ec6b | 400 | items[numitems] = xalloc((unsigned)strlen(entry) + 1); |
5b182e7a SL |
401 | copyn(items[numitems], entry, MAXNAMLEN); |
402 | numitems++; | |
403 | } else /* RECOGNIZE command */ | |
404 | if (recognize(extended_name, entry, name_length, | |
405 | ++numitems)) | |
406 | break; | |
407 | } | |
d8bd96dd | 408 | if (looking_for_lognames) |
35371dec | 409 | (void) endpwent(); |
d8bd96dd | 410 | else |
5b182e7a SL |
411 | closedir(dir_fd); |
412 | if (command == RECOGNIZE && numitems > 0) { | |
413 | if (looking_for_lognames) | |
414 | copyn(word, "~", 1); | |
415 | else | |
416 | /* put back dir part */ | |
417 | copyn(word, dir, max_word_length); | |
418 | /* add extended name */ | |
419 | catn(word, extended_name, max_word_length); | |
420 | return (numitems); | |
421 | } | |
422 | if (command == LIST) { | |
35371dec | 423 | qsort((char *)items, numitems, sizeof(items[1]), fcompare); |
5b182e7a SL |
424 | print_by_column(looking_for_lognames ? NULL : tilded_dir, |
425 | items, numitems); | |
426 | if (items != NULL) | |
427 | FREE_ITEMS(items); | |
428 | } | |
429 | return (0); | |
d8bd96dd KL |
430 | } |
431 | ||
432 | /* | |
433 | * Object: extend what user typed up to an ambiguity. | |
434 | * Algorithm: | |
435 | * On first match, copy full entry (assume it'll be the only match) | |
436 | * On subsequent matches, shorten extended_name to the first | |
437 | * character mismatch between extended_name and entry. | |
438 | * If we shorten it back to the prefix length, stop searching. | |
439 | */ | |
5b182e7a SL |
440 | recognize(extended_name, entry, name_length, numitems) |
441 | char *extended_name, *entry; | |
d8bd96dd | 442 | { |
5b182e7a SL |
443 | |
444 | if (numitems == 1) /* 1st match */ | |
445 | copyn(extended_name, entry, MAXNAMLEN); | |
446 | else { /* 2nd and subsequent matches */ | |
447 | register char *x, *ent; | |
448 | register int len = 0; | |
449 | ||
450 | x = extended_name; | |
451 | for (ent = entry; *x && *x == *ent++; x++, len++) | |
452 | ; | |
453 | *x = '\0'; /* Shorten at 1st char diff */ | |
454 | if (len == name_length) /* Ambiguous to prefix? */ | |
455 | return (-1); /* So stop now and save time */ | |
456 | } | |
457 | return (0); | |
d8bd96dd KL |
458 | } |
459 | ||
460 | /* | |
5b182e7a | 461 | * Return true if check items initial chars in template |
d8bd96dd KL |
462 | * This differs from PWB imatch in that if check is null |
463 | * it items anything | |
464 | */ | |
465 | static | |
5b182e7a SL |
466 | is_prefix(check, template) |
467 | char *check, *template; | |
d8bd96dd | 468 | { |
5b182e7a SL |
469 | register char *check_char, *template_char; |
470 | ||
471 | check_char = check; | |
472 | template_char = template; | |
473 | do | |
474 | if (*check_char == 0) | |
475 | return (TRUE); | |
476 | while (*check_char++ == *template_char++); | |
477 | return (FALSE); | |
d8bd96dd KL |
478 | } |
479 | ||
5b182e7a SL |
480 | tenex(inputline, inputline_size) |
481 | char *inputline; | |
482 | int inputline_size; | |
d8bd96dd | 483 | { |
5b182e7a SL |
484 | register int numitems, num_read; |
485 | ||
486 | setup_tty(ON); | |
487 | while ((num_read = read (SHIN, inputline, inputline_size)) > 0) { | |
488 | static char *delims = " '\"\t;&<>()|^%"; | |
489 | register char *str_end, *word_start, last_char, should_retype; | |
490 | register int space_left; | |
491 | COMMAND command; | |
492 | ||
493 | last_char = inputline[num_read - 1] & 0177; | |
494 | ||
495 | if (last_char == '\n' || num_read == inputline_size) | |
496 | break; | |
497 | command = (last_char == ESC) ? RECOGNIZE : LIST; | |
498 | if (command == LIST) | |
499 | putchar ('\n'); | |
500 | str_end = &inputline[num_read]; | |
501 | if (last_char == ESC) | |
502 | --str_end; /* wipeout trailing cmd char */ | |
503 | *str_end = '\0'; | |
504 | /* | |
505 | * Find LAST occurence of a delimiter in the inputline. | |
506 | * The word start is one character past it. | |
507 | */ | |
508 | for (word_start = str_end; word_start > inputline; --word_start) | |
509 | if (index(delims, word_start[-1])) | |
510 | break; | |
511 | space_left = inputline_size - (word_start - inputline) - 1; | |
512 | numitems = search(word_start, command, space_left); | |
513 | ||
514 | if (command == RECOGNIZE) { | |
515 | /* print from str_end on */ | |
516 | print_recognized_stuff(str_end); | |
517 | if (numitems != 1) /* Beep = No match/ambiguous */ | |
518 | beep (); | |
519 | } | |
520 | ||
521 | /* | |
522 | * Tabs in the input line cause trouble after a pushback. | |
523 | * tty driver won't backspace over them because column | |
524 | * positions are now incorrect. This is solved by retyping | |
525 | * over current line. | |
526 | */ | |
527 | should_retype = FALSE; | |
528 | if (index(inputline, '\t')) { /* tab char in input line? */ | |
529 | back_to_col_1(); | |
530 | should_retype = TRUE; | |
531 | } | |
532 | if (command == LIST) /* Always retype after a LIST */ | |
533 | should_retype = TRUE; | |
534 | if (should_retype) | |
535 | printprompt(); | |
536 | pushback(inputline); | |
537 | if (should_retype) | |
538 | retype (); | |
d8bd96dd | 539 | } |
5b182e7a SL |
540 | setup_tty (OFF); |
541 | return (num_read); | |
d8bd96dd | 542 | } |
35371dec | 543 | #endif FILEC |