complete.c revision 1.18 1 /* $NetBSD: complete.c,v 1.18 2009/02/11 19:22:22 christos Exp $ */
2
3 /*-
4 * Copyright (c) 1997-2000,2005,2006 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Luke Mewburn.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /*
33 * Most of this is derived or copied from src/usr.bin/ftp/complete.c (1.41).
34 */
35
36 #ifdef USE_EDITLINE
37 #undef NO_EDITCOMPLETE
38
39 #include <sys/cdefs.h>
40 #ifndef lint
41 __RCSID("$NetBSD: complete.c,v 1.18 2009/02/11 19:22:22 christos Exp $");
42 #endif /* not lint */
43
44 /*
45 * FTP user program - command and file completion routines
46 */
47
48 #include <assert.h>
49 #include <ctype.h>
50 #include <err.h>
51 #include <dirent.h>
52 #include <glob.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <stringlist.h>
57 #include <termcap.h>
58 #include <util.h>
59
60 #include <sys/param.h>
61 #include <sys/stat.h>
62
63 #include "rcv.h" /* includes "glob.h" */
64 #include "extern.h"
65 #include "complete.h"
66 #ifdef MIME_SUPPORT
67 #include "mime.h"
68 #endif
69 #ifdef THREAD_SUPPORT
70 #include "thread.h"
71 #endif
72
73 #define BELL 0x7
74
75 /*
76 * Global variables
77 */
78 static int doglob = 1; /* glob local file names */
79
80 #define ttyout stdout
81 #define ttywidth screenwidth /* in "glob.h" */
82 #define ttyheight screenheight /* in "glob.h" */
83
84 /************************************************************************/
85 /* from src/usr.bin/ftp/utils.h (1.135) - begin */
86
87 /*
88 * List words in stringlist, vertically arranged
89 */
90 static void
91 list_vertical(StringList *sl)
92 {
93 int k;
94 size_t i, j, columns, lines;
95 char *p;
96 size_t w, width;
97
98 width = 0;
99
100 for (i = 0; i < sl->sl_cur; i++) {
101 w = strlen(sl->sl_str[i]);
102 if (w > width)
103 width = w;
104 }
105 width = (width + 8) &~ 7;
106
107 columns = ttywidth / width;
108 if (columns == 0)
109 columns = 1;
110 lines = (sl->sl_cur + columns - 1) / columns;
111 k = 0;
112 for (i = 0; i < lines; i++) {
113 for (j = 0; j < columns; j++) {
114 p = sl->sl_str[j * lines + i];
115 if (p)
116 (void)fputs(p, ttyout);
117 if (j * lines + i + lines >= sl->sl_cur) {
118 (void)putc('\n', ttyout);
119 break;
120 }
121 if (p) {
122 w = strlen(p);
123 while (w < width) {
124 w = (w + 8) &~ 7;
125 (void)putc('\t', ttyout);
126 }
127 }
128 }
129 if (ttyheight > 2 && ++k == ttyheight - 2) {
130 int ch;
131 k = 0;
132 (void)fputs("--more--", ttyout);
133 while ((ch = getchar()) != EOF && ch != ' ' && ch != 'q')
134 (void)putc(BELL, ttyout);
135 (void)fputs("\r \r", ttyout);
136 if (ch == 'q')
137 break;
138 }
139 }
140 }
141
142 /*
143 * Copy characters from src into dst, \ quoting characters that require it
144 */
145 static void
146 ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
147 {
148 size_t di, si;
149
150 for (di = si = 0;
151 src[si] != '\0' && di < dstlen && si < srclen;
152 di++, si++) {
153 switch (src[si]) {
154 case '\\':
155 case ' ':
156 case '\t':
157 case '\r':
158 case '\n':
159 case '"':
160 dst[di++] = '\\';
161 if (di >= dstlen)
162 break;
163 /* FALLTHROUGH */
164 default:
165 dst[di] = src[si];
166 }
167 }
168 dst[di] = '\0';
169 }
170
171 /*
172 * sl_init() with inbuilt error checking
173 */
174 static StringList *
175 mail_sl_init(void)
176 {
177 StringList *p;
178
179 p = sl_init();
180 if (p == NULL)
181 err(1, "Unable to allocate memory for stringlist");
182 return p;
183 }
184
185
186 /*
187 * sl_add() with inbuilt error checking
188 */
189 static void
190 mail_sl_add(StringList *sl, char *i)
191 {
192
193 if (sl_add(sl, i) == -1)
194 err(1, "Unable to add `%s' to stringlist", i);
195 }
196
197
198 /*
199 * Glob a local file name specification with the expectation of a single
200 * return value. Can't control multiple values being expanded from the
201 * expression, we return only the first.
202 * Returns NULL on error, or a pointer to a buffer containing the filename
203 * that's the caller's responsiblity to free(3) when finished with.
204 */
205 static char *
206 globulize(const char *pattern)
207 {
208 glob_t gl;
209 int flags;
210 char *p;
211
212 if (!doglob)
213 return estrdup(pattern);
214
215 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
216 (void)memset(&gl, 0, sizeof(gl));
217 if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) {
218 warnx("%s: not found", pattern);
219 globfree(&gl);
220 return NULL;
221 }
222 p = estrdup(gl.gl_pathv[0]);
223 globfree(&gl);
224 return p;
225 }
226
227 /* from src/usr.bin/ftp/utils.h (1.135) - end */
228 /************************************************************************/
229
230 static int
231 comparstr(const void *a, const void *b)
232 {
233 return strcmp(*(const char * const *)a, *(const char * const *)b);
234 }
235
236 /*
237 * Determine if complete is ambiguous. If unique, insert.
238 * If no choices, error. If unambiguous prefix, insert that.
239 * Otherwise, list choices. words is assumed to be filtered
240 * to only contain possible choices.
241 * Args:
242 * word word which started the match
243 * dolist list by default
244 * words stringlist containing possible matches
245 * Returns a result as per el_set(EL_ADDFN, ...)
246 */
247 static unsigned char
248 complete_ambiguous(EditLine *el, char *word, int dolist, StringList *words)
249 {
250 char insertstr[MAXPATHLEN];
251 char *lastmatch, *p;
252 size_t i, j, matchlen, wordlen;
253
254 wordlen = strlen(word);
255 if (words->sl_cur == 0)
256 return CC_ERROR; /* no choices available */
257
258 if (words->sl_cur == 1) { /* only once choice available */
259 p = words->sl_str[0] + wordlen;
260 if (*p == '\0') /* at end of word? */
261 return CC_REFRESH;
262 ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
263 if (el_insertstr(el, insertstr) == -1)
264 return CC_ERROR;
265 else
266 return CC_REFRESH;
267 }
268
269 if (!dolist) {
270 matchlen = 0;
271 lastmatch = words->sl_str[0];
272 matchlen = strlen(lastmatch);
273 for (i = 1; i < words->sl_cur; i++) {
274 for (j = wordlen; j < strlen(words->sl_str[i]); j++)
275 if (lastmatch[j] != words->sl_str[i][j])
276 break;
277 if (j < matchlen)
278 matchlen = j;
279 }
280 if (matchlen >= wordlen) {
281 ftpvis(insertstr, sizeof(insertstr),
282 lastmatch + wordlen, matchlen - wordlen);
283 if (el_insertstr(el, insertstr) == -1)
284 return CC_ERROR;
285 else
286 return CC_REFRESH_BEEP;
287 }
288 }
289
290 (void)putc('\n', ttyout);
291 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
292
293 list_vertical(words);
294 return CC_REDISPLAY;
295 }
296
297 /*
298 * Complete a mail command.
299 */
300 static unsigned char
301 complete_command(EditLine *el, char *word, int dolist)
302 {
303 const struct cmd *c;
304 StringList *words;
305 size_t wordlen;
306 unsigned char rv;
307
308 words = mail_sl_init();
309 wordlen = strlen(word);
310
311 for (c = cmdtab; c->c_name != NULL; c++) {
312 if (wordlen > strlen(c->c_name))
313 continue;
314 if (strncmp(word, c->c_name, wordlen) == 0)
315 mail_sl_add(words, __UNCONST(c->c_name));
316 }
317
318 rv = complete_ambiguous(el, word, dolist, words);
319 if (rv == CC_REFRESH) {
320 if (el_insertstr(el, " ") == -1)
321 rv = CC_ERROR;
322 }
323 sl_free(words, 0);
324 return rv;
325 }
326
327 /*
328 * Complete a local filename.
329 */
330 static unsigned char
331 complete_filename(EditLine *el, char *word, int dolist)
332 {
333 StringList *words;
334 char dir[MAXPATHLEN];
335 char *fname;
336 DIR *dd;
337 struct dirent *dp;
338 unsigned char rv;
339 size_t len;
340
341 if ((fname = strrchr(word, '/')) == NULL) {
342 dir[0] = '.';
343 dir[1] = '\0';
344 fname = word;
345 } else {
346 if (fname == word) {
347 dir[0] = '/';
348 dir[1] = '\0';
349 } else {
350 len = fname - word + 1;
351 (void)estrlcpy(dir, word, sizeof(dir));
352 dir[len] = '\0';
353 }
354 fname++;
355 }
356 if (dir[0] == '~') {
357 char *p;
358
359 if ((p = globulize(dir)) == NULL)
360 return CC_ERROR;
361 (void)estrlcpy(dir, p, sizeof(dir));
362 free(p);
363 }
364
365 if ((dd = opendir(dir)) == NULL)
366 return CC_ERROR;
367
368 words = mail_sl_init();
369 len = strlen(fname);
370
371 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
372 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
373 continue;
374
375 #if defined(DIRENT_MISSING_D_NAMLEN)
376 if (len > strlen(dp->d_name))
377 continue;
378 #else
379 if (len > dp->d_namlen)
380 continue;
381 #endif
382 if (strncmp(fname, dp->d_name, len) == 0) {
383 char *tcp;
384
385 tcp = estrdup(dp->d_name);
386 mail_sl_add(words, tcp);
387 }
388 }
389 (void)closedir(dd);
390
391 rv = complete_ambiguous(el, fname, dolist, words);
392 if (rv == CC_REFRESH) {
393 struct stat sb;
394 char path[MAXPATHLEN];
395
396 (void)estrlcpy(path, dir, sizeof(path));
397 (void)estrlcat(path, "/", sizeof(path));
398 (void)estrlcat(path, words->sl_str[0], sizeof(path));
399
400 if (stat(path, &sb) >= 0) {
401 char suffix[2] = " ";
402
403 if (S_ISDIR(sb.st_mode))
404 suffix[0] = '/';
405 if (el_insertstr(el, suffix) == -1)
406 rv = CC_ERROR;
407 }
408 }
409 sl_free(words, 1);
410 return rv;
411 }
412
413 static int
414 find_execs(char *word, char *path, StringList *list)
415 {
416 char *sep;
417 char *dir=path;
418 DIR *dd;
419 struct dirent *dp;
420 size_t len = strlen(word);
421 uid_t uid = getuid();
422 gid_t gid = getgid();
423
424 for (sep = dir; sep; dir = sep + 1) {
425 if ((sep=strchr(dir, ':')) != NULL) {
426 *sep=0;
427 }
428
429 if ((dd = opendir(dir)) == NULL) {
430 perror("dir");
431 return -1;
432 }
433
434 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
435
436 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
437 continue;
438
439 #if defined(DIRENT_MISSING_D_NAMLEN)
440 if (len > strlen(dp->d_name))
441 continue;
442 #else
443 if (len > dp->d_namlen)
444 continue;
445 #endif
446
447 if (strncmp(word, dp->d_name, len) == 0) {
448 struct stat sb;
449 char pathname[ MAXPATHLEN ];
450 unsigned mask;
451
452 (void)snprintf(pathname, sizeof(pathname),
453 "%s/%s", dir, dp->d_name);
454 if (stat(pathname, &sb) != 0) {
455 perror(pathname);
456 continue;
457 }
458
459 mask = 0001;
460 if (sb.st_uid == uid) mask |= 0100;
461 if (sb.st_gid == gid) mask |= 0010;
462
463 if ((sb.st_mode & mask) != 0) {
464 char *tcp;
465 tcp = estrdup(dp->d_name);
466 mail_sl_add(list, tcp);
467 }
468 }
469
470 }
471
472 (void)closedir(dd);
473 }
474
475 return 0;
476 }
477
478
479 /*
480 * Complete a local executable
481 */
482 static unsigned char
483 complete_executable(EditLine *el, char *word, int dolist)
484 {
485 StringList *words;
486 char dir[ MAXPATHLEN ];
487 char *fname;
488 unsigned char rv;
489 size_t len;
490 int error;
491
492 if ((fname = strrchr(word, '/')) == NULL) {
493 dir[0] = '\0'; /* walk the path */
494 fname = word;
495 } else {
496 if (fname == word) {
497 dir[0] = '/';
498 dir[1] = '\0';
499 } else {
500 len = fname - word;
501 (void)strncpy(dir, word, len);
502 dir[fname - word] = '\0';
503 }
504 fname++;
505 }
506
507 words = sl_init();
508
509 if (*dir == '\0') { /* walk path */
510 char *env;
511 char *path;
512 env = getenv("PATH");
513 len = strlen(env);
514 path = salloc(len + 1);
515 (void)strcpy(path, env);
516 error = find_execs(word, path, words);
517 }
518 else { /* check specified dir only */
519 error = find_execs(word, dir, words);
520 }
521 if (error != 0)
522 return CC_ERROR;
523
524 rv = complete_ambiguous(el, fname, dolist, words);
525 if (rv == CC_REFRESH) {
526 if (el_insertstr(el, " ") == -1)
527 rv = CC_ERROR;
528 }
529 sl_free(words, 1);
530
531 return rv;
532 }
533
534
535 static unsigned char
536 complete_set(EditLine *el, char *word, int dolist)
537 {
538 struct var *vp;
539 const char **ap;
540 const char **p;
541 int h;
542 int s;
543 size_t len = strlen(word);
544 StringList *words;
545 unsigned char rv;
546
547 words = sl_init();
548
549 /* allocate space for variables table */
550 s = 1;
551 for (h = 0; h < HSHSIZE; h++)
552 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
553 s++;
554 ap = salloc(s * sizeof(*ap));
555
556 /* save the pointers */
557 for (h = 0, p = ap; h < HSHSIZE; h++)
558 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
559 *p++ = vp->v_name;
560 *p = NULL;
561 sort(ap);
562 for (p = ap; *p != NULL; p++)
563 if (len == 0 || strncmp(*p, word, len) == 0)
564 mail_sl_add(words, estrdup(*p));
565
566 rv = complete_ambiguous(el, word, dolist, words);
567
568 sl_free(words, 1);
569
570 return rv;
571 }
572
573
574 static unsigned char
575 complete_alias(EditLine *el, char *word, int dolist)
576 {
577 struct grouphead *gh;
578 const char **ap;
579 const char **p;
580 int h;
581 int s;
582 size_t len = strlen(word);
583 StringList *words;
584 unsigned char rv;
585
586 words = sl_init();
587
588 /* allocate space for alias table */
589 s = 1;
590 for (h = 0; h < HSHSIZE; h++)
591 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
592 s++;
593 ap = salloc(s * sizeof(*ap));
594
595 /* save pointers */
596 p = ap;
597 for (h = 0; h < HSHSIZE; h++)
598 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
599 *p++ = gh->g_name;
600
601 *p = NULL;
602
603 sort(ap);
604 for (p = ap; *p != NULL; p++)
605 if (len == 0 || strncmp(*p, word, len) == 0)
606 mail_sl_add(words, estrdup(*p));
607
608 rv = complete_ambiguous(el, word, dolist, words);
609 if (rv == CC_REFRESH) {
610 if (el_insertstr(el, " ") == -1)
611 rv = CC_ERROR;
612 }
613 sl_free(words, 1);
614 return rv;
615 }
616
617
618 static unsigned char
619 complete_smopts(EditLine *el, char *word, int dolist)
620 {
621 struct grouphead *gh;
622 struct smopts_s *sp;
623 const char **ap;
624 const char **p;
625 int h;
626 int s1;
627 int s2;
628 size_t len;
629 StringList *words;
630 unsigned char rv;
631
632 len = strlen(word);
633 words = sl_init();
634
635 /* count the entries in the smoptstbl and groups (alias) tables */
636 s1 = 1;
637 s2 = 1;
638 for (h = 0; h < HSHSIZE; h++) {
639 for (sp = smoptstbl[h]; sp != NULL; sp = sp->s_link)
640 s1++;
641 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
642 s2++;
643 }
644
645 /* allocate sufficient space for the pointers */
646 ap = salloc(MAX(s1, s2) * sizeof(*ap));
647
648 /*
649 * First do the smoptstbl pointers. (case _insensitive_)
650 */
651 p = ap;
652 for (h = 0; h < HSHSIZE; h++)
653 for (sp = smoptstbl[h]; sp != NULL; sp = sp->s_link)
654 *p++ = sp->s_name;
655 *p = NULL;
656 sort(ap);
657 for (p = ap; *p != NULL; p++)
658 if (len == 0 || strncasecmp(*p, word, len) == 0)
659 mail_sl_add(words, estrdup(*p));
660
661 /*
662 * Now do the groups (alias) pointers. (case sensitive)
663 */
664 p = ap;
665 for (h = 0; h < HSHSIZE; h++)
666 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
667 *p++ = gh->g_name;
668 *p = NULL;
669 sort(ap);
670 for (p = ap; *p != NULL; p++)
671 if (len == 0 || strncmp(*p, word, len) == 0)
672 mail_sl_add(words, estrdup(*p));
673
674 rv = complete_ambiguous(el, word, dolist, words);
675
676 sl_free(words, 1);
677
678 return rv;
679 }
680
681
682 #ifdef THREAD_SUPPORT
683 static unsigned char
684 complete_thread_key(EditLine *el, char *word, int dolist)
685 {
686 const char **ap;
687 const char **p;
688 const char *name;
689 size_t len;
690 StringList *words;
691 unsigned char rv;
692 int cnt;
693 const void *cookie;
694
695 len = strlen(word);
696 words = sl_init();
697
698 /* count the entries in the table */
699 /* XXX - have a function return this rather than counting? */
700 cnt = 1; /* count the NULL terminator */
701 cookie = NULL;
702 while (thread_next_key_name(&cookie) != NULL)
703 cnt++;
704
705 /* allocate sufficient space for the pointers */
706 ap = salloc(cnt * sizeof(*ap));
707
708 /* load the array */
709 p = ap;
710 cookie = NULL;
711 while ((name = thread_next_key_name(&cookie)) != NULL)
712 *p++ = name;
713 *p = NULL;
714 sort(ap);
715 for (p = ap; *p != NULL; p++)
716 if (len == 0 || strncmp(*p, word, len) == 0)
717 mail_sl_add(words, estrdup(*p));
718
719 rv = complete_ambiguous(el, word, dolist, words);
720
721 sl_free(words, 1);
722
723 return rv;
724 }
725 #endif /* THREAD_SUPPORT */
726
727 /* from /usr/src/usr.bin/ftp/main.c(1.101) - end */
728 /************************************************************************/
729
730 /* Some people like to bind file completion to CTRL-D. In emacs mode,
731 * CTRL-D is also used to delete the current character, we have to
732 * special case this situation.
733 */
734 #define EMACS_CTRL_D_BINDING_HACK
735
736 #ifdef EMACS_CTRL_D_BINDING_HACK
737 static int
738 is_emacs_mode(EditLine *el)
739 {
740 char *mode;
741 if (el_get(el, EL_EDITOR, &mode) == -1)
742 return 0;
743 return equal(mode, "emacs");
744 }
745
746 static int
747 emacs_ctrl_d(EditLine *el, const LineInfo *lf, int ch)
748 {
749 static char delunder[3] = { CTRL('f'), CTRL('h'), '\0' };
750 if (ch == CTRL('d') && is_emacs_mode(el)) { /* CTRL-D is special */
751 if (lf->buffer == lf->lastchar)
752 return CC_EOF;
753 if (lf->cursor != lf->lastchar) { /* delete without using ^D */
754 el_push(el, delunder); /* ^F^H */
755 return CC_NORM;
756 }
757 }
758 return -1;
759 }
760 #endif /* EMACS_CTRL_D_BINDING_HACK */
761
762 /*
763 * Check if this is the second request made for this line indicating
764 * the need to list all the completion possibilities.
765 */
766 static int
767 get_dolist(const LineInfo *lf)
768 {
769 static char last_line[LINESIZE];
770 static char *last_cursor_pos;
771 char *cursor_pos;
772 int dolist;
773 size_t len;
774
775 len = lf->lastchar - lf->buffer;
776 if (len >= sizeof(last_line) - 1)
777 return -1;
778
779 cursor_pos = last_line + (lf->cursor - lf->buffer);
780 dolist =
781 cursor_pos == last_cursor_pos &&
782 strncmp(last_line, lf->buffer, len) == 0;
783
784 (void)strlcpy(last_line, lf->buffer, len + 1);
785 last_cursor_pos = cursor_pos;
786
787 return dolist;
788 }
789
790 /*
791 * Take the full line (lf) including the command and split it into a
792 * sub-line (returned) and a completion context (cmplarray).
793 */
794 static LineInfo *
795 split_line(const char **cmplarray, const LineInfo *lf)
796 {
797 static LineInfo li;
798 const struct cmd *c;
799 char *cmdname;
800 char line[LINESIZE];
801 char *cp;
802 size_t len;
803
804 len = lf->cursor - lf->buffer;
805 if (len + 1 > sizeof(line))
806 return NULL;
807
808 (void)strlcpy(line, lf->buffer, len + 1);
809
810 li.cursor = line + len;
811 li.lastchar = line + len;
812
813 cp = skip_WSP(line);
814 cmdname = get_cmdname(cp);
815 cp += strlen(cmdname);
816
817 if (cp == li.cursor) {
818 *cmplarray = "c";
819 li.buffer = cmdname;
820 return &li;
821 }
822
823 c = lex(cmdname);
824 if (c == NULL)
825 return NULL;
826
827 *cmplarray = c->c_complete;
828 if (c->c_pipe) {
829 char *cp2;
830 if ((cp2 = shellpr(cp)) != NULL) {
831 cp = cp2;
832 # define XX(a) ((a) + ((a)[1] == '>' ? 2 : 1))
833 while ((cp2 = shellpr(XX(cp))) != NULL)
834 cp = cp2;
835
836 if (*cp == '|') {
837 *cmplarray = "xF";
838 cp = skip_WSP(cp + 1);
839 }
840 else {
841 assert(*cp == '>');
842 cp = skip_WSP(XX(cp));
843 *cmplarray = "f";
844 }
845 # undef XX
846 }
847 }
848 li.buffer = cp;
849 return &li;
850 }
851
852 /*
853 * Split a sub-line and a completion context into a word and a
854 * completion type. Use the editline tokenizer to handle the quoting
855 * and splitting.
856 */
857 static char *
858 split_word(int *cmpltype, const char *cmplarray, LineInfo *li)
859 {
860 static Tokenizer *t = NULL;
861 const char **argv;
862 char *word;
863 int argc;
864 int cursorc;
865 int cursoro;
866 int arraylen;
867
868 if (t != NULL)
869 tok_reset(t);
870 else {
871 if ((t = tok_init(NULL)) == NULL)
872 err(EXIT_FAILURE, "tok_init");
873 }
874 if (tok_line(t, li, &argc, &argv, &cursorc, &cursoro) == -1)
875 err(EXIT_FAILURE, "tok_line");
876
877 if (cursorc >= argc)
878 word = __UNCONST("");
879 else {
880 word = salloc((size_t)cursoro + 1);
881 (void)strlcpy(word, argv[cursorc], (size_t)cursoro + 1);
882 }
883
884 /* check for 'continuation' completes (which are uppercase) */
885 arraylen = strlen(cmplarray);
886 if (cursorc >= arraylen &&
887 arraylen > 0 &&
888 isupper((unsigned char)cmplarray[arraylen - 1]))
889 cursorc = arraylen - 1;
890
891 if (cursorc >= arraylen)
892 return NULL;
893
894 *cmpltype = cmplarray[cursorc];
895 return word;
896 }
897
898 /*
899 * A generic complete routine for the mail command line.
900 */
901 static unsigned char
902 mail_complete(EditLine *el, int ch)
903 {
904 LineInfo *li;
905 const LineInfo *lf;
906 const char *cmplarray;
907 int dolist;
908 int cmpltype;
909 char *word;
910
911 lf = el_line(el);
912
913 #ifdef EMACS_CTRL_D_BINDING_HACK
914 {
915 int cc_ret;
916 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
917 return cc_ret;
918 }
919 #endif /* EMACS_CTRL_D_BINDING_HACK */
920
921 if ((dolist = get_dolist(lf)) == -1)
922 return CC_ERROR;
923
924 if ((li = split_line(&cmplarray, lf)) == NULL)
925 return CC_ERROR;
926
927 if ((word = split_word(&cmpltype, cmplarray, li)) == NULL)
928 return CC_ERROR;
929
930 switch (cmpltype) {
931 case 'a': /* alias complete */
932 case 'A':
933 return complete_alias(el, word, dolist);
934
935 case 'c': /* command complete */
936 case 'C':
937 return complete_command(el, word, dolist);
938
939 case 'f': /* filename complete */
940 case 'F':
941 return complete_filename(el, word, dolist);
942
943 case 'm':
944 case 'M':
945 return complete_smopts(el, word, dolist);
946
947 case 'n': /* no complete */
948 case 'N': /* no complete */
949 return CC_ERROR;
950
951 case 's':
952 case 'S':
953 return complete_set(el, word, dolist);
954 #ifdef THREAD_SUPPORT
955 case 't':
956 case 'T':
957 return complete_thread_key(el, word, dolist);
958 #endif
959 case 'x': /* executable complete */
960 case 'X':
961 return complete_executable(el, word, dolist);
962
963 default:
964 warnx("unknown complete type `%c'", cmpltype);
965 #if 0
966 assert(/*CONSTCOND*/0);
967 #endif
968 return CC_ERROR;
969 }
970 /* NOTREACHED */
971 }
972
973
974 /*
975 * A generic file completion routine.
976 */
977 static unsigned char
978 file_complete(EditLine *el, int ch)
979 {
980 static char word[LINESIZE];
981 const LineInfo *lf;
982 size_t word_len;
983 int dolist;
984
985 lf = el_line(el);
986
987 #ifdef EMACS_CTRL_D_BINDING_HACK
988 {
989 int cc_ret;
990 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
991 return cc_ret;
992 }
993 #endif /* EMACS_CTRL_D_BINDING_HACK */
994
995 word_len = lf->cursor - lf->buffer;
996 if (word_len + 1 > sizeof(word))
997 return CC_ERROR;
998
999 (void)strlcpy(word, lf->buffer, word_len + 1); /* do not use estrlcpy here! */
1000
1001 if ((dolist = get_dolist(lf)) == -1)
1002 return CC_ERROR;
1003
1004 return complete_filename(el, word, dolist);
1005 }
1006
1007
1008 #ifdef MIME_SUPPORT
1009 /*
1010 * Complete mime_transfer_encoding type.
1011 */
1012 static unsigned char
1013 mime_enc_complete(EditLine *el, int ch)
1014 {
1015 static char word[LINESIZE];
1016 StringList *words;
1017 unsigned char rv;
1018 const LineInfo *lf;
1019 size_t word_len;
1020 int dolist;
1021
1022 lf = el_line(el);
1023
1024 #ifdef EMACS_CTRL_D_BINDING_HACK
1025 {
1026 int cc_ret;
1027 if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
1028 return cc_ret;
1029 }
1030 #endif /* EMACS_CTRL_D_BINDING_HACK */
1031
1032 word_len = lf->cursor - lf->buffer;
1033 if (word_len >= sizeof(word) - 1)
1034 return CC_ERROR;
1035
1036 words = mail_sl_init();
1037 {
1038 const char *ename;
1039 const void *cookie;
1040 cookie = NULL;
1041 for (ename = mime_next_encoding_name(&cookie);
1042 ename;
1043 ename = mime_next_encoding_name(&cookie))
1044 if (word_len == 0 ||
1045 strncmp(lf->buffer, ename, word_len) == 0) {
1046 char *cp;
1047 cp = estrdup(ename);
1048 mail_sl_add(words, cp);
1049 }
1050 }
1051 (void)strlcpy(word, lf->buffer, word_len + 1);
1052
1053 if ((dolist = get_dolist(lf)) == -1)
1054 return CC_ERROR;
1055
1056 rv = complete_ambiguous(el, word, dolist, words);
1057
1058 sl_free(words, 1);
1059 return rv;
1060 }
1061 #endif /* MIME_SUPPORT */
1062
1063
1064 /*************************************************************************
1065 * Our public interface to el_gets():
1066 *
1067 * init_editline()
1068 * Initializes of all editline and completion data strutures.
1069 *
1070 * my_gets()
1071 * Returns the next line of input as a NULL termnated string without
1072 * the trailing newline.
1073 *
1074 * my_getline()
1075 * Same as my_gets(), but strips leading and trailing whitespace
1076 * and returns an empty line if it gets a SIGINT.
1077 */
1078
1079 static const char *el_prompt;
1080
1081 /*ARGSUSED*/
1082 static const char *
1083 show_prompt(EditLine *e __unused)
1084 {
1085 return el_prompt;
1086 }
1087
1088 PUBLIC char *
1089 my_gets(el_mode_t *em, const char *prompt, char *string)
1090 {
1091 int cnt;
1092 size_t len;
1093 const char *buf;
1094 HistEvent ev;
1095 static char line[LINE_MAX];
1096
1097 el_prompt = prompt;
1098
1099 if (string)
1100 el_push(em->el, string);
1101
1102 buf = el_gets(em->el, &cnt);
1103
1104 if (buf == NULL || cnt <= 0) {
1105 if (cnt == 0)
1106 (void)putc('\n', stdout);
1107 return NULL;
1108 }
1109
1110 if (buf[cnt - 1] == '\n')
1111 cnt--; /* trash the trailing LF */
1112 len = MIN(sizeof(line) - 1, (size_t)cnt);
1113 (void)memcpy(line, buf, len);
1114 line[cnt] = '\0';
1115
1116 /* enter non-empty lines into history */
1117 if (em->hist) {
1118 const char *p;
1119 p = skip_WSP(line);
1120 if (*p && history(em->hist, &ev, H_ENTER, line) == 0)
1121 (void)printf("Failed history entry: %s", line);
1122 }
1123 return line;
1124 }
1125
1126 #ifdef MIME_SUPPORT
1127 /* XXX - do we really want this here? */
1128
1129 static jmp_buf intjmp;
1130 /*ARGSUSED*/
1131 static void
1132 sigint(int signum __unused)
1133 {
1134 siglongjmp(intjmp, 1);
1135 }
1136
1137 PUBLIC char *
1138 my_getline(el_mode_t *em, const char *prompt, const char *str)
1139 {
1140 sig_t saveint;
1141 char *cp;
1142 char *line;
1143
1144 saveint = signal(SIGINT, sigint);
1145 if (sigsetjmp(intjmp, 1)) {
1146 (void)signal(SIGINT, saveint);
1147 (void)putc('\n', stdout);
1148 return __UNCONST("");
1149 }
1150
1151 line = my_gets(em, prompt, __UNCONST(str));
1152 /* LINTED */
1153 line = line ? savestr(line) : __UNCONST("");
1154
1155 (void)signal(SIGINT, saveint);
1156
1157 /* strip trailing white space */
1158 for (cp = line + strlen(line) - 1;
1159 cp >= line && is_WSP(*cp); cp--)
1160 *cp = '\0';
1161
1162 /* skip leading white space */
1163 cp = skip_WSP(line);
1164
1165 return cp;
1166 }
1167 #endif /* MIME_SUPPORT */
1168
1169 static el_mode_t
1170 init_el_mode(
1171 const char *el_editor,
1172 unsigned char (*completer)(EditLine *, int),
1173 struct name *keys,
1174 int history_size)
1175 {
1176 FILE *nullfp;
1177 el_mode_t em;
1178
1179 (void)memset(&em, 0, sizeof(em));
1180
1181 if ((nullfp = fopen(_PATH_DEVNULL, "w")) == NULL)
1182 err(EXIT_FAILURE, "Cannot open `%s'", _PATH_DEVNULL);
1183
1184 if ((em.el = el_init(getprogname(), stdin, stdout, nullfp)) == NULL) {
1185 warn("el_init");
1186 return em;
1187 }
1188 (void)fflush(nullfp);
1189 (void)dup2(STDERR_FILENO, fileno(nullfp));
1190
1191 (void)el_set(em.el, EL_PROMPT, show_prompt);
1192 (void)el_set(em.el, EL_SIGNAL, 1); /* editline handles the signals. */
1193
1194 if (el_editor)
1195 (void)el_set(em.el, EL_EDITOR, el_editor);
1196
1197 if (completer) {
1198 struct name *np;
1199 (void)el_set(em.el, EL_ADDFN, "mail-complete",
1200 "Context sensitive argument completion", completer);
1201 for (np = keys; np; np = np->n_flink)
1202 (void)el_set(em.el, EL_BIND, np->n_name,
1203 "mail-complete", NULL);
1204 }
1205
1206 if (history_size) {
1207 HistEvent ev;
1208 if ((em.hist = history_init()) == NULL) {
1209 warn("history_init");
1210 return em;
1211 }
1212 if (history(em.hist, &ev, H_SETSIZE, history_size) == -1)
1213 (void)printf("history: %s\n", ev.str);
1214 (void)el_set(em.el, EL_HIST, history, em.hist);
1215 }
1216
1217 (void)el_source(em.el, NULL); /* read ~/.editrc */
1218
1219 return em;
1220 }
1221
1222
1223 struct el_modes_s elm = {
1224 .command = { .el = NULL, .hist = NULL, },
1225 .string = { .el = NULL, .hist = NULL, },
1226 .filec = { .el = NULL, .hist = NULL, },
1227 #ifdef MIME_SUPPORT
1228 .mime_enc = { .el = NULL, .hist = NULL, },
1229 #endif
1230 };
1231
1232 PUBLIC void
1233 init_editline(void)
1234 {
1235 const char *mode;
1236 int hist_size;
1237 struct name *keys;
1238 char *cp;
1239
1240 mode = value(ENAME_EL_EDITOR);
1241
1242 cp = value(ENAME_EL_HISTORY_SIZE);
1243 hist_size = cp ? atoi(cp) : 0;
1244
1245 cp = value(ENAME_EL_COMPLETION_KEYS);
1246 keys = cp && *cp ? lexpand(cp, 0) : NULL;
1247
1248 elm.command = init_el_mode(mode, mail_complete, keys, hist_size);
1249 elm.filec = init_el_mode(mode, file_complete, keys, 0);
1250 elm.string = init_el_mode(mode, NULL, NULL, 0);
1251 #ifdef MIME_SUPPORT
1252 elm.mime_enc = init_el_mode(mode, mime_enc_complete, keys, 0);
1253 #endif
1254 return;
1255 }
1256
1257 #endif /* USE_EDITLINE */
1258