Home | History | Annotate | Line # | Download | only in mail
complete.c revision 1.8
      1 /*	$NetBSD: complete.c,v 1.8 2006/10/02 16:43:31 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.8 2006/10/02 16:43:31 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 && got_one &&
    749 		    strchr(" \t0123456789$^.:/-+*'\"", *sb))
    750 			goto OUT;
    751 
    752 		*ap = *sb;	/* add character to token */
    753 		ap++;
    754 		INC_CHKCURSOR(sb);
    755 		got_one = 1;
    756 		goto S1;
    757 	}
    758 
    759 S2:
    760 	switch (*sb) {
    761 
    762 	case '\0':
    763 		goto OUT;
    764 
    765 	default:
    766 		*ap = *sb;
    767 		ap++;
    768 		INC_CHKCURSOR(sb);
    769 		got_one = 1;
    770 		goto S1;
    771 	}
    772 
    773 S3:
    774 	switch (*sb) {
    775 
    776 	case '\0':
    777 		goto OUT;
    778 
    779 	case '"':
    780 		INC_CHKCURSOR(sb);
    781 		goto S1;
    782 
    783 	default:
    784 		*ap = *sb;
    785 		ap++;
    786 		INC_CHKCURSOR(sb);
    787 		got_one = 1;
    788 		goto S3;
    789 	}
    790 
    791 OUT:
    792 	if (got_one)
    793 		*ap++ = '\0';
    794 	argbase = ap;			/* update storage pointer */
    795 	stringbase = sb;		/* update scan pointer */
    796 	if (got_one) {
    797 		return (tmp);
    798 	}
    799 	switch (slrflag) {
    800 		case 0:
    801 			slrflag++;
    802 			break;
    803 		case 1:
    804 			slrflag++;
    805 			altarg = NULL;
    806 			break;
    807 		default:
    808 			break;
    809 	}
    810 	return (NULL);
    811 }
    812 
    813 
    814 /*
    815  * Slice a string up into argc/argv.
    816  */
    817 static void
    818 makeargv(char *line)
    819 {
    820 	static char argbuf[ LINESIZE ];	/* argument storage buffer */
    821 	char *argp;
    822 
    823 	stringbase = line;		/* scan from first of buffer */
    824 	argbase = argbuf;		/* store from first of buffer */
    825 	slrflag = 0;
    826 	marg_sl->sl_cur = 0;		/* reset to start of marg_sl */
    827 	for (margc = 0; ; margc++) {
    828 		argp = slurpstring();
    829 		ftp_sl_add(marg_sl, argp);
    830 		if (argp == NULL)
    831 			break;
    832 	}
    833 #ifndef NO_EDITCOMPLETE
    834 	if (cursor_pos == line) {
    835 		cursor_argc = 0;
    836 		cursor_argo = 0;
    837 	} else if (cursor_pos != NULL) {
    838 		cursor_argc = margc;
    839 		cursor_argo = strlen(margv[margc-1]);
    840 	}
    841 #endif /* !NO_EDITCOMPLETE */
    842 }
    843 
    844 /* from /usr/src/usr.bin/ftp/main.c(1.101) */
    845 /************************************************************************/
    846 
    847 
    848 /*
    849  * Generic complete routine
    850  */
    851 static unsigned char
    852 complete(EditLine *el, int ch)
    853 {
    854 	static char line[LINESIZE];	/* input line buffer */
    855 	static char word[LINESIZE];
    856 	static int lastc_argc, lastc_argo;
    857 
    858 	const struct cmd *c;
    859 	const LineInfo *lf;
    860 	int celems, dolist, cmpltype;
    861 	size_t len;
    862 
    863 	lf = el_line(el);
    864 	len = lf->lastchar - lf->buffer;
    865 #if 1
    866 	if (ch == 04) {	/* CTRL-D is special */
    867 		if (len == 0)
    868 			return (CC_EOF);
    869 		if (lf->lastchar != lf->cursor) {
    870 			el_push(el, __UNCONST("")); /* delete current char without using ^D */
    871 			return (CC_NORM);
    872 		}
    873 	}
    874 #endif
    875 	if (len >= sizeof(line))
    876 		return (CC_ERROR);
    877 	(void)strlcpy(line, lf->buffer, len + 1);
    878 	cursor_pos = line + (lf->cursor - lf->buffer);
    879 	lastc_argc = cursor_argc;	/* remember last cursor pos */
    880 	lastc_argo = cursor_argo;
    881 	makeargv(line);			/* build argc/argv of current line */
    882 
    883 	if (cursor_argo >= sizeof(word))
    884 		return (CC_ERROR);
    885 
    886 	dolist = 0;
    887 	/* if cursor and word are the same, list alternatives */
    888 	if (lastc_argc == cursor_argc && lastc_argo == cursor_argo
    889 	    && strncmp(word, margv[cursor_argc] ? margv[cursor_argc] : "",
    890 		       cursor_argo) == 0)
    891 		dolist = 1;
    892 	else if (cursor_argc < margc)
    893 		(void)strlcpy(word, margv[cursor_argc], cursor_argo + 1);
    894 	word[cursor_argo] = '\0';
    895 
    896 	if (cursor_argc == 0)
    897 		return (complete_command(el, word, dolist));
    898 
    899 	c = getcmd(margv[0]);
    900 	if (c == NULL)
    901 		return (CC_ERROR);
    902 	celems = strlen(c->c_complete);
    903 
    904 		/* check for 'continuation' completes (which are uppercase) */
    905 	if ((cursor_argc > celems) && (celems > 0)
    906 	    && isupper((unsigned char) c->c_complete[celems-1]))
    907 		cursor_argc = celems;
    908 
    909 	if (cursor_argc > celems)
    910 		return (CC_ERROR);
    911 
    912 	cmpltype = c->c_complete[cursor_argc - 1];
    913 	switch (cmpltype) {
    914 		case 'a':			/* alias complete */
    915 		case 'A':
    916 			return (complete_alias(el, word, dolist));
    917 
    918 		case 'c':			/* command complete */
    919 		case 'C':
    920 			return (complete_command(el, word, dolist));
    921 
    922 		case 'f':			/* filename complete */
    923 		case 'F':
    924 			return (complete_filename(el, word, dolist));
    925 
    926 		case 'n':			/* no complete */
    927 		case 'N':			/* no complete */
    928 			return (CC_ERROR);
    929 
    930 		case 's':
    931 		case 'S':
    932 			return (complete_set(el, word, dolist));
    933 
    934 		case 'x':			/* executable complete */
    935 		case 'X':
    936 			return (complete_executable(el, word, dolist));
    937 
    938 		default:
    939 			errx(1, "unknown complete type `%c'", cmpltype);
    940 			return (CC_ERROR);
    941 	}
    942 	/* NOTREACHED */
    943 }
    944 
    945 
    946 /*************************************************************************/
    947 /* Most of this was originally taken directly from the readline manpage. */
    948 
    949 static struct {
    950 	EditLine *el;		/* editline(3) with completion and history */
    951 	EditLine *elo;		/* editline(3) editline only, no completion */
    952 	History  *hist;		/* editline(3) history structure */
    953 	const char *prompt;	/* prompt */
    954 } rl_global = {
    955 	.el = NULL,
    956 	.hist = NULL,
    957 	.prompt = NULL
    958 };
    959 
    960 char *
    961 rl_gets(const char *prompt)
    962 {
    963 	int cnt;
    964 	const char *buf;
    965 	HistEvent ev;
    966 	static char line[LINE_MAX];
    967 
    968 	rl_global.prompt = prompt;
    969 	buf = el_gets(rl_global.el, &cnt);
    970 
    971 	if (buf == NULL || cnt <= 0)
    972 		return NULL;
    973 
    974 	/* enter the line into history */
    975 	if (history(rl_global.hist, &ev, H_ENTER, buf) == 0)
    976 		printf("Failed history entry: %s", buf);
    977 #ifdef DEBUG
    978 	else
    979 		printf("history entry: %s\n", ev.str);
    980 #endif
    981 
    982 	cnt--;	/* trash the trailing LF */
    983 	cnt = MIN(sizeof(line) - 1, cnt);
    984 	(void)memcpy(line, buf, cnt);
    985 	line[cnt] = '\0';
    986 
    987 	return line;
    988 }
    989 
    990 
    991 /*
    992  * Edit a line containing string, with no history or completion.
    993  */
    994 char *
    995 rl_getline(const char *prompt, char *string)
    996 {
    997 	static char line[LINE_MAX];
    998 	const char *buf;
    999 	int cnt;
   1000 
   1001 	rl_global.prompt = prompt;
   1002 
   1003 	if (string)
   1004 		el_push(rl_global.elo, string);
   1005 
   1006 	buf = el_gets(rl_global.elo, &cnt);
   1007 	if (buf == NULL || cnt <= 0) {
   1008 		if (cnt == 0)
   1009 			fputc('\n', stdout);
   1010 	  	line[0] = '\0';
   1011 		return line;
   1012 	}
   1013 
   1014 	cnt--;
   1015 	cnt = MIN(sizeof(line) - 1, cnt);
   1016 	(void)memcpy(line, buf, cnt);
   1017 	line[cnt] = '\0';
   1018 
   1019 	return line;
   1020 }
   1021 
   1022 
   1023 static const char *
   1024 show_prompt(EditLine *e __attribute__((unused)))
   1025 {
   1026 	return rl_global.prompt;
   1027 }
   1028 
   1029 void
   1030 init_readline(void)
   1031 {
   1032 	HistEvent ev;
   1033 	const char *el_editor;
   1034 	const char *el_history_size;
   1035 	char *el_completion_keys;
   1036 
   1037 	rl_global.hist = history_init();	/* init the builtin history */
   1038 	el_history_size = value("el_history_size");
   1039 	if (el_history_size == NULL)
   1040 		el_history_size = "0";
   1041 	if (history(rl_global.hist, &ev, H_SETSIZE, atoi(el_history_size)))
   1042 		printf("history: %s\n", ev.str);
   1043 
   1044 	rl_global.el  = el_init(getprogname(), stdin, stdout, stderr);
   1045 	rl_global.elo = el_init(getprogname(), stdin, stdout, stderr);
   1046 
   1047 	el_editor = value("el_editor");
   1048 	if (el_editor) {
   1049 		el_set(rl_global.el, EL_EDITOR, el_editor);
   1050 		el_set(rl_global.elo, EL_EDITOR, el_editor);
   1051 	}
   1052 
   1053 	el_set(rl_global.el, EL_PROMPT, show_prompt);
   1054 	el_set(rl_global.elo, EL_PROMPT, show_prompt);
   1055 	el_set(rl_global.el, EL_HIST, history, rl_global.hist);	/* use history */
   1056 	el_source(rl_global.el, NULL);				/* read ~/.editrc */
   1057 
   1058 	/* add local file completion, bind to TAB */
   1059 	el_set(rl_global.el, EL_ADDFN, "mail-complete",
   1060 	    "Context sensitive argument completion",
   1061 	    complete);
   1062 
   1063 	el_completion_keys = value("el_completion_keys");
   1064 	if (el_completion_keys && *el_completion_keys) {
   1065 		struct name *np, *nq;
   1066 		np = lexpand(el_completion_keys, 0);
   1067 		for (nq = np ; nq ; nq = nq->n_flink)
   1068 			el_set(rl_global.el, EL_BIND, nq->n_name, "mail-complete", NULL);
   1069 	}
   1070 
   1071 	init_complete();
   1072 
   1073 	el_set(rl_global.el, EL_SIGNAL, 1);
   1074 	el_set(rl_global.elo, EL_SIGNAL, 1);
   1075 
   1076 	return;
   1077 }
   1078 
   1079 /************************************************************************/
   1080 #endif /* USE_READLINE */
   1081