Home | History | Annotate | Line # | Download | only in mail
complete.c revision 1.7
      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