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