xref: /netbsd-src/usr.bin/mail/complete.c (revision 56a34939419542e88b386b2229be7565f4f45461)
1 /*	$NetBSD: complete.c,v 1.15 2008/04/28 20:24:14 martin 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.15 2008/04/28 20:24:14 martin 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 <util.h>
58 
59 #include <sys/param.h>
60 #include <sys/stat.h>
61 
62 #include "rcv.h"			/* includes "glob.h" */
63 #include "extern.h"
64 #include "complete.h"
65 #ifdef MIME_SUPPORT
66 #include "mime.h"
67 #endif
68 #ifdef THREAD_SUPPORT
69 #include "thread.h"
70 #endif
71 
72 #define BELL 0x7
73 
74 /*
75  * Global variables
76  */
77 static int doglob = 1;			/* glob local file names */
78 
79 #define ttyout stdout
80 #define ttywidth screenwidth		/* in "glob.h" */
81 #define ttyheight screenheight		/* in "glob.h" */
82 
83 /************************************************************************/
84 /* from src/usr.bin/ftp/utils.h (1.135) - begin */
85 
86 /*
87  * List words in stringlist, vertically arranged
88  */
89 static void
90 list_vertical(StringList *sl)
91 {
92 	int i, j, k;
93 	int columns, lines;
94 	char *p;
95 	size_t w, width;
96 
97 	width = 0;
98 
99 	for (i = 0; i < sl->sl_cur; i++) {
100 		w = strlen(sl->sl_str[i]);
101 		if (w > width)
102 			width = w;
103 	}
104 	width = (width + 8) &~ 7;
105 
106 	columns = ttywidth / width;
107 	if (columns == 0)
108 		columns = 1;
109 	lines = (sl->sl_cur + columns - 1) / columns;
110 	k = 0;
111 	for (i = 0; i < lines; i++) {
112 		for (j = 0; j < columns; j++) {
113 			p = sl->sl_str[j * lines + i];
114 			if (p)
115 				(void)fputs(p, ttyout);
116 			if (j * lines + i + lines >= sl->sl_cur) {
117 				(void)putc('\n', ttyout);
118 				break;
119 			}
120 			if (p) {
121 				w = strlen(p);
122 				while (w < width) {
123 					w = (w + 8) &~ 7;
124 					(void)putc('\t', ttyout);
125 				}
126 			}
127 		}
128 		if (ttyheight > 2 && ++k == ttyheight - 2) {
129 			int ch;
130 			k = 0;
131 			(void)fputs("--more--", ttyout);
132 			while ((ch = getchar()) != EOF && ch != ' ' && ch != 'q')
133 				(void)putc(BELL, ttyout);
134 			(void)fputs("\r        \r", ttyout);
135 			if (ch == 'q')
136 				break;
137 		}
138 	}
139 }
140 
141 /*
142  * Copy characters from src into dst, \ quoting characters that require it
143  */
144 static void
145 ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
146 {
147 	int	di, si;
148 
149 	for (di = si = 0;
150 	    src[si] != '\0' && di < dstlen && si < srclen;
151 	    di++, si++) {
152 		switch (src[si]) {
153 		case '\\':
154 		case ' ':
155 		case '\t':
156 		case '\r':
157 		case '\n':
158 		case '"':
159 			dst[di++] = '\\';
160 			if (di >= dstlen)
161 				break;
162 			/* FALLTHROUGH */
163 		default:
164 			dst[di] = src[si];
165 		}
166 	}
167 	dst[di] = '\0';
168 }
169 
170 /*
171  * sl_init() with inbuilt error checking
172  */
173 static StringList *
174 mail_sl_init(void)
175 {
176 	StringList *p;
177 
178 	p = sl_init();
179 	if (p == NULL)
180 		err(1, "Unable to allocate memory for stringlist");
181 	return p;
182 }
183 
184 
185 /*
186  * sl_add() with inbuilt error checking
187  */
188 static void
189 mail_sl_add(StringList *sl, char *i)
190 {
191 
192 	if (sl_add(sl, i) == -1)
193 		err(1, "Unable to add `%s' to stringlist", i);
194 }
195 
196 
197 /*
198  * Glob a local file name specification with the expectation of a single
199  * return value. Can't control multiple values being expanded from the
200  * expression, we return only the first.
201  * Returns NULL on error, or a pointer to a buffer containing the filename
202  * that's the caller's responsiblity to free(3) when finished with.
203  */
204 static char *
205 globulize(const char *pattern)
206 {
207 	glob_t gl;
208 	int flags;
209 	char *p;
210 
211 	if (!doglob)
212 		return estrdup(pattern);
213 
214 	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
215 	(void)memset(&gl, 0, sizeof(gl));
216 	if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) {
217 		warnx("%s: not found", pattern);
218 		globfree(&gl);
219 		return NULL;
220 	}
221 	p = estrdup(gl.gl_pathv[0]);
222 	globfree(&gl);
223 	return p;
224 }
225 
226 /* from src/usr.bin/ftp/utils.h (1.135) - end */
227 /************************************************************************/
228 
229 static int
230 comparstr(const void *a, const void *b)
231 {
232 	return strcmp(*(const char * const *)a, *(const char * const *)b);
233 }
234 
235 /*
236  * Determine if complete is ambiguous. If unique, insert.
237  * If no choices, error. If unambiguous prefix, insert that.
238  * Otherwise, list choices. words is assumed to be filtered
239  * to only contain possible choices.
240  * Args:
241  *	word	word which started the match
242  *	dolist	list by default
243  *	words	stringlist containing possible matches
244  * Returns a result as per el_set(EL_ADDFN, ...)
245  */
246 static unsigned char
247 complete_ambiguous(EditLine *el, char *word, int dolist, StringList *words)
248 {
249 	char insertstr[MAXPATHLEN];
250 	char *lastmatch, *p;
251 	int i, j;
252 	size_t 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, 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 	el_mode_t em;
1177 	(void)memset(&em, 0, sizeof(em));
1178 
1179 	if ((em.el = el_init(getprogname(), stdin, stdout, stderr)) == NULL) {
1180 		warn("el_init");
1181 		return em;
1182 	}
1183 
1184 	(void)el_set(em.el, EL_PROMPT, show_prompt);
1185 	(void)el_set(em.el, EL_SIGNAL, 1); /* editline handles the signals. */
1186 
1187 	if (el_editor)
1188 		(void)el_set(em.el, EL_EDITOR, el_editor);
1189 
1190 	if (completer) {
1191 		struct name *np;
1192 		(void)el_set(em.el, EL_ADDFN, "mail-complete",
1193 		    "Context sensitive argument completion", completer);
1194 		for (np = keys; np; np = np->n_flink)
1195 			(void)el_set(em.el, EL_BIND, np->n_name,
1196 			    "mail-complete", NULL);
1197 	}
1198 
1199 	if (history_size) {
1200 		HistEvent ev;
1201 		if ((em.hist = history_init()) == NULL) {
1202 			warn("history_init");
1203 			return em;
1204 		}
1205 		if (history(em.hist, &ev, H_SETSIZE, history_size) == -1)
1206 			(void)printf("history: %s\n", ev.str);
1207 		(void)el_set(em.el, EL_HIST, history, em.hist);
1208 	}
1209 
1210 	(void)el_source(em.el, NULL);		/* read ~/.editrc */
1211 
1212 	return em;
1213 }
1214 
1215 
1216 struct el_modes_s elm = {
1217 	.command  = { .el = NULL, .hist = NULL, },
1218 	.string   = { .el = NULL, .hist = NULL, },
1219 	.filec    = { .el = NULL, .hist = NULL, },
1220 #ifdef MIME_SUPPORT
1221 	.mime_enc = { .el = NULL, .hist = NULL, },
1222 #endif
1223 };
1224 
1225 PUBLIC void
1226 init_editline(void)
1227 {
1228 	const char *mode;
1229 	int hist_size;
1230 	struct name *keys;
1231 	char *cp;
1232 
1233 	mode = value(ENAME_EL_EDITOR);
1234 
1235 	cp = value(ENAME_EL_HISTORY_SIZE);
1236 	hist_size = cp ? atoi(cp) : 0;
1237 
1238 	cp = value(ENAME_EL_COMPLETION_KEYS);
1239 	keys = cp && *cp ? lexpand(cp, 0) : NULL;
1240 
1241 	elm.command  = init_el_mode(mode, mail_complete,     keys, hist_size);
1242 	elm.filec    = init_el_mode(mode, file_complete,     keys, 0);
1243 	elm.string   = init_el_mode(mode, NULL,              NULL, 0);
1244 #ifdef MIME_SUPPORT
1245 	elm.mime_enc = init_el_mode(mode, mime_enc_complete, keys, 0);
1246 #endif
1247 	return;
1248 }
1249 
1250 #endif /* USE_EDITLINE */
1251