xref: /netbsd/usr.bin/mail/complete.c (revision 6550d01e)
1 /*	$NetBSD: complete.c,v 1.20 2010/01/12 14:44:24 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 
38 #include <sys/cdefs.h>
39 #ifndef lint
40 __RCSID("$NetBSD: complete.c,v 1.20 2010/01/12 14:44:24 christos Exp $");
41 #endif /* not lint */
42 
43 /*
44  * FTP user program - command and file completion routines
45  */
46 
47 #include <assert.h>
48 #include <ctype.h>
49 #include <err.h>
50 #include <dirent.h>
51 #include <glob.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <stringlist.h>
56 #include <termcap.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 #include "sig.h"
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(EXIT_FAILURE, "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(EXIT_FAILURE, "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 
742 	if (el_get(el, EL_EDITOR, &mode) == -1)
743 		return 0;
744 	return equal(mode, "emacs");
745 }
746 
747 static int
748 emacs_ctrl_d(EditLine *el, const LineInfo *lf, int ch)
749 {
750 	static char delunder[3] = { CTRL('f'), CTRL('h'), '\0' };
751 
752 	if (ch == CTRL('d') && is_emacs_mode(el)) {	/* CTRL-D is special */
753 		if (lf->buffer == lf->lastchar)
754 			return CC_EOF;
755 		if (lf->cursor != lf->lastchar) { /* delete without using ^D */
756 			el_push(el, delunder); /* ^F^H */
757 			return CC_NORM;
758 		}
759 	}
760 	return -1;
761 }
762 #endif /* EMACS_CTRL_D_BINDING_HACK */
763 
764 /*
765  * Check if this is the second request made for this line indicating
766  * the need to list all the completion possibilities.
767  */
768 static int
769 get_dolist(const LineInfo *lf)
770 {
771 	static char last_line[LINESIZE];
772 	static char *last_cursor_pos;
773 	char *cursor_pos;
774 	int dolist;
775 	size_t len;
776 
777 	len = lf->lastchar - lf->buffer;
778 	if (len >= sizeof(last_line) - 1)
779 		return -1;
780 
781 	cursor_pos = last_line + (lf->cursor - lf->buffer);
782 	dolist =
783 	    cursor_pos == last_cursor_pos &&
784 	    strncmp(last_line, lf->buffer, len) == 0;
785 
786 	(void)strlcpy(last_line, lf->buffer, len + 1);
787 	last_cursor_pos = cursor_pos;
788 
789 	return dolist;
790 }
791 
792 /*
793  * Take the full line (lf) including the command and split it into a
794  * sub-line (returned) and a completion context (cmplarray).
795  */
796 static LineInfo *
797 split_line(const char **cmplarray, const LineInfo *lf)
798 {
799 	static LineInfo li;
800 	const struct cmd *c;
801 	char *cmdname;
802 	char line[LINESIZE];
803 	char *cp;
804 	size_t len;
805 
806 	len = lf->cursor - lf->buffer;
807 	if (len + 1 > sizeof(line))
808 		return NULL;
809 
810 	(void)strlcpy(line, lf->buffer, len + 1);
811 
812 	li.cursor   = line + len;
813 	li.lastchar = line + len;
814 
815 	cp = skip_WSP(line);
816 	cmdname = get_cmdname(cp);
817 	cp += strlen(cmdname);
818 
819 	if (cp == li.cursor) {
820 		*cmplarray = "c";
821 		li.buffer = cmdname;
822 		return &li;
823 	}
824 
825 	c = lex(cmdname);
826 	if (c == NULL)
827 		return NULL;
828 
829 	*cmplarray = c->c_complete;
830 	if (c->c_pipe) {
831 		char *cp2;
832 		if ((cp2 = shellpr(cp)) != NULL) {
833 			cp = cp2;
834 # define XX(a)  ((a) + ((a)[1] == '>' ? 2 : 1))
835 			while ((cp2 = shellpr(XX(cp))) != NULL)
836 				cp = cp2;
837 
838 			if (*cp == '|') {
839 				*cmplarray = "xF";
840 				cp = skip_WSP(cp + 1);
841 			}
842 			else {
843 				assert(*cp == '>');
844 				cp = skip_WSP(XX(cp));
845 				*cmplarray = "f";
846 			}
847 # undef XX
848 		}
849 	}
850 	li.buffer = cp;
851 	return &li;
852 }
853 
854 /*
855  * Split a sub-line and a completion context into a word and a
856  * completion type.  Use the editline tokenizer to handle the quoting
857  * and splitting.
858  */
859 static char *
860 split_word(int *cmpltype, const char *cmplarray, LineInfo *li)
861 {
862 	static Tokenizer *t = NULL;
863 	const char **argv;
864 	char *word;
865 	int argc;
866 	int cursorc;
867 	int cursoro;
868 	int arraylen;
869 
870 	if (t != NULL)
871 		tok_reset(t);
872 	else {
873 		if ((t = tok_init(NULL)) == NULL)
874 			err(EXIT_FAILURE, "tok_init");
875 	}
876 	if (tok_line(t, li, &argc, &argv, &cursorc, &cursoro) == -1)
877 		err(EXIT_FAILURE, "tok_line");
878 
879 	if (cursorc >= argc)
880 		word = __UNCONST("");
881 	else {
882 		word = salloc((size_t)cursoro + 1);
883 		(void)strlcpy(word, argv[cursorc], (size_t)cursoro + 1);
884 	}
885 
886 	/* check for 'continuation' completes (which are uppercase) */
887 	arraylen = (int)strlen(cmplarray);
888 	if (cursorc >= arraylen &&
889 	    arraylen > 0 &&
890 	    isupper((unsigned char)cmplarray[arraylen - 1]))
891 		cursorc = arraylen - 1;
892 
893 	if (cursorc >= arraylen)
894 		return NULL;
895 
896 	*cmpltype = cmplarray[cursorc];
897 	return word;
898 }
899 
900 /*
901  * A generic complete routine for the mail command line.
902  */
903 static unsigned char
904 mail_complete(EditLine *el, int ch)
905 {
906 	LineInfo *li;
907 	const LineInfo *lf;
908 	const char *cmplarray;
909 	int dolist;
910 	int cmpltype;
911 	char *word;
912 
913 	lf = el_line(el);
914 
915 #ifdef EMACS_CTRL_D_BINDING_HACK
916 	{
917 		int cc_ret;
918 		if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
919 			return cc_ret;
920 	}
921 #endif /* EMACS_CTRL_D_BINDING_HACK */
922 
923 	if ((dolist = get_dolist(lf)) == -1)
924 		return CC_ERROR;
925 
926 	if ((li = split_line(&cmplarray, lf)) == NULL)
927 		return CC_ERROR;
928 
929 	if ((word = split_word(&cmpltype, cmplarray, li)) == NULL)
930 		return CC_ERROR;
931 
932 	switch (cmpltype) {
933 	case 'a':			/* alias complete */
934 	case 'A':
935 		return complete_alias(el, word, dolist);
936 
937 	case 'c':			/* command complete */
938 	case 'C':
939 		return complete_command(el, word, dolist);
940 
941 	case 'f':			/* filename complete */
942 	case 'F':
943 		return complete_filename(el, word, dolist);
944 
945 	case 'm':
946 	case 'M':
947 		return complete_smopts(el, word, dolist);
948 
949 	case 'n':			/* no complete */
950 	case 'N':			/* no complete */
951 		return CC_ERROR;
952 
953 	case 's':
954 	case 'S':
955 		return complete_set(el, word, dolist);
956 #ifdef THREAD_SUPPORT
957 	case 't':
958 	case 'T':
959 		return complete_thread_key(el, word, dolist);
960 #endif
961 		case 'x':			/* executable complete */
962 	case 'X':
963 		return complete_executable(el, word, dolist);
964 
965 	default:
966 		warnx("unknown complete type `%c'", cmpltype);
967 #if 0
968 		assert(/*CONSTCOND*/0);
969 #endif
970 		return CC_ERROR;
971 	}
972 	/* NOTREACHED */
973 }
974 
975 
976 /*
977  * A generic file completion routine.
978  */
979 static unsigned char
980 file_complete(EditLine *el, int ch)
981 {
982 	static char word[LINESIZE];
983 	const LineInfo *lf;
984 	size_t word_len;
985 	int dolist;
986 
987 	lf = el_line(el);
988 
989 #ifdef EMACS_CTRL_D_BINDING_HACK
990 	{
991 		int cc_ret;
992 		if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
993 			return cc_ret;
994 	}
995 #endif /* EMACS_CTRL_D_BINDING_HACK */
996 
997 	word_len = lf->cursor - lf->buffer;
998 	if (word_len + 1 > sizeof(word))
999 		return CC_ERROR;
1000 
1001 	(void)strlcpy(word, lf->buffer, word_len + 1);	/* do not use estrlcpy here! */
1002 
1003 	if ((dolist = get_dolist(lf)) == -1)
1004 		return CC_ERROR;
1005 
1006 	return complete_filename(el, word, dolist);
1007 }
1008 
1009 
1010 #ifdef MIME_SUPPORT
1011 /*
1012  * Complete mime_transfer_encoding type.
1013  */
1014 static unsigned char
1015 mime_enc_complete(EditLine *el, int ch)
1016 {
1017 	static char word[LINESIZE];
1018 	StringList *words;
1019 	unsigned char rv;
1020 	const LineInfo *lf;
1021 	size_t word_len;
1022 	int dolist;
1023 
1024 	lf = el_line(el);
1025 
1026 #ifdef EMACS_CTRL_D_BINDING_HACK
1027 	{
1028 		int cc_ret;
1029 		if ((cc_ret = emacs_ctrl_d(el, lf, ch)) != -1)
1030 			return cc_ret;
1031 	}
1032 #endif /* EMACS_CTRL_D_BINDING_HACK */
1033 
1034 	word_len = lf->cursor - lf->buffer;
1035 	if (word_len >= sizeof(word) - 1)
1036 		return CC_ERROR;
1037 
1038 	words = mail_sl_init();
1039 	{
1040 		const char *ename;
1041 		const void *cookie;
1042 		cookie = NULL;
1043 		for (ename = mime_next_encoding_name(&cookie);
1044 		     ename;
1045 		     ename = mime_next_encoding_name(&cookie))
1046 			if (word_len == 0 ||
1047 			    strncmp(lf->buffer, ename, word_len) == 0) {
1048 				char *cp;
1049 				cp = estrdup(ename);
1050 				mail_sl_add(words, cp);
1051 			}
1052 	}
1053 	(void)strlcpy(word, lf->buffer, word_len + 1);
1054 
1055 	if ((dolist = get_dolist(lf)) == -1)
1056 		return CC_ERROR;
1057 
1058 	rv = complete_ambiguous(el, word, dolist, words);
1059 
1060 	sl_free(words, 1);
1061 	return rv;
1062 }
1063 #endif /* MIME_SUPPORT */
1064 
1065 
1066 /*************************************************************************
1067  * Our public interface to el_gets():
1068  *
1069  * init_editline()
1070  *    Initializes of all editline and completion data strutures.
1071  *
1072  * my_gets()
1073  *    Displays prompt, calls el_gets() and deals with history.
1074  *    Returns the next line of input as a NULL termnated string
1075  *    without the trailing newline, or NULL if el_gets() sees is an
1076  *    error or signal.
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 /*
1089  * Write the current INTR character to fp in a friendly form.
1090  */
1091 static void
1092 echo_INTR(void *p)
1093 {
1094 	struct termios ttybuf;
1095 	char buf[5];
1096 	FILE *fp;
1097 
1098 	fp = p;
1099 	if (tcgetattr(fileno(stdin), &ttybuf) == -1)
1100 		warn("tcgetattr");
1101 	else {
1102 		(void)vis(buf, ttybuf.c_cc[VINTR], VIS_SAFE | VIS_NOSLASH, 0);
1103 		(void)fprintf(fp, "%s", buf);
1104 		(void)fflush(fp);
1105 	}
1106 }
1107 
1108 static sig_t old_sigint;
1109 static void
1110 comp_intr(int signo)
1111 {
1112 
1113 	echo_INTR(stdout);
1114 	old_sigint(signo);
1115 }
1116 
1117 PUBLIC char *
1118 my_gets(el_mode_t *em, const char *prompt, char *string)
1119 {
1120 	static char line[LINE_MAX];
1121 	size_t len;
1122 	int cnt;
1123 	const char *buf;
1124 	HistEvent ev;
1125 
1126 	sig_check();
1127 
1128 	el_prompt = prompt;
1129 	if (string)
1130 		el_push(em->el, string);
1131 
1132 	/*
1133 	 * Let el_gets() deal with flow control.  Also, make sure we
1134 	 * output a ^C when we get a SIGINT as el_gets() doesn't echo
1135 	 * one.
1136 	 */
1137 	old_sigint = sig_signal(SIGINT, comp_intr);
1138 	buf = el_gets(em->el, &cnt);
1139 	(void)sig_signal(SIGINT, old_sigint);
1140 
1141 	if (buf == NULL) {
1142 		sig_check();
1143 		return NULL;
1144 	}
1145 
1146 	if (cnt > 0) {
1147 		if (buf[cnt - 1] == '\n')
1148 			cnt--;	/* trash the trailing LF */
1149 
1150 		len = MIN(sizeof(line) - 1, (size_t)cnt);
1151 		(void)memcpy(line, buf, len);
1152 	}
1153 	line[cnt] = '\0';
1154 
1155 	/* enter non-empty lines into history */
1156 	if (em->hist) {
1157 		const char *p;
1158 
1159 		p = skip_WSP(line);
1160 		if (*p && history(em->hist, &ev, H_ENTER, line) == 0)
1161 			(void)printf("Failed history entry: %s", line);
1162 	}
1163 	sig_check();
1164 	return line;
1165 }
1166 
1167 static el_mode_t
1168 init_el_mode(
1169 	const char *el_editor,
1170 	unsigned char (*completer)(EditLine *, int),
1171 	struct name *keys,
1172 	int history_size)
1173 {
1174 	FILE *nullfp;
1175 	el_mode_t em;
1176 
1177 	(void)memset(&em, 0, sizeof(em));
1178 
1179 	if ((nullfp = fopen(_PATH_DEVNULL, "w")) == NULL)
1180 		err(EXIT_FAILURE, "Cannot open `%s'", _PATH_DEVNULL);
1181 
1182 	if ((em.el = el_init(getprogname(), stdin, stdout, nullfp)) == NULL) {
1183 		warn("el_init");
1184 		return em;
1185 	}
1186 	(void)fflush(nullfp);
1187 	(void)dup2(STDERR_FILENO, fileno(nullfp));
1188 
1189 	(void)el_set(em.el, EL_PROMPT, show_prompt);
1190 	(void)el_set(em.el, EL_SIGNAL, 1); /* editline handles the signals. */
1191 
1192 	if (el_editor)
1193 		(void)el_set(em.el, EL_EDITOR, el_editor);
1194 
1195 	if (completer) {
1196 		struct name *np;
1197 		(void)el_set(em.el, EL_ADDFN, "mail-complete",
1198 		    "Context sensitive argument completion", completer);
1199 		for (np = keys; np; np = np->n_flink)
1200 			(void)el_set(em.el, EL_BIND, np->n_name,
1201 			    "mail-complete", NULL);
1202 	}
1203 
1204 	if (history_size) {
1205 		HistEvent ev;
1206 		if ((em.hist = history_init()) == NULL) {
1207 			warn("history_init");
1208 			return em;
1209 		}
1210 		if (history(em.hist, &ev, H_SETSIZE, history_size) == -1)
1211 			(void)printf("history: %s\n", ev.str);
1212 		(void)el_set(em.el, EL_HIST, history, em.hist);
1213 	}
1214 
1215 	(void)el_source(em.el, NULL);		/* read ~/.editrc */
1216 
1217 	return em;
1218 }
1219 
1220 
1221 struct el_modes_s elm = {
1222 	.command  = { .el = NULL, .hist = NULL, },
1223 	.string   = { .el = NULL, .hist = NULL, },
1224 	.filec    = { .el = NULL, .hist = NULL, },
1225 #ifdef MIME_SUPPORT
1226 	.mime_enc = { .el = NULL, .hist = NULL, },
1227 #endif
1228 };
1229 
1230 PUBLIC void
1231 init_editline(void)
1232 {
1233 	const char *mode;
1234 	int hist_size;
1235 	struct name *keys;
1236 	char *cp;
1237 
1238 	mode = value(ENAME_EL_EDITOR);
1239 
1240 	cp = value(ENAME_EL_HISTORY_SIZE);
1241 	hist_size = cp ? atoi(cp) : 0;
1242 
1243 	cp = value(ENAME_EL_COMPLETION_KEYS);
1244 	keys = cp && *cp ? lexpand(cp, 0) : NULL;
1245 
1246 	elm.command  = init_el_mode(mode, mail_complete,     keys, hist_size);
1247 	elm.filec    = init_el_mode(mode, file_complete,     keys, 0);
1248 	elm.string   = init_el_mode(mode, NULL,              NULL, 0);
1249 #ifdef MIME_SUPPORT
1250 	elm.mime_enc = init_el_mode(mode, mime_enc_complete, keys, 0);
1251 #endif
1252 	return;
1253 }
1254 
1255 #endif /* USE_EDITLINE */
1256