from Jim McKie
[unix-history] / usr / src / bin / csh / file.c
CommitLineData
5b182e7a
SL
1#ifndef lint
2static 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
19extern 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
29typedef enum {LIST, RECOGNIZE} COMMAND;
30
31#define equal(a, b) (strcmp(a, b) == 0)
32
33static struct tchars tchars; /* INT, QUIT, XON, XOFF, EOF, BRK */
34
35static
5b182e7a
SL
36setup_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 */
69static
5b182e7a 70back_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 */
88static
5b182e7a
SL
89pushback(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
113catn(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
126static
5b182e7a 127max(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
137copyn(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 */
151static
5b182e7a
SL
152fcompare(file1, file2)
153 char **file1, **file2;
d8bd96dd 154{
5b182e7a
SL
155
156 return (strcmp(*file1, *file2));
d8bd96dd
KL
157}
158
159static char
5b182e7a
SL
160filetype(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 */
182static
5b182e7a
SL
183print_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 218char *
5b182e7a
SL
219tilde(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 */
248static
5b182e7a 249retype()
d8bd96dd 250{
5b182e7a
SL
251 int pending_input = LPENDIN;
252
253 ioctl(SHOUT, TIOCLBIS, &pending_input);
d8bd96dd
KL
254}
255
256static
5b182e7a 257beep()
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 */
267static
5b182e7a
SL
268print_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 */
294static
5b182e7a
SL
295extract_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 311char *
5b182e7a
SL
312getentry(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
329static
5b182e7a
SL
330free_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 */
352static
5b182e7a
SL
353search(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
449recognize(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 */
474static
5b182e7a
SL
475is_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
489tenex(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}