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