xref: /netbsd-src/usr.bin/mail/complete.c (revision b7ae68fde0d8ef1c03714e8bbb1ee7c6118ea93b)
1 /*	$NetBSD: complete.c,v 1.7 2006/09/27 15:23:34 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 1997-2000,2005 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  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *	This product includes software developed by the NetBSD
21  *	Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 /* Most of this is derived or copied from src/usr.bin/ftp/complete.c (1.41). */
40 
41 
42 #ifdef USE_READLINE
43 
44 #include <sys/cdefs.h>
45 #ifndef lint
46 __RCSID("$NetBSD: complete.c,v 1.7 2006/09/27 15:23:34 christos Exp $");
47 #endif /* not lint */
48 
49 /*
50  * FTP user program - command and file completion routines
51  */
52 
53 #include <sys/stat.h>
54 
55 #include <ctype.h>
56 #include <err.h>
57 #include <dirent.h>
58 #include <glob.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <histedit.h>
63 #include <sys/param.h>
64 #include <stringlist.h>
65 
66 #include "rcv.h"			/* includes "glob.h" */
67 #include "extern.h"
68 #include "complete.h"
69 
70 /*
71  * Global variables
72  */
73 static int doglob = 1;			/* glob local file names */
74 
75 #define ttyout stdout
76 #define ttywidth screenwidth		/* in "glob.h" */
77 #define ttyheight screenheight		/* in "glob.h" */
78 
79 
80 /* This should find the first command that matches the name given or
81  * NULL if none.  Use the routine from mail in lex.c.
82  */
83 #define getcmd(w) lex(w)
84 
85 
86 /************************************************************************/
87 /* from src/usr.bin/ftp/utils.h (1.135) */
88 
89 /*
90  * List words in stringlist, vertically arranged
91  */
92 static void
93 list_vertical(StringList *sl)
94 {
95 	int i, j;
96 	int columns, lines;
97 	char *p;
98 	size_t w, width;
99 
100 	width = 0;
101 
102 	for (i = 0 ; i < sl->sl_cur ; i++) {
103 		w = strlen(sl->sl_str[i]);
104 		if (w > width)
105 			width = w;
106 	}
107 	width = (width + 8) &~ 7;
108 
109 	columns = ttywidth / width;
110 	if (columns == 0)
111 		columns = 1;
112 	lines = (sl->sl_cur + columns - 1) / columns;
113 	for (i = 0; i < lines; i++) {
114 		for (j = 0; j < columns; j++) {
115 			p = sl->sl_str[j * lines + i];
116 			if (p)
117 				fputs(p, ttyout);
118 			if (j * lines + i + lines >= sl->sl_cur) {
119 				putc('\n', ttyout);
120 				break;
121 			}
122 			if (p) {
123 				w = strlen(p);
124 				while (w < width) {
125 					w = (w + 8) &~ 7;
126 					(void)putc('\t', ttyout);
127 				}
128 			}
129 		}
130 	}
131 }
132 
133 /*
134  * Copy characters from src into dst, \ quoting characters that require it
135  */
136 static void
137 ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
138 {
139 	int	di, si;
140 
141 	for (di = si = 0;
142 	    src[si] != '\0' && di < dstlen && si < srclen;
143 	    di++, si++) {
144 		switch (src[si]) {
145 		case '\\':
146 		case ' ':
147 		case '\t':
148 		case '\r':
149 		case '\n':
150 		case '"':
151 			dst[di++] = '\\';
152 			if (di >= dstlen)
153 				break;
154 			/* FALLTHROUGH */
155 		default:
156 			dst[di] = src[si];
157 		}
158 	}
159 	dst[di] = '\0';
160 }
161 
162 /*
163  * sl_init() with inbuilt error checking
164  */
165 static StringList *
166 ftp_sl_init(void)
167 {
168 	StringList *p;
169 
170 	p = sl_init();
171 	if (p == NULL)
172 		err(1, "Unable to allocate memory for stringlist");
173 	return (p);
174 }
175 
176 
177 /*
178  * sl_add() with inbuilt error checking
179  */
180 static void
181 ftp_sl_add(StringList *sl, char *i)
182 {
183 
184 	if (sl_add(sl, i) == -1)
185 		err(1, "Unable to add `%s' to stringlist", i);
186 }
187 
188 /*
189  * strdup() with inbuilt error checking
190  */
191 static char *
192 ftp_strdup(const char *str)
193 {
194 	char *s;
195 
196 	if (str == NULL)
197 		errx(1, "ftp_strdup() called with NULL argument");
198 	s = strdup(str);
199 	if (s == NULL)
200 		err(1, "Unable to allocate memory for string copy");
201 	return (s);
202 }
203 
204 /*
205  * Glob a local file name specification with the expectation of a single
206  * return value. Can't control multiple values being expanded from the
207  * expression, we return only the first.
208  * Returns NULL on error, or a pointer to a buffer containing the filename
209  * that's the caller's responsiblity to free(3) when finished with.
210  */
211 static char *
212 globulize(const char *pattern)
213 {
214 	glob_t gl;
215 	int flags;
216 	char *p;
217 
218 	if (!doglob)
219 		return (ftp_strdup(pattern));
220 
221 	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
222 	memset(&gl, 0, sizeof(gl));
223 	if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) {
224 		warnx("%s: not found", pattern);
225 		globfree(&gl);
226 		return (NULL);
227 	}
228 	p = ftp_strdup(gl.gl_pathv[0]);
229 	globfree(&gl);
230 	return (p);
231 }
232 
233 /* from src/usr.bin/ftp/utils.h (1.135) */
234 /************************************************************************/
235 
236 static int
237 comparstr(const void *a, const void *b)
238 {
239 	return (strcmp(*(const char * const *)a, *(const char * const *)b));
240 }
241 
242 /*
243  * Determine if complete is ambiguous. If unique, insert.
244  * If no choices, error. If unambiguous prefix, insert that.
245  * Otherwise, list choices. words is assumed to be filtered
246  * to only contain possible choices.
247  * Args:
248  *	word	word which started the match
249  *	list	list by default
250  *	words	stringlist containing possible matches
251  * Returns a result as per el_set(EL_ADDFN, ...)
252  */
253 static unsigned char
254 complete_ambiguous(EditLine *el, char *word, int list, StringList *words)
255 {
256 	char insertstr[MAXPATHLEN];
257 	char *lastmatch, *p;
258 	int i, j;
259 	size_t matchlen, wordlen;
260 
261 	wordlen = strlen(word);
262 	if (words->sl_cur == 0)
263 		return (CC_ERROR);	/* no choices available */
264 
265 	if (words->sl_cur == 1) {	/* only once choice available */
266 		p = words->sl_str[0] + wordlen;
267 		if (*p == '\0')		/* at end of word? */
268 			return (CC_REFRESH);
269 		ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
270 		if (el_insertstr(el, insertstr) == -1)
271 			return (CC_ERROR);
272 		else
273 			return (CC_REFRESH);
274 	}
275 
276 	if (!list) {
277 		matchlen = 0;
278 		lastmatch = words->sl_str[0];
279 		matchlen = strlen(lastmatch);
280 		for (i = 1 ; i < words->sl_cur ; i++) {
281 			for (j = wordlen ; j < strlen(words->sl_str[i]); j++)
282 				if (lastmatch[j] != words->sl_str[i][j])
283 					break;
284 			if (j < matchlen)
285 				matchlen = j;
286 		}
287 		if (matchlen > wordlen) {
288 			ftpvis(insertstr, sizeof(insertstr),
289 			    lastmatch + wordlen, matchlen - wordlen);
290 			if (el_insertstr(el, insertstr) == -1)
291 				return (CC_ERROR);
292 			else
293 				return (CC_REFRESH_BEEP);
294 		}
295 	}
296 
297 	putc('\n', ttyout);
298 	qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
299 	list_vertical(words);
300 	return (CC_REDISPLAY);
301 }
302 
303 /*
304  * Complete a mail command.
305  */
306 static unsigned char
307 complete_command(EditLine *el, char *word, int list)
308 {
309 	const struct cmd *c;
310 	StringList *words;
311 	size_t wordlen;
312 	unsigned char rv;
313 
314 	words = ftp_sl_init();
315 	wordlen = strlen(word);
316 
317 	for (c = cmdtab; c->c_name != NULL; c++) {
318 		if (wordlen > strlen(c->c_name))
319 			continue;
320 		if (strncmp(word, c->c_name, wordlen) == 0)
321 			ftp_sl_add(words, __UNCONST(c->c_name));
322 	}
323 
324 	rv = complete_ambiguous(el, word, list, words);
325 	if (rv == CC_REFRESH) {
326 		if (el_insertstr(el, " ") == -1)
327 			rv = CC_ERROR;
328 	}
329 	sl_free(words, 0);
330 	return (rv);
331 }
332 
333 /*
334  * Complete a local filename.
335  */
336 static unsigned char
337 complete_filename(EditLine *el, char *word, int list)
338 {
339 	StringList *words;
340 	char dir[MAXPATHLEN];
341 	char *fname;
342 	DIR *dd;
343 	struct dirent *dp;
344 	unsigned char rv;
345 	size_t len;
346 
347 	if ((fname = strrchr(word, '/')) == NULL) {
348 		dir[0] = '.';
349 		dir[1] = '\0';
350 		fname = word;
351 	} else {
352 		if (fname == word) {
353 			dir[0] = '/';
354 			dir[1] = '\0';
355 		} else
356 			(void)strlcpy(dir, word, fname - word + 1);
357 		fname++;
358 	}
359 	if (dir[0] == '~') {
360 		char *p;
361 
362 		if ((p = globulize(dir)) == NULL)
363 			return (CC_ERROR);
364 		(void)strlcpy(dir, p, sizeof(dir));
365 		free(p);
366 	}
367 
368 	if ((dd = opendir(dir)) == NULL)
369 		return (CC_ERROR);
370 
371 	words = ftp_sl_init();
372 	len = strlen(fname);
373 
374 	for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
375 		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
376 			continue;
377 
378 #if defined(DIRENT_MISSING_D_NAMLEN)
379 		if (len > strlen(dp->d_name))
380 			continue;
381 #else
382 		if (len > dp->d_namlen)
383 			continue;
384 #endif
385 		if (strncmp(fname, dp->d_name, len) == 0) {
386 			char *tcp;
387 
388 			tcp = ftp_strdup(dp->d_name);
389 			ftp_sl_add(words, tcp);
390 		}
391 	}
392 	closedir(dd);
393 
394 	rv = complete_ambiguous(el, fname, list, words);
395 	if (rv == CC_REFRESH) {
396 		struct stat sb;
397 		char path[MAXPATHLEN];
398 
399 		(void)strlcpy(path, dir,		sizeof(path));
400 		(void)strlcat(path, "/",		sizeof(path));
401 		(void)strlcat(path, words->sl_str[0],	sizeof(path));
402 
403 		if (stat(path, &sb) >= 0) {
404 			char suffix[2] = " ";
405 
406 			if (S_ISDIR(sb.st_mode))
407 				suffix[0] = '/';
408 			if (el_insertstr(el, suffix) == -1)
409 				rv = CC_ERROR;
410 		}
411 	}
412 	sl_free(words, 1);
413 	return (rv);
414 }
415 
416 static int
417 find_execs(char *word, char *path, StringList *list)
418 {
419 	char *sep;
420 	char *dir=path;
421 	DIR *dd;
422 	struct dirent *dp;
423 	int len = strlen(word);
424 	uid_t uid = getuid();
425 	gid_t gid = getgid();
426 
427 	for (sep=dir ; sep ; dir=sep+1) {
428 		if ((sep=strchr(dir, ':')) != NULL) {
429 			*sep=0;
430 		}
431 
432 		if ((dd = opendir(dir)) == NULL) {
433 			perror("dir");
434 			return -1;
435 		}
436 
437 		for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
438 
439 			if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
440 				continue;
441 
442 #if defined(DIRENT_MISSING_D_NAMLEN)
443 			if (len > strlen(dp->d_name))
444 				continue;
445 #else
446 			if (len > dp->d_namlen)
447 				continue;
448 #endif
449 
450 			if (strncmp(word, dp->d_name, len) == 0) {
451 				struct stat sb;
452 				char pathname[ MAXPATHLEN ];
453 				unsigned mask;
454 
455 				snprintf(pathname, sizeof(pathname), "%s/%s", dir, dp->d_name);
456 				if (stat(pathname, &sb) != 0) {
457 					perror(pathname);
458 					continue;
459 				}
460 
461 				mask = 0001;
462 				if (sb.st_uid == uid)	mask |= 0100;
463 				if (sb.st_gid == gid)	mask |= 0010;
464 
465 				if ((sb.st_mode & mask) != 0) {
466 					char *tcp;
467 					tcp = strdup(dp->d_name);
468 					sl_add(list, tcp);
469 				}
470 			}
471 
472 		}
473 
474 		closedir(dd);
475 	}
476 
477 	return 0;
478 }
479 
480 
481 /*
482  * Complete a local executable
483  */
484 static unsigned char
485 complete_executable(EditLine *el, char *word, int list)
486 {
487 	StringList *words;
488 	char dir[ MAXPATHLEN ];
489 	char *fname;
490 	unsigned char rv;
491 	int error;
492 
493 	if ((fname = strrchr(word, '/')) == NULL) {
494 		dir[0] = '\0';		/* walk the path */
495 		fname = word;
496 	} else {
497 		if (fname == word) {
498 			dir[0] = '/';
499 			dir[1] = '\0';
500 		} else {
501 			(void)strncpy(dir, word, fname - word);
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 		int len;
513 		env = getenv("PATH");
514 		len = strlen(env);
515 		path = salloc(len + 1);
516 		strcpy(path, env);
517 		error = find_execs(word, path, words);
518 	}
519 	else {		/* check specified dir only */
520 		error = find_execs(word, dir, words);
521 	}
522 	if (error != 0)
523 		return CC_ERROR;
524 
525 	rv = complete_ambiguous(el, fname, list, words);
526 
527 	if (rv == CC_REFRESH)
528 		if (el_insertstr(el, " ") == -1)
529 			rv = CC_ERROR;
530 
531 	sl_free(words, 1);
532 
533 	return (rv);
534 }
535 
536 
537 static unsigned
538 char complete_set(EditLine *el, char *word, int list)
539 {
540 	struct var *vp;
541 	char **ap;
542 	char **p;
543 	int h;
544 	int s;
545 	int len = strlen(word);
546 	StringList *words;
547 	unsigned char rv;
548 
549 	words = sl_init();
550 
551 	/* allocate space for variables table */
552 	for (h = 0, s = 1; h < HSHSIZE; h++)
553 		for (vp = variables[h]; vp != NULL; vp = vp->v_link)
554 			s++;
555 	ap = (char **) salloc(s * sizeof *ap);
556 
557 	/* save pointers */
558 	for (h = 0, p = ap; h < HSHSIZE; h++)
559 		for (vp = variables[h]; vp != NULL; vp = vp->v_link)
560 			*p++ = vp->v_name;
561 	*p = NULL;
562 
563 	/* sort pointers */
564 	sort(ap);
565 
566 	/* complete on list */
567 	for (p = ap; *p != NULL; p++) {
568 		if (len == 0 || strncmp(*p, word, len) == 0) {
569 			char *tcp;
570 			tcp = strdup(*p);
571 			sl_add(words, tcp);
572 		}
573 	}
574 
575 	rv = complete_ambiguous(el, word, list, words);
576 
577 	sl_free(words, 1);
578 
579 	return(rv);
580 }
581 
582 
583 
584 
585 
586 static unsigned char
587 complete_alias(EditLine *el, char *word, int list)
588 {
589 	struct grouphead *gh;
590 	char **ap;
591 	char **p;
592 	int h;
593 	int s;
594 	int len = strlen(word);
595 	StringList *words;
596 	unsigned char rv;
597 
598 	words = sl_init();
599 
600 	/* allocate space for alias table */
601 	for (h = 0, s = 1; h < HSHSIZE; h++)
602 		for (gh = groups[h]; gh != NULL; gh = gh->g_link)
603 			s++;
604 	ap = (char **) salloc(s * sizeof *ap);
605 
606 	/* save pointers */
607 	for (h = 0, p = ap; h < HSHSIZE; h++)
608 		for (gh = groups[h]; gh != NULL; gh = gh->g_link)
609 			*p++ = gh->g_name;
610 	*p = NULL;
611 
612 	/* sort pointers */
613 	sort(ap);
614 
615 	/* complete on list */
616 	for (p = ap; *p != NULL; p++) {
617 		if (len == 0 || strncmp(*p, word, len) == 0) {
618 			char *tcp;
619 			tcp = strdup(*p);
620 			sl_add(words, tcp);
621 		}
622 	}
623 
624 	rv = complete_ambiguous(el, word, list, words);
625 	if (rv == CC_REFRESH)
626 		if (el_insertstr(el, " ") == -1)
627 			rv = CC_ERROR;
628 
629 	sl_free(words, 1);
630 
631 	return(rv);
632 }
633 
634 
635 
636 static char	*stringbase;	/* current scan point in line buffer */
637 static char	*argbase;	/* current storage point in arg buffer */
638 static StringList *marg_sl;	/* stringlist containing margv */
639 static int	margc;		/* count of arguments on input line */
640 #define margv (marg_sl->sl_str)	/* args parsed from input line */
641 
642 static char *cursor_pos;
643 static int   cursor_argc;
644 static int   cursor_argo;
645 
646 static void
647 init_complete(void)
648 {
649 	marg_sl = sl_init();
650 	return;
651 }
652 
653 
654 
655 /************************************************************************/
656 /* from /usr/src/usr.bin/ftp/main.c(1.101) */
657 
658 static int   slrflag;
659 static char *altarg;		/* argv[1] with no shell-like preprocessing  */
660 
661 #ifdef NO_EDITCOMPLETE
662 #define	INC_CHKCURSOR(x)	(x)++
663 #else  /* !NO_EDITCOMPLETE */
664 #define	INC_CHKCURSOR(x)				\
665 	do {						\
666 		(x)++ ;					\
667 		if (x == cursor_pos) {			\
668 			cursor_argc = margc;		\
669 			cursor_argo = ap - argbase;	\
670 			cursor_pos  = NULL;		\
671 		}					\
672 	} while(0)
673 #endif /* !NO_EDITCOMPLETE */
674 
675 /*
676  * Parse string into argbuf;
677  * implemented with FSM to
678  * handle quoting and strings
679  */
680 static char *
681 slurpstring(void)
682 {
683 	int got_one = 0;
684 	char *sb = stringbase;
685 	char *ap = argbase;
686 	char *tmp = argbase;		/* will return this if token found */
687 
688 	if (*sb == '!' || *sb == '$') {	/* recognize ! as a token for shell */
689 		switch (slrflag) {	/* and $ as token for macro invoke */
690 			case 0:
691 				slrflag++;
692 				INC_CHKCURSOR(stringbase);
693 				return __UNCONST((*sb == '!') ? "!" : "$");
694 				/* NOTREACHED */
695 			case 1:
696 				slrflag++;
697 				altarg = stringbase;
698 				break;
699 			default:
700 				break;
701 		}
702 	}
703 
704 S0:
705 	switch (*sb) {
706 
707 	case '\0':
708 		goto OUT;
709 
710 	case ' ':
711 	case '\t':
712 		INC_CHKCURSOR(sb);
713 		goto S0;
714 
715 	default:
716 		switch (slrflag) {
717 			case 0:
718 				slrflag++;
719 				break;
720 			case 1:
721 				slrflag++;
722 				altarg = sb;
723 				break;
724 			default:
725 				break;
726 		}
727 		goto S1;
728 	}
729 
730 S1:
731 	switch (*sb) {
732 
733 	case ' ':
734 	case '\t':
735 	case '\0':
736 		goto OUT;	/* end of token */
737 
738 	case '\\':
739 		INC_CHKCURSOR(sb);
740 		goto S2;	/* slurp next character */
741 
742 	case '"':
743 		INC_CHKCURSOR(sb);
744 		goto S3;	/* slurp quoted string */
745 
746 	default:
747 		/* the first arg (command) is special - see execute() in lex.c */
748 		if (margc == 0 && index(" \t0123456789$^.:/-+*'\"", *sb))
749 			goto OUT;
750 
751 		*ap = *sb;	/* add character to token */
752 		ap++;
753 		INC_CHKCURSOR(sb);
754 		got_one = 1;
755 		goto S1;
756 	}
757 
758 S2:
759 	switch (*sb) {
760 
761 	case '\0':
762 		goto OUT;
763 
764 	default:
765 		*ap = *sb;
766 		ap++;
767 		INC_CHKCURSOR(sb);
768 		got_one = 1;
769 		goto S1;
770 	}
771 
772 S3:
773 	switch (*sb) {
774 
775 	case '\0':
776 		goto OUT;
777 
778 	case '"':
779 		INC_CHKCURSOR(sb);
780 		goto S1;
781 
782 	default:
783 		*ap = *sb;
784 		ap++;
785 		INC_CHKCURSOR(sb);
786 		got_one = 1;
787 		goto S3;
788 	}
789 
790 OUT:
791 	if (got_one)
792 		*ap++ = '\0';
793 	argbase = ap;			/* update storage pointer */
794 	stringbase = sb;		/* update scan pointer */
795 	if (got_one) {
796 		return (tmp);
797 	}
798 	switch (slrflag) {
799 		case 0:
800 			slrflag++;
801 			break;
802 		case 1:
803 			slrflag++;
804 			altarg = NULL;
805 			break;
806 		default:
807 			break;
808 	}
809 	return (NULL);
810 }
811 
812 
813 /*
814  * Slice a string up into argc/argv.
815  */
816 static void
817 makeargv(char *line)
818 {
819 	static char argbuf[ LINESIZE ];	/* argument storage buffer */
820 	char *argp;
821 
822 	stringbase = line;		/* scan from first of buffer */
823 	argbase = argbuf;		/* store from first of buffer */
824 	slrflag = 0;
825 	marg_sl->sl_cur = 0;		/* reset to start of marg_sl */
826 	for (margc = 0; ; margc++) {
827 		argp = slurpstring();
828 		ftp_sl_add(marg_sl, argp);
829 		if (argp == NULL)
830 			break;
831 	}
832 #ifndef NO_EDITCOMPLETE
833 	if (cursor_pos == line) {
834 		cursor_argc = 0;
835 		cursor_argo = 0;
836 	} else if (cursor_pos != NULL) {
837 		cursor_argc = margc;
838 		cursor_argo = strlen(margv[margc-1]);
839 	}
840 #endif /* !NO_EDITCOMPLETE */
841 }
842 
843 /* from /usr/src/usr.bin/ftp/main.c(1.101) */
844 /************************************************************************/
845 
846 
847 /*
848  * Generic complete routine
849  */
850 static unsigned char
851 complete(EditLine *el, int ch)
852 {
853 	static char line[LINESIZE];	/* input line buffer */
854 	static char word[LINESIZE];
855 	static int lastc_argc, lastc_argo;
856 
857 	const struct cmd *c;
858 	const LineInfo *lf;
859 	int celems, dolist, cmpltype;
860 	size_t len;
861 
862 	lf = el_line(el);
863 	len = lf->lastchar - lf->buffer;
864 #if 1
865 	if (ch == 04) {	/* CTRL-D is special */
866 		if (len == 0)
867 			return (CC_EOF);
868 		if (lf->lastchar != lf->cursor) {
869 			el_push(el, __UNCONST("")); /* delete current char without using ^D */
870 			return (CC_NORM);
871 		}
872 	}
873 #endif
874 	if (len >= sizeof(line))
875 		return (CC_ERROR);
876 	(void)strlcpy(line, lf->buffer, len + 1);
877 	cursor_pos = line + (lf->cursor - lf->buffer);
878 	lastc_argc = cursor_argc;	/* remember last cursor pos */
879 	lastc_argo = cursor_argo;
880 	makeargv(line);			/* build argc/argv of current line */
881 
882 	if (cursor_argo >= sizeof(word))
883 		return (CC_ERROR);
884 
885 	dolist = 0;
886 	/* if cursor and word are the same, list alternatives */
887 	if (lastc_argc == cursor_argc && lastc_argo == cursor_argo
888 	    && strncmp(word, margv[cursor_argc] ? margv[cursor_argc] : "",
889 		       cursor_argo) == 0)
890 		dolist = 1;
891 	else if (cursor_argc < margc)
892 		(void)strlcpy(word, margv[cursor_argc], cursor_argo + 1);
893 	word[cursor_argo] = '\0';
894 
895 	if (cursor_argc == 0)
896 		return (complete_command(el, word, dolist));
897 
898 	c = getcmd(margv[0]);
899 	if (c == NULL)
900 		return (CC_ERROR);
901 	celems = strlen(c->c_complete);
902 
903 		/* check for 'continuation' completes (which are uppercase) */
904 	if ((cursor_argc > celems) && (celems > 0)
905 	    && isupper((unsigned char) c->c_complete[celems-1]))
906 		cursor_argc = celems;
907 
908 	if (cursor_argc > celems)
909 		return (CC_ERROR);
910 
911 	cmpltype = c->c_complete[cursor_argc - 1];
912 	switch (cmpltype) {
913 		case 'a':			/* alias complete */
914 		case 'A':
915 			return (complete_alias(el, word, dolist));
916 
917 		case 'c':			/* command complete */
918 		case 'C':
919 			return (complete_command(el, word, dolist));
920 
921 		case 'f':			/* filename complete */
922 		case 'F':
923 			return (complete_filename(el, word, dolist));
924 
925 		case 'n':			/* no complete */
926 		case 'N':			/* no complete */
927 			return (CC_ERROR);
928 
929 		case 's':
930 		case 'S':
931 			return (complete_set(el, word, dolist));
932 
933 		case 'x':			/* executable complete */
934 		case 'X':
935 			return (complete_executable(el, word, dolist));
936 
937 		default:
938 			errx(1, "unknown complete type `%c'", cmpltype);
939 			return (CC_ERROR);
940 	}
941 	/* NOTREACHED */
942 }
943 
944 
945 /*************************************************************************/
946 /* Most of this was originally taken directly from the readline manpage. */
947 
948 static struct {
949 	EditLine *el;		/* editline(3) with completion and history */
950 	EditLine *elo;		/* editline(3) editline only, no completion */
951 	History  *hist;		/* editline(3) history structure */
952 	const char *prompt;	/* prompt */
953 } rl_global = {
954 	.el = NULL,
955 	.hist = NULL,
956 	.prompt = NULL
957 };
958 
959 char *
960 rl_gets(const char *prompt)
961 {
962 	int cnt;
963 	const char *buf;
964 	HistEvent ev;
965 	static char line[LINE_MAX];
966 
967 	rl_global.prompt = prompt;
968 	buf = el_gets(rl_global.el, &cnt);
969 
970 	if (buf == NULL || cnt <= 0)
971 		return NULL;
972 
973 	/* enter the line into history */
974 	if (history(rl_global.hist, &ev, H_ENTER, buf) == 0)
975 		printf("Failed history entry: %s", buf);
976 #ifdef DEBUG
977 	else
978 		printf("history entry: %s\n", ev.str);
979 #endif
980 
981 	cnt--;	/* trash the trailing LF */
982 	cnt = MIN(sizeof(line) - 1, cnt);
983 	(void)memcpy(line, buf, cnt);
984 	line[cnt] = '\0';
985 
986 	return line;
987 }
988 
989 
990 /*
991  * Edit a line containing string, with no history or completion.
992  */
993 char *
994 rl_getline(const char *prompt, char *string)
995 {
996 	static char line[LINE_MAX];
997 	const char *buf;
998 	int cnt;
999 
1000 	rl_global.prompt = prompt;
1001 
1002 	if (string)
1003 		el_push(rl_global.elo, string);
1004 
1005 	buf = el_gets(rl_global.elo, &cnt);
1006 	if (buf == NULL || cnt <= 0) {
1007 		if (cnt == 0)
1008 			fputc('\n', stdout);
1009 	  	line[0] = '\0';
1010 		return line;
1011 	}
1012 
1013 	cnt--;
1014 	cnt = MIN(sizeof(line) - 1, cnt);
1015 	(void)memcpy(line, buf, cnt);
1016 	line[cnt] = '\0';
1017 
1018 	return line;
1019 }
1020 
1021 
1022 static const char *
1023 show_prompt(EditLine *e __attribute__((unused)))
1024 {
1025 	return rl_global.prompt;
1026 }
1027 
1028 void
1029 init_readline(void)
1030 {
1031 	HistEvent ev;
1032 	const char *el_editor;
1033 	const char *el_history_size;
1034 	char *el_completion_keys;
1035 
1036 	rl_global.hist = history_init();	/* init the builtin history */
1037 	el_history_size = value("el_history_size");
1038 	if (el_history_size == NULL)
1039 		el_history_size = "0";
1040 	if (history(rl_global.hist, &ev, H_SETSIZE, atoi(el_history_size)))
1041 		printf("history: %s\n", ev.str);
1042 
1043 	rl_global.el  = el_init(getprogname(), stdin, stdout, stderr);
1044 	rl_global.elo = el_init(getprogname(), stdin, stdout, stderr);
1045 
1046 	el_editor = value("el_editor");
1047 	if (el_editor) {
1048 		el_set(rl_global.el, EL_EDITOR, el_editor);
1049 		el_set(rl_global.elo, EL_EDITOR, el_editor);
1050 	}
1051 
1052 	el_set(rl_global.el, EL_PROMPT, show_prompt);
1053 	el_set(rl_global.elo, EL_PROMPT, show_prompt);
1054 	el_set(rl_global.el, EL_HIST, history, rl_global.hist);	/* use history */
1055 	el_source(rl_global.el, NULL);				/* read ~/.editrc */
1056 
1057 	/* add local file completion, bind to TAB */
1058 	el_set(rl_global.el, EL_ADDFN, "mail-complete",
1059 	    "Context sensitive argument completion",
1060 	    complete);
1061 
1062 	el_completion_keys = value("el_completion_keys");
1063 	if (el_completion_keys && *el_completion_keys) {
1064 		struct name *np, *nq;
1065 		np = lexpand(el_completion_keys, 0);
1066 		for (nq = np ; nq ; nq = nq->n_flink)
1067 			el_set(rl_global.el, EL_BIND, nq->n_name, "mail-complete", NULL);
1068 	}
1069 
1070 	init_complete();
1071 
1072 	el_set(rl_global.el, EL_SIGNAL, 1);
1073 	el_set(rl_global.elo, EL_SIGNAL, 1);
1074 
1075 	return;
1076 }
1077 
1078 /************************************************************************/
1079 #endif /* USE_READLINE */
1080