xref: /netbsd-src/lib/libedit/filecomplete.c (revision 4ee547369c79e33850ff911fab3840da8be0b1d9)
1*4ee54736Schristos /*	$NetBSD: filecomplete.c,v 1.73 2023/04/25 17:51:32 christos Exp $	*/
241a59814Sdsl 
341a59814Sdsl /*-
441a59814Sdsl  * Copyright (c) 1997 The NetBSD Foundation, Inc.
541a59814Sdsl  * All rights reserved.
641a59814Sdsl  *
741a59814Sdsl  * This code is derived from software contributed to The NetBSD Foundation
841a59814Sdsl  * by Jaromir Dolecek.
941a59814Sdsl  *
1041a59814Sdsl  * Redistribution and use in source and binary forms, with or without
1141a59814Sdsl  * modification, are permitted provided that the following conditions
1241a59814Sdsl  * are met:
1341a59814Sdsl  * 1. Redistributions of source code must retain the above copyright
1441a59814Sdsl  *    notice, this list of conditions and the following disclaimer.
1541a59814Sdsl  * 2. Redistributions in binary form must reproduce the above copyright
1641a59814Sdsl  *    notice, this list of conditions and the following disclaimer in the
1741a59814Sdsl  *    documentation and/or other materials provided with the distribution.
1841a59814Sdsl  *
1941a59814Sdsl  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2041a59814Sdsl  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2141a59814Sdsl  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2241a59814Sdsl  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
2341a59814Sdsl  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2441a59814Sdsl  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2541a59814Sdsl  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2641a59814Sdsl  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2741a59814Sdsl  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2841a59814Sdsl  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2941a59814Sdsl  * POSSIBILITY OF SUCH DAMAGE.
3041a59814Sdsl  */
3141a59814Sdsl 
3241a59814Sdsl #include "config.h"
3341a59814Sdsl #if !defined(lint) && !defined(SCCSID)
34*4ee54736Schristos __RCSID("$NetBSD: filecomplete.c,v 1.73 2023/04/25 17:51:32 christos Exp $");
3541a59814Sdsl #endif /* not lint && not SCCSID */
3641a59814Sdsl 
3741a59814Sdsl #include <sys/types.h>
3841a59814Sdsl #include <sys/stat.h>
3941a59814Sdsl #include <dirent.h>
4041a59814Sdsl #include <errno.h>
4141a59814Sdsl #include <fcntl.h>
4222383670Schristos #include <limits.h>
4322383670Schristos #include <pwd.h>
4422383670Schristos #include <stdio.h>
4522383670Schristos #include <stdlib.h>
4622383670Schristos #include <string.h>
4722383670Schristos #include <unistd.h>
481237974aSchristos 
4941a59814Sdsl #include "el.h"
5041a59814Sdsl #include "filecomplete.h"
5141a59814Sdsl 
520594af80Schristos static const wchar_t break_chars[] = L" \t\n\"\\'`@$><=;|&{(";
5341a59814Sdsl 
5441a59814Sdsl /********************************/
5541a59814Sdsl /* completion functions */
5641a59814Sdsl 
5741a59814Sdsl /*
5841a59814Sdsl  * does tilde expansion of strings of type ``~user/foo''
5941a59814Sdsl  * if ``user'' isn't valid user name or ``txt'' doesn't start
6041a59814Sdsl  * w/ '~', returns pointer to strdup()ed copy of ``txt''
6141a59814Sdsl  *
6241482d77Sriz  * it's the caller's responsibility to free() the returned string
6341a59814Sdsl  */
6441a59814Sdsl char *
fn_tilde_expand(const char * txt)6519c38590Schristos fn_tilde_expand(const char *txt)
6641a59814Sdsl {
6756eac9eaSchristos #if defined(HAVE_GETPW_R_POSIX) || defined(HAVE_GETPW_R_DRAFT)
6856eac9eaSchristos 	struct passwd pwres;
6956eac9eaSchristos 	char pwbuf[1024];
7056eac9eaSchristos #endif
7156eac9eaSchristos 	struct passwd *pass;
725679adf8Schristos 	const char *pos;
7341a59814Sdsl 	char *temp;
7441a59814Sdsl 	size_t len = 0;
7541a59814Sdsl 
7641a59814Sdsl 	if (txt[0] != '~')
77b71bed95Schristos 		return strdup(txt);
7841a59814Sdsl 
795679adf8Schristos 	pos = strchr(txt + 1, '/');
805679adf8Schristos 	if (pos == NULL) {
8141a59814Sdsl 		temp = strdup(txt + 1);
8241a59814Sdsl 		if (temp == NULL)
8341a59814Sdsl 			return NULL;
8441a59814Sdsl 	} else {
853d802cf5Schristos 		/* text until string after slash */
865679adf8Schristos 		len = (size_t)(pos - txt + 1);
87113f06a3Schristos 		temp = el_calloc(len, sizeof(*temp));
8841a59814Sdsl 		if (temp == NULL)
8941a59814Sdsl 			return NULL;
90a47ebb18Schristos 		(void)strlcpy(temp, txt + 1, len - 1);
9141a59814Sdsl 	}
922e685adeSdsl 	if (temp[0] == 0) {
93b2105969Schristos #ifdef HAVE_GETPW_R_POSIX
94b2105969Schristos 		if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf),
95b2105969Schristos 		    &pass) != 0)
962e685adeSdsl 			pass = NULL;
97b2105969Schristos #elif HAVE_GETPW_R_DRAFT
98b2105969Schristos 		pass = getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf));
99b2105969Schristos #else
100b2105969Schristos 		pass = getpwuid(getuid());
101b2105969Schristos #endif
1022e685adeSdsl 	} else {
103b2105969Schristos #ifdef HAVE_GETPW_R_POSIX
10441a59814Sdsl 		if (getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf), &pass) != 0)
10541a59814Sdsl 			pass = NULL;
106b2105969Schristos #elif HAVE_GETPW_R_DRAFT
107b2105969Schristos 		pass = getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf));
108b2105969Schristos #else
109b2105969Schristos 		pass = getpwnam(temp);
110b2105969Schristos #endif
1112e685adeSdsl 	}
112a13cd756Schristos 	el_free(temp);		/* value no more needed */
11341a59814Sdsl 	if (pass == NULL)
114b71bed95Schristos 		return strdup(txt);
11541a59814Sdsl 
11641a59814Sdsl 	/* update pointer txt to point at string immedially following */
11741a59814Sdsl 	/* first slash */
11841a59814Sdsl 	txt += len;
11941a59814Sdsl 
120a13cd756Schristos 	len = strlen(pass->pw_dir) + 1 + strlen(txt) + 1;
121113f06a3Schristos 	temp = el_calloc(len, sizeof(*temp));
12241a59814Sdsl 	if (temp == NULL)
12341a59814Sdsl 		return NULL;
124a13cd756Schristos 	(void)snprintf(temp, len, "%s/%s", pass->pw_dir, txt);
12541a59814Sdsl 
126b71bed95Schristos 	return temp;
12741a59814Sdsl }
12841a59814Sdsl 
129fa615556Sabhinav static int
needs_escaping(wchar_t c)130d1647d9cSchristos needs_escaping(wchar_t c)
131fa615556Sabhinav {
132fa615556Sabhinav 	switch (c) {
133fa615556Sabhinav 	case '\'':
134fa615556Sabhinav 	case '"':
135fa615556Sabhinav 	case '(':
136fa615556Sabhinav 	case ')':
137fa615556Sabhinav 	case '\\':
138fa615556Sabhinav 	case '<':
139fa615556Sabhinav 	case '>':
140fa615556Sabhinav 	case '$':
141fa615556Sabhinav 	case '#':
142fa615556Sabhinav 	case ' ':
143fa615556Sabhinav 	case '\n':
144fa615556Sabhinav 	case '\t':
145fa615556Sabhinav 	case '?':
146fa615556Sabhinav 	case ';':
147fa615556Sabhinav 	case '`':
148fa615556Sabhinav 	case '@':
149fa615556Sabhinav 	case '=':
150fa615556Sabhinav 	case '|':
151fa615556Sabhinav 	case '{':
152fa615556Sabhinav 	case '}':
153fa615556Sabhinav 	case '&':
154b36e4c8fSabhinav 	case '*':
155b36e4c8fSabhinav 	case '[':
156fa615556Sabhinav 		return 1;
157fa615556Sabhinav 	default:
158fa615556Sabhinav 		return 0;
159fa615556Sabhinav 	}
160fa615556Sabhinav }
161fa615556Sabhinav 
1627512edc2Schristos static int
needs_dquote_escaping(char c)1637512edc2Schristos needs_dquote_escaping(char c)
1647512edc2Schristos {
1657512edc2Schristos 	switch (c) {
1667512edc2Schristos 	case '"':
1677512edc2Schristos 	case '\\':
1687512edc2Schristos 	case '`':
1697512edc2Schristos 	case '$':
1707512edc2Schristos 		return 1;
1717512edc2Schristos 	default:
1727512edc2Schristos 		return 0;
1737512edc2Schristos 	}
1747512edc2Schristos }
1757512edc2Schristos 
176c93cba3aSabhinav 
177c93cba3aSabhinav static wchar_t *
unescape_string(const wchar_t * string,size_t length)178c93cba3aSabhinav unescape_string(const wchar_t *string, size_t length)
179c93cba3aSabhinav {
18063147cb5Sabhinav 	size_t i;
18163147cb5Sabhinav 	size_t j = 0;
182113f06a3Schristos 	wchar_t *unescaped = el_calloc(length + 1, sizeof(*string));
183c93cba3aSabhinav 	if (unescaped == NULL)
184c93cba3aSabhinav 		return NULL;
18563147cb5Sabhinav 	for (i = 0; i < length ; i++) {
186c93cba3aSabhinav 		if (string[i] == '\\')
187c93cba3aSabhinav 			continue;
188c93cba3aSabhinav 		unescaped[j++] = string[i];
189c93cba3aSabhinav 	}
190c93cba3aSabhinav 	unescaped[j] = 0;
191c93cba3aSabhinav 	return unescaped;
192c93cba3aSabhinav }
193c93cba3aSabhinav 
194fa615556Sabhinav static char *
escape_filename(EditLine * el,const char * filename,int single_match,const char * (* app_func)(const char *))195c3d00a7dSabhinav escape_filename(EditLine * el, const char *filename, int single_match,
196c3d00a7dSabhinav 		const char *(*app_func)(const char *))
197fa615556Sabhinav {
198fa615556Sabhinav 	size_t original_len = 0;
199fa615556Sabhinav 	size_t escaped_character_count = 0;
200fa615556Sabhinav 	size_t offset = 0;
201fa615556Sabhinav 	size_t newlen;
202fa615556Sabhinav 	const char *s;
203fa615556Sabhinav 	char c;
204fa615556Sabhinav 	size_t s_quoted = 0;	/* does the input contain a single quote */
205fa615556Sabhinav 	size_t d_quoted = 0;	/* does the input contain a double quote */
206fa615556Sabhinav 	char *escaped_str;
207fa615556Sabhinav 	wchar_t *temp = el->el_line.buffer;
208c3d00a7dSabhinav 	const char *append_char = NULL;
2097512edc2Schristos 
210c93cba3aSabhinav 	if (filename == NULL)
211c93cba3aSabhinav 		return NULL;
212fa615556Sabhinav 
213fa615556Sabhinav 	while (temp != el->el_line.cursor) {
214fa615556Sabhinav 		/*
2157512edc2Schristos 		 * If we see a single quote but have not seen a double quote
2165679adf8Schristos 		 * so far set/unset s_quote, unless it is already quoted
217fa615556Sabhinav 		 */
2185679adf8Schristos 		if (temp[0] == '\'' && !d_quoted &&
2195679adf8Schristos 		    (temp == el->el_line.buffer || temp[-1] != '\\'))
220fa615556Sabhinav 			s_quoted = !s_quoted;
221fa615556Sabhinav 		/*
222fa615556Sabhinav 		 * vice versa to the above condition
223fa615556Sabhinav 		 */
224fa615556Sabhinav 		else if (temp[0] == '"' && !s_quoted)
225fa615556Sabhinav 			d_quoted = !d_quoted;
226fa615556Sabhinav 		temp++;
227fa615556Sabhinav 	}
228fa615556Sabhinav 
229fa615556Sabhinav 	/* Count number of special characters so that we can calculate
230fa615556Sabhinav 	 * number of extra bytes needed in the new string
231fa615556Sabhinav 	 */
232fa615556Sabhinav 	for (s = filename; *s; s++, original_len++) {
233fa615556Sabhinav 		c = *s;
234fa615556Sabhinav 		/* Inside a single quote only single quotes need escaping */
235fa615556Sabhinav 		if (s_quoted && c == '\'') {
236fa615556Sabhinav 			escaped_character_count += 3;
237fa615556Sabhinav 			continue;
238fa615556Sabhinav 		}
239fa615556Sabhinav 		/* Inside double quotes only ", \, ` and $ need escaping */
2407512edc2Schristos 		if (d_quoted && needs_dquote_escaping(c)) {
241fa615556Sabhinav 			escaped_character_count++;
242fa615556Sabhinav 			continue;
243fa615556Sabhinav 		}
244fa615556Sabhinav 		if (!s_quoted && !d_quoted && needs_escaping(c))
245fa615556Sabhinav 			escaped_character_count++;
246fa615556Sabhinav 	}
247fa615556Sabhinav 
248fa615556Sabhinav 	newlen = original_len + escaped_character_count + 1;
2497512edc2Schristos 	if (s_quoted || d_quoted)
2507512edc2Schristos 		newlen++;
2517512edc2Schristos 
252c3d00a7dSabhinav 	if (single_match && app_func)
253c3d00a7dSabhinav 		newlen++;
254c3d00a7dSabhinav 
255fa615556Sabhinav 	if ((escaped_str = el_malloc(newlen)) == NULL)
256fa615556Sabhinav 		return NULL;
257fa615556Sabhinav 
258fa615556Sabhinav 	for (s = filename; *s; s++) {
259fa615556Sabhinav 		c = *s;
260fa615556Sabhinav 		if (!needs_escaping(c)) {
261fa615556Sabhinav 			/* no escaping is required continue as usual */
262fa615556Sabhinav 			escaped_str[offset++] = c;
263fa615556Sabhinav 			continue;
264fa615556Sabhinav 		}
265fa615556Sabhinav 
266fa615556Sabhinav 		/* single quotes inside single quotes require special handling */
267fa615556Sabhinav 		if (c == '\'' && s_quoted) {
268fa615556Sabhinav 			escaped_str[offset++] = '\'';
269fa615556Sabhinav 			escaped_str[offset++] = '\\';
270fa615556Sabhinav 			escaped_str[offset++] = '\'';
271fa615556Sabhinav 			escaped_str[offset++] = '\'';
272fa615556Sabhinav 			continue;
273fa615556Sabhinav 		}
274fa615556Sabhinav 
275fa615556Sabhinav 		/* Otherwise no escaping needed inside single quotes */
276fa615556Sabhinav 		if (s_quoted) {
277fa615556Sabhinav 			escaped_str[offset++] = c;
278fa615556Sabhinav 			continue;
279fa615556Sabhinav 		}
280fa615556Sabhinav 
281fa615556Sabhinav 		/* No escaping needed inside a double quoted string either
282fa615556Sabhinav 		 * unless we see a '$', '\', '`', or '"' (itself)
283fa615556Sabhinav 		 */
2847512edc2Schristos 		if (d_quoted && !needs_dquote_escaping(c)) {
285fa615556Sabhinav 			escaped_str[offset++] = c;
286fa615556Sabhinav 			continue;
287fa615556Sabhinav 		}
288fa615556Sabhinav 
289fa615556Sabhinav 		/* If we reach here that means escaping is actually needed */
290fa615556Sabhinav 		escaped_str[offset++] = '\\';
291fa615556Sabhinav 		escaped_str[offset++] = c;
292fa615556Sabhinav 	}
293fa615556Sabhinav 
294c3d00a7dSabhinav 	if (single_match && app_func) {
295c3d00a7dSabhinav 		escaped_str[offset] = 0;
296b9455193Schristos 		append_char = app_func(filename);
297c3d00a7dSabhinav 		/* we want to append space only if we are not inside quotes */
298c3d00a7dSabhinav 		if (append_char[0] == ' ') {
299c3d00a7dSabhinav 			if (!s_quoted && !d_quoted)
300c3d00a7dSabhinav 				escaped_str[offset++] = append_char[0];
301c3d00a7dSabhinav 		} else
302c3d00a7dSabhinav 			escaped_str[offset++] = append_char[0];
303c3d00a7dSabhinav 	}
304c3d00a7dSabhinav 
305c3d00a7dSabhinav 	/* close the quotes if single match and the match is not a directory */
306c3d00a7dSabhinav 	if (single_match && (append_char && append_char[0] == ' ')) {
307fa615556Sabhinav 		if (s_quoted)
308fa615556Sabhinav 			escaped_str[offset++] = '\'';
309fa615556Sabhinav 		else if (d_quoted)
310fa615556Sabhinav 			escaped_str[offset++] = '"';
311c3d00a7dSabhinav 	}
312fa615556Sabhinav 
313fa615556Sabhinav 	escaped_str[offset] = 0;
314fa615556Sabhinav 	return escaped_str;
315fa615556Sabhinav }
31641a59814Sdsl 
31741a59814Sdsl /*
31841a59814Sdsl  * return first found file name starting by the ``text'' or NULL if no
31941a59814Sdsl  * such file can be found
32041a59814Sdsl  * value of ``state'' is ignored
32141a59814Sdsl  *
322f0a7346dSsnj  * it's the caller's responsibility to free the returned string
32341a59814Sdsl  */
3246ddc453eSdsl char *
fn_filename_completion_function(const char * text,int state)32519c38590Schristos fn_filename_completion_function(const char *text, int state)
32641a59814Sdsl {
32741a59814Sdsl 	static DIR *dir = NULL;
3282e685adeSdsl 	static char *filename = NULL, *dirname = NULL, *dirpath = NULL;
32941a59814Sdsl 	static size_t filename_len = 0;
33041a59814Sdsl 	struct dirent *entry;
33141a59814Sdsl 	char *temp;
3325679adf8Schristos 	const char *pos;
33341a59814Sdsl 	size_t len;
33441a59814Sdsl 
33541a59814Sdsl 	if (state == 0 || dir == NULL) {
3365679adf8Schristos 		pos = strrchr(text, '/');
3375679adf8Schristos 		if (pos) {
33841a59814Sdsl 			char *nptr;
3395679adf8Schristos 			pos++;
3405679adf8Schristos 			nptr = el_realloc(filename, (strlen(pos) + 1) *
341a13cd756Schristos 			    sizeof(*nptr));
34241a59814Sdsl 			if (nptr == NULL) {
343a13cd756Schristos 				el_free(filename);
344cbd798c9Schristos 				filename = NULL;
34541a59814Sdsl 				return NULL;
34641a59814Sdsl 			}
34741a59814Sdsl 			filename = nptr;
3485679adf8Schristos 			(void)strcpy(filename, pos);
3495679adf8Schristos 			len = (size_t)(pos - text);	/* including last slash */
350cbd798c9Schristos 
351a13cd756Schristos 			nptr = el_realloc(dirname, (len + 1) *
352a13cd756Schristos 			    sizeof(*nptr));
35341a59814Sdsl 			if (nptr == NULL) {
354a13cd756Schristos 				el_free(dirname);
355cbd798c9Schristos 				dirname = NULL;
35641a59814Sdsl 				return NULL;
35741a59814Sdsl 			}
35841a59814Sdsl 			dirname = nptr;
359a47ebb18Schristos 			(void)strlcpy(dirname, text, len + 1);
36041a59814Sdsl 		} else {
361a13cd756Schristos 			el_free(filename);
36241a59814Sdsl 			if (*text == 0)
36341a59814Sdsl 				filename = NULL;
36441a59814Sdsl 			else {
36541a59814Sdsl 				filename = strdup(text);
36641a59814Sdsl 				if (filename == NULL)
36741a59814Sdsl 					return NULL;
36841a59814Sdsl 			}
369a13cd756Schristos 			el_free(dirname);
37041a59814Sdsl 			dirname = NULL;
37141a59814Sdsl 		}
37241a59814Sdsl 
37341a59814Sdsl 		if (dir != NULL) {
37441a59814Sdsl 			(void)closedir(dir);
37541a59814Sdsl 			dir = NULL;
37641a59814Sdsl 		}
3772e685adeSdsl 
3782e685adeSdsl 		/* support for ``~user'' syntax */
379cbd798c9Schristos 
380a13cd756Schristos 		el_free(dirpath);
381cbd798c9Schristos 		dirpath = NULL;
382cbd798c9Schristos 		if (dirname == NULL) {
383cbd798c9Schristos 			if ((dirname = strdup("")) == NULL)
384182beb15Schristos 				return NULL;
385cbd798c9Schristos 			dirpath = strdup("./");
386cbd798c9Schristos 		} else if (*dirname == '~')
38719c38590Schristos 			dirpath = fn_tilde_expand(dirname);
388182beb15Schristos 		else
389182beb15Schristos 			dirpath = strdup(dirname);
390182beb15Schristos 
3912e685adeSdsl 		if (dirpath == NULL)
3922e685adeSdsl 			return NULL;
393182beb15Schristos 
3942e685adeSdsl 		dir = opendir(dirpath);
39541a59814Sdsl 		if (!dir)
396b71bed95Schristos 			return NULL;	/* cannot open the directory */
3972e685adeSdsl 
3982e685adeSdsl 		/* will be used in cycle */
3992e685adeSdsl 		filename_len = filename ? strlen(filename) : 0;
40041a59814Sdsl 	}
40141a59814Sdsl 
40241a59814Sdsl 	/* find the match */
40341a59814Sdsl 	while ((entry = readdir(dir)) != NULL) {
40441a59814Sdsl 		/* skip . and .. */
40541a59814Sdsl 		if (entry->d_name[0] == '.' && (!entry->d_name[1]
40641a59814Sdsl 		    || (entry->d_name[1] == '.' && !entry->d_name[2])))
40741a59814Sdsl 			continue;
40841a59814Sdsl 		if (filename_len == 0)
40941a59814Sdsl 			break;
41041a59814Sdsl 		/* otherwise, get first entry where first */
41141a59814Sdsl 		/* filename_len characters are equal	  */
41241a59814Sdsl 		if (entry->d_name[0] == filename[0]
41369a442faSapb #if HAVE_STRUCT_DIRENT_D_NAMLEN
41441a59814Sdsl 		    && entry->d_namlen >= filename_len
41569a442faSapb #else
41669a442faSapb 		    && strlen(entry->d_name) >= filename_len
41741a59814Sdsl #endif
41841a59814Sdsl 		    && strncmp(entry->d_name, filename,
41941a59814Sdsl 			filename_len) == 0)
42041a59814Sdsl 			break;
42141a59814Sdsl 	}
42241a59814Sdsl 
42341a59814Sdsl 	if (entry) {		/* match found */
4242e685adeSdsl 
42569a442faSapb #if HAVE_STRUCT_DIRENT_D_NAMLEN
4262e685adeSdsl 		len = entry->d_namlen;
42769a442faSapb #else
42869a442faSapb 		len = strlen(entry->d_name);
42941a59814Sdsl #endif
43041a59814Sdsl 
431a13cd756Schristos 		len = strlen(dirname) + len + 1;
432113f06a3Schristos 		temp = el_calloc(len, sizeof(*temp));
4332e685adeSdsl 		if (temp == NULL)
4342e685adeSdsl 			return NULL;
435a13cd756Schristos 		(void)snprintf(temp, len, "%s%s", dirname, entry->d_name);
43641a59814Sdsl 	} else {
43741a59814Sdsl 		(void)closedir(dir);
43841a59814Sdsl 		dir = NULL;
43941a59814Sdsl 		temp = NULL;
44041a59814Sdsl 	}
44141a59814Sdsl 
442b71bed95Schristos 	return temp;
44341a59814Sdsl }
44441a59814Sdsl 
44541a59814Sdsl 
4463cfbfdb2Schristos static const char *
append_char_function(const char * name)4473cfbfdb2Schristos append_char_function(const char *name)
4483cfbfdb2Schristos {
4493cfbfdb2Schristos 	struct stat stbuf;
45019c38590Schristos 	char *expname = *name == '~' ? fn_tilde_expand(name) : NULL;
4513cfbfdb2Schristos 	const char *rs = " ";
45241a59814Sdsl 
4533cfbfdb2Schristos 	if (stat(expname ? expname : name, &stbuf) == -1)
4543cfbfdb2Schristos 		goto out;
4553cfbfdb2Schristos 	if (S_ISDIR(stbuf.st_mode))
4563cfbfdb2Schristos 		rs = "/";
4573cfbfdb2Schristos out:
4583cfbfdb2Schristos 	if (expname)
459a13cd756Schristos 		el_free(expname);
4603cfbfdb2Schristos 	return rs;
4613cfbfdb2Schristos }
462*4ee54736Schristos 
46341a59814Sdsl /*
46441a59814Sdsl  * returns list of completions for text given
46533b05629Schristos  * non-static for readline.
46641a59814Sdsl  */
46733b05629Schristos char **
completion_matches(const char * text,char * (* genfunc)(const char *,int))46841a59814Sdsl completion_matches(const char *text, char *(*genfunc)(const char *, int))
46941a59814Sdsl {
47041a59814Sdsl 	char **match_list = NULL, *retstr, *prevstr;
47141a59814Sdsl 	size_t match_list_len, max_equal, which, i;
47241a59814Sdsl 	size_t matches;
47341a59814Sdsl 
47441a59814Sdsl 	matches = 0;
47541a59814Sdsl 	match_list_len = 1;
47641a59814Sdsl 	while ((retstr = (*genfunc) (text, (int)matches)) != NULL) {
47741a59814Sdsl 		/* allow for list terminator here */
47841a59814Sdsl 		if (matches + 3 >= match_list_len) {
47941a59814Sdsl 			char **nmatch_list;
48041a59814Sdsl 			while (matches + 3 >= match_list_len)
48141a59814Sdsl 				match_list_len <<= 1;
482a13cd756Schristos 			nmatch_list = el_realloc(match_list,
483a13cd756Schristos 			    match_list_len * sizeof(*nmatch_list));
48441a59814Sdsl 			if (nmatch_list == NULL) {
485a13cd756Schristos 				el_free(match_list);
48641a59814Sdsl 				return NULL;
48741a59814Sdsl 			}
48841a59814Sdsl 			match_list = nmatch_list;
48941a59814Sdsl 
49041a59814Sdsl 		}
49141a59814Sdsl 		match_list[++matches] = retstr;
49241a59814Sdsl 	}
49341a59814Sdsl 
49441a59814Sdsl 	if (!match_list)
49541a59814Sdsl 		return NULL;	/* nothing found */
49641a59814Sdsl 
49741a59814Sdsl 	/* find least denominator and insert it to match_list[0] */
49841a59814Sdsl 	which = 2;
49941a59814Sdsl 	prevstr = match_list[1];
50041a59814Sdsl 	max_equal = strlen(prevstr);
50141a59814Sdsl 	for (; which <= matches; which++) {
50241a59814Sdsl 		for (i = 0; i < max_equal &&
50341a59814Sdsl 		    prevstr[i] == match_list[which][i]; i++)
50441a59814Sdsl 			continue;
50541a59814Sdsl 		max_equal = i;
50641a59814Sdsl 	}
50741a59814Sdsl 
508113f06a3Schristos 	retstr = el_calloc(max_equal + 1, sizeof(*retstr));
50941a59814Sdsl 	if (retstr == NULL) {
510a13cd756Schristos 		el_free(match_list);
51141a59814Sdsl 		return NULL;
51241a59814Sdsl 	}
513a47ebb18Schristos 	(void)strlcpy(retstr, match_list[1], max_equal + 1);
51441a59814Sdsl 	match_list[0] = retstr;
51541a59814Sdsl 
51641a59814Sdsl 	/* add NULL as last pointer to the array */
5172b8aaed8Splunky 	match_list[matches + 1] = NULL;
51841a59814Sdsl 
519b71bed95Schristos 	return match_list;
52041a59814Sdsl }
52141a59814Sdsl 
52241a59814Sdsl /*
52341a59814Sdsl  * Sort function for qsort(). Just wrapper around strcasecmp().
52441a59814Sdsl  */
52541a59814Sdsl static int
_fn_qsort_string_compare(const void * i1,const void * i2)52641a59814Sdsl _fn_qsort_string_compare(const void *i1, const void *i2)
52741a59814Sdsl {
52841a59814Sdsl 	const char *s1 = ((const char * const *)i1)[0];
52941a59814Sdsl 	const char *s2 = ((const char * const *)i2)[0];
53041a59814Sdsl 
53141a59814Sdsl 	return strcasecmp(s1, s2);
53241a59814Sdsl }
53341a59814Sdsl 
53441a59814Sdsl /*
53541a59814Sdsl  * Display list of strings in columnar format on readline's output stream.
53692417c82Sdholland  * 'matches' is list of strings, 'num' is number of strings in 'matches',
53792417c82Sdholland  * 'width' is maximum length of string in 'matches'.
53892417c82Sdholland  *
539eb1ab8eeSdholland  * matches[0] is not one of the match strings, but it is counted in
540eb1ab8eeSdholland  * num, so the strings are matches[1] *through* matches[num-1].
54141a59814Sdsl  */
54241a59814Sdsl void
fn_display_match_list(EditLine * el,char ** matches,size_t num,size_t width,const char * (* app_func)(const char *))5433a89c576Sabhinav fn_display_match_list(EditLine * el, char **matches, size_t num, size_t width,
5443a89c576Sabhinav     const char *(*app_func) (const char *))
54541a59814Sdsl {
54692417c82Sdholland 	size_t line, lines, col, cols, thisguy;
547b2105969Schristos 	int screenwidth = el->el_terminal.t_size.h;
5483a89c576Sabhinav 	if (app_func == NULL)
5493a89c576Sabhinav 		app_func = append_char_function;
55041a59814Sdsl 
55192417c82Sdholland 	/* Ignore matches[0]. Avoid 1-based array logic below. */
55292417c82Sdholland 	matches++;
553eb1ab8eeSdholland 	num--;
55492417c82Sdholland 
55541a59814Sdsl 	/*
55692417c82Sdholland 	 * Find out how many entries can be put on one line; count
55792417c82Sdholland 	 * with one space between strings the same way it's printed.
55841a59814Sdsl 	 */
559272963aeSchristos 	cols = (size_t)screenwidth / (width + 2);
56092417c82Sdholland 	if (cols == 0)
56192417c82Sdholland 		cols = 1;
56241a59814Sdsl 
56392417c82Sdholland 	/* how many lines of output, rounded up */
56492417c82Sdholland 	lines = (num + cols - 1) / cols;
56541a59814Sdsl 
56692417c82Sdholland 	/* Sort the items. */
56792417c82Sdholland 	qsort(matches, num, sizeof(char *), _fn_qsort_string_compare);
56841a59814Sdsl 
56992417c82Sdholland 	/*
57092417c82Sdholland 	 * On the ith line print elements i, i+lines, i+lines*2, etc.
57192417c82Sdholland 	 */
57292417c82Sdholland 	for (line = 0; line < lines; line++) {
57392417c82Sdholland 		for (col = 0; col < cols; col++) {
57492417c82Sdholland 			thisguy = line + col * lines;
57592417c82Sdholland 			if (thisguy >= num)
57692417c82Sdholland 				break;
5773a89c576Sabhinav 			(void)fprintf(el->el_outfile, "%s%s%s",
5783a89c576Sabhinav 			    col == 0 ? "" : " ", matches[thisguy],
579272963aeSchristos 				(*app_func)(matches[thisguy]));
5803a89c576Sabhinav 			(void)fprintf(el->el_outfile, "%-*s",
5813a89c576Sabhinav 				(int) (width - strlen(matches[thisguy])), "");
582b90e7198Schristos 		}
58341a59814Sdsl 		(void)fprintf(el->el_outfile, "\n");
58441a59814Sdsl 	}
58541a59814Sdsl }
58641a59814Sdsl 
5872afc179cSabhinav static wchar_t *
find_word_to_complete(const wchar_t * cursor,const wchar_t * buffer,const wchar_t * word_break,const wchar_t * special_prefixes,size_t * length,int do_unescape)5882afc179cSabhinav find_word_to_complete(const wchar_t * cursor, const wchar_t * buffer,
5892d114b3cSabhinav     const wchar_t * word_break, const wchar_t * special_prefixes, size_t * length,
5902d114b3cSabhinav 	int do_unescape)
5912afc179cSabhinav {
5922afc179cSabhinav 	/* We now look backwards for the start of a filename/variable word */
5932afc179cSabhinav 	const wchar_t *ctemp = cursor;
5942d114b3cSabhinav 	wchar_t *temp;
5952afc179cSabhinav 	size_t len;
5962afc179cSabhinav 
5972afc179cSabhinav 	/* if the cursor is placed at a slash or a quote, we need to find the
5982afc179cSabhinav 	 * word before it
5992afc179cSabhinav 	 */
6002afc179cSabhinav 	if (ctemp > buffer) {
6012afc179cSabhinav 		switch (ctemp[-1]) {
6022afc179cSabhinav 		case '\\':
6032afc179cSabhinav 		case '\'':
6042afc179cSabhinav 		case '"':
6052afc179cSabhinav 			ctemp--;
6062afc179cSabhinav 			break;
6072afc179cSabhinav 		default:
608c93cba3aSabhinav 			break;
6092afc179cSabhinav 		}
610c93cba3aSabhinav 	}
611c93cba3aSabhinav 
612c93cba3aSabhinav 	for (;;) {
613c93cba3aSabhinav 		if (ctemp <= buffer)
614c93cba3aSabhinav 			break;
615d1647d9cSchristos 		if (ctemp - buffer >= 2 && ctemp[-2] == '\\' &&
616d1647d9cSchristos 		    needs_escaping(ctemp[-1])) {
617c93cba3aSabhinav 			ctemp -= 2;
618c93cba3aSabhinav 			continue;
61952de8937Stih 		}
620d1647d9cSchristos 		if (wcschr(word_break, ctemp[-1]))
621c93cba3aSabhinav 			break;
622c93cba3aSabhinav 		if (special_prefixes && wcschr(special_prefixes, ctemp[-1]))
623c93cba3aSabhinav 			break;
6242afc179cSabhinav 		ctemp--;
625c93cba3aSabhinav 	}
6262afc179cSabhinav 
627c93cba3aSabhinav 	len = (size_t) (cursor - ctemp);
628c3d00a7dSabhinav 	if (len == 1 && (ctemp[0] == '\'' || ctemp[0] == '"')) {
629c3d00a7dSabhinav 		len = 0;
630c3d00a7dSabhinav 		ctemp++;
631c3d00a7dSabhinav 	}
6322afc179cSabhinav 	*length = len;
6332d114b3cSabhinav 	if (do_unescape) {
634c93cba3aSabhinav 		wchar_t *unescaped_word = unescape_string(ctemp, len);
635c93cba3aSabhinav 		if (unescaped_word == NULL)
636c93cba3aSabhinav 			return NULL;
637c93cba3aSabhinav 		return unescaped_word;
6382afc179cSabhinav 	}
6392d114b3cSabhinav 	temp = el_malloc((len + 1) * sizeof(*temp));
64021a4b1ccSchristos 	if (temp == NULL)
64121a4b1ccSchristos 		return NULL;
6422d114b3cSabhinav 	(void) wcsncpy(temp, ctemp, len);
6432d114b3cSabhinav 	temp[len] = '\0';
6442d114b3cSabhinav 	return temp;
6452d114b3cSabhinav }
6462afc179cSabhinav 
64741a59814Sdsl /*
64841a59814Sdsl  * Complete the word at or before point,
64941a59814Sdsl  * 'what_to_do' says what to do with the completion.
65041a59814Sdsl  * \t   means do standard completion.
65141a59814Sdsl  * `?' means list the possible completions.
65241a59814Sdsl  * `*' means insert all of the possible completions.
65341a59814Sdsl  * `!' means to do standard completion, and list all possible completions if
65441a59814Sdsl  * there is more than one.
65541a59814Sdsl  *
65641a59814Sdsl  * Note: '*' support is not implemented
65741a59814Sdsl  *       '!' could never be invoked
65841a59814Sdsl  */
65941a59814Sdsl int
fn_complete2(EditLine * el,char * (* complete_func)(const char *,int),char ** (* attempted_completion_function)(const char *,int,int),const wchar_t * word_break,const wchar_t * special_prefixes,const char * (* app_func)(const char *),size_t query_items,int * completion_type,int * over,int * point,int * end,unsigned int flags)66065371df8Schristos fn_complete2(EditLine *el,
66165371df8Schristos     char *(*complete_func)(const char *, int),
66241a59814Sdsl     char **(*attempted_completion_function)(const char *, int, int),
6630594af80Schristos     const wchar_t *word_break, const wchar_t *special_prefixes,
6647939d24eSchristos     const char *(*app_func)(const char *), size_t query_items,
66565371df8Schristos     int *completion_type, int *over, int *point, int *end,
66665371df8Schristos     unsigned int flags)
66741a59814Sdsl {
6680aefc7f9Schristos 	const LineInfoW *li;
6690594af80Schristos 	wchar_t *temp;
67034e53048Schristos 	char **matches;
671c93cba3aSabhinav 	char *completion;
67241a59814Sdsl 	size_t len;
67341a59814Sdsl 	int what_to_do = '\t';
67472301cb0Schristos 	int retval = CC_NORM;
675ba06fc1cSchristos 	int do_unescape = flags & FN_QUOTE_MATCH;
67641a59814Sdsl 
67741a59814Sdsl 	if (el->el_state.lastcmd == el->el_state.thiscmd)
67841a59814Sdsl 		what_to_do = '?';
67941a59814Sdsl 
68041a59814Sdsl 	/* readline's rl_complete() has to be told what we did... */
68141a59814Sdsl 	if (completion_type != NULL)
68241a59814Sdsl 		*completion_type = what_to_do;
68341a59814Sdsl 
68465371df8Schristos 	if (!complete_func)
68565371df8Schristos 		complete_func = fn_filename_completion_function;
6863cfbfdb2Schristos 	if (!app_func)
6873cfbfdb2Schristos 		app_func = append_char_function;
68841a59814Sdsl 
6890aefc7f9Schristos 	li = el_wline(el);
6902afc179cSabhinav 	temp = find_word_to_complete(li->cursor,
6912d114b3cSabhinav 	    li->buffer, word_break, special_prefixes, &len, do_unescape);
6923c9fe72fSabhinav 	if (temp == NULL)
6933c9fe72fSabhinav 		goto out;
69441a59814Sdsl 
69541a59814Sdsl 	/* these can be used by function called in completion_matches() */
69641a59814Sdsl 	/* or (*attempted_completion_function)() */
6972dd09931Schristos 	if (point != NULL)
6985c894153Schristos 		*point = (int)(li->cursor - li->buffer);
69941a59814Sdsl 	if (end != NULL)
7005c894153Schristos 		*end = (int)(li->lastchar - li->buffer);
70141a59814Sdsl 
70241a59814Sdsl 	if (attempted_completion_function) {
7035c894153Schristos 		int cur_off = (int)(li->cursor - li->buffer);
7043d802cf5Schristos 		matches = (*attempted_completion_function)(
7053d802cf5Schristos 		    ct_encode_string(temp, &el->el_scratch),
7063d802cf5Schristos 		    cur_off - (int)len, cur_off);
70741a59814Sdsl 	} else
7082dd09931Schristos 		matches = NULL;
70941a59814Sdsl 	if (!attempted_completion_function ||
710262f96a2Schristos 	    (over != NULL && !*over && !matches))
7113d802cf5Schristos 		matches = completion_matches(
71265371df8Schristos 		    ct_encode_string(temp, &el->el_scratch), complete_func);
71341a59814Sdsl 
71441a59814Sdsl 	if (over != NULL)
71541a59814Sdsl 		*over = 0;
71641a59814Sdsl 
7175e9cbb11Schristos 	if (matches == NULL) {
7185e9cbb11Schristos 		goto out;
7195e9cbb11Schristos 	}
72072301cb0Schristos 	int i;
7215c894153Schristos 	size_t matches_num, maxlen, match_len, match_display=1;
722fa615556Sabhinav 	int single_match = matches[2] == NULL &&
723fa615556Sabhinav 		(matches[1] == NULL || strcmp(matches[0], matches[1]) == 0);
72441a59814Sdsl 
72572301cb0Schristos 	retval = CC_REFRESH;
726fa615556Sabhinav 
727fa615556Sabhinav 	if (matches[0][0] != '\0') {
728fa615556Sabhinav 		el_deletestr(el, (int)len);
72965371df8Schristos 		if (flags & FN_QUOTE_MATCH)
730c3d00a7dSabhinav 			completion = escape_filename(el, matches[0],
731c3d00a7dSabhinav 			    single_match, app_func);
732e09538bdSabhinav 		else
733e09538bdSabhinav 			completion = strdup(matches[0]);
734e09538bdSabhinav 		if (completion == NULL)
73520fa0b90Schristos 			goto out2;
7365e9cbb11Schristos 
7375e9cbb11Schristos 		/*
7385e9cbb11Schristos 		 * Replace the completed string with the common part of
7395e9cbb11Schristos 		 * all possible matches if there is a possible completion.
7405e9cbb11Schristos 		 */
7415e9cbb11Schristos 		el_winsertstr(el,
7425e9cbb11Schristos 		    ct_decode_string(completion, &el->el_scratch));
7435e9cbb11Schristos 
744ba06fc1cSchristos 		if (single_match && attempted_completion_function &&
745ba06fc1cSchristos 		    !(flags & FN_QUOTE_MATCH))
746ba06fc1cSchristos 		{
7475e9cbb11Schristos 			/*
7485e9cbb11Schristos 			 * We found an exact match. Add a space after
7495e9cbb11Schristos 			 * it, unless we do filename completion and the
750c3d00a7dSabhinav 			 * object is a directory. Also do necessary
751c3d00a7dSabhinav 			 * escape quoting
752c93cba3aSabhinav 			 */
7535e9cbb11Schristos 			el_winsertstr(el, ct_decode_string(
7545e9cbb11Schristos 			    (*app_func)(completion), &el->el_scratch));
75541a59814Sdsl 		}
756c93cba3aSabhinav 		free(completion);
757fa615556Sabhinav 	}
75841a59814Sdsl 
75941a59814Sdsl 
760fa615556Sabhinav 	if (!single_match && (what_to_do == '!' || what_to_do == '?')) {
76141a59814Sdsl 		/*
76241a59814Sdsl 		 * More than one match and requested to list possible
76341a59814Sdsl 		 * matches.
76441a59814Sdsl 		 */
76541a59814Sdsl 
76641a59814Sdsl 		for(i = 1, maxlen = 0; matches[i]; i++) {
76741a59814Sdsl 			match_len = strlen(matches[i]);
76841a59814Sdsl 			if (match_len > maxlen)
76941a59814Sdsl 				maxlen = match_len;
77041a59814Sdsl 		}
77192417c82Sdholland 		/* matches[1] through matches[i-1] are available */
7723d802cf5Schristos 		matches_num = (size_t)(i - 1);
77341a59814Sdsl 
77441a59814Sdsl 		/* newline to get on next line from command line */
77541a59814Sdsl 		(void)fprintf(el->el_outfile, "\n");
77641a59814Sdsl 
77741a59814Sdsl 		/*
77841a59814Sdsl 		 * If there are too many items, ask user for display
77941a59814Sdsl 		 * confirmation.
78041a59814Sdsl 		 */
78141a59814Sdsl 		if (matches_num > query_items) {
78241a59814Sdsl 			(void)fprintf(el->el_outfile,
7835c894153Schristos 			    "Display all %zu possibilities? (y or n) ",
78441a59814Sdsl 			    matches_num);
78541a59814Sdsl 			(void)fflush(el->el_outfile);
78641a59814Sdsl 			if (getc(stdin) != 'y')
78741a59814Sdsl 				match_display = 0;
78841a59814Sdsl 			(void)fprintf(el->el_outfile, "\n");
78941a59814Sdsl 		}
79041a59814Sdsl 
791eb1ab8eeSdholland 		if (match_display) {
792eb1ab8eeSdholland 			/*
793eb1ab8eeSdholland 			 * Interface of this function requires the
794eb1ab8eeSdholland 			 * strings be matches[1..num-1] for compat.
795eb1ab8eeSdholland 			 * We have matches_num strings not counting
796eb1ab8eeSdholland 			 * the prefix in matches[0], so we need to
797eb1ab8eeSdholland 			 * add 1 to matches_num for the call.
798eb1ab8eeSdholland 			 */
799eb1ab8eeSdholland 			fn_display_match_list(el, matches,
8003a89c576Sabhinav 			    matches_num+1, maxlen, app_func);
801eb1ab8eeSdholland 		}
80241a59814Sdsl 		retval = CC_REDISPLAY;
80341a59814Sdsl 	} else if (matches[0][0]) {
80441a59814Sdsl 		/*
80541a59814Sdsl 		 * There was some common match, but the name was
80641a59814Sdsl 		 * not complete enough. Next tab will print possible
80741a59814Sdsl 		 * completions.
80841a59814Sdsl 		 */
80941a59814Sdsl 		el_beep(el);
81041a59814Sdsl 	} else {
81141a59814Sdsl 		/* lcd is not a valid object - further specification */
81241a59814Sdsl 		/* is needed */
81341a59814Sdsl 		el_beep(el);
81441a59814Sdsl 		retval = CC_NORM;
81541a59814Sdsl 	}
81641a59814Sdsl 
81741a59814Sdsl 	/* free elements of array and the array itself */
81820fa0b90Schristos out2:
81941a59814Sdsl 	for (i = 0; matches[i]; i++)
820a13cd756Schristos 		el_free(matches[i]);
821a13cd756Schristos 	el_free(matches);
82272301cb0Schristos 	matches = NULL;
8233c9fe72fSabhinav 
8243c9fe72fSabhinav out:
825a13cd756Schristos 	el_free(temp);
82672301cb0Schristos 	return retval;
82741a59814Sdsl }
82841a59814Sdsl 
82965371df8Schristos int
fn_complete(EditLine * el,char * (* complete_func)(const char *,int),char ** (* attempted_completion_function)(const char *,int,int),const wchar_t * word_break,const wchar_t * special_prefixes,const char * (* app_func)(const char *),size_t query_items,int * completion_type,int * over,int * point,int * end)83065371df8Schristos fn_complete(EditLine *el,
83165371df8Schristos     char *(*complete_func)(const char *, int),
83265371df8Schristos     char **(*attempted_completion_function)(const char *, int, int),
83365371df8Schristos     const wchar_t *word_break, const wchar_t *special_prefixes,
83465371df8Schristos     const char *(*app_func)(const char *), size_t query_items,
83565371df8Schristos     int *completion_type, int *over, int *point, int *end)
83665371df8Schristos {
83765371df8Schristos 	return fn_complete2(el, complete_func, attempted_completion_function,
83865371df8Schristos 	    word_break, special_prefixes, app_func, query_items,
83965371df8Schristos 	    completion_type, over, point, end,
84065371df8Schristos 	    attempted_completion_function ? 0 : FN_QUOTE_MATCH);
84165371df8Schristos }
84265371df8Schristos 
84341a59814Sdsl /*
84441a59814Sdsl  * el-compatible wrapper around rl_complete; needed for key binding
84541a59814Sdsl  */
84641a59814Sdsl /* ARGSUSED */
84741a59814Sdsl unsigned char
_el_fn_complete(EditLine * el,int ch)84841a59814Sdsl _el_fn_complete(EditLine *el, int ch __attribute__((__unused__)))
84941a59814Sdsl {
85041a59814Sdsl 	return (unsigned char)fn_complete(el, NULL, NULL,
851c11bd863Schristos 	    break_chars, NULL, NULL, (size_t)100,
85241a59814Sdsl 	    NULL, NULL, NULL, NULL);
85341a59814Sdsl }
8543ce4aa66Schristos 
8553ce4aa66Schristos /*
8563ce4aa66Schristos  * el-compatible wrapper around rl_complete; needed for key binding
8573ce4aa66Schristos  */
8583ce4aa66Schristos /* ARGSUSED */
8593ce4aa66Schristos unsigned char
_el_fn_sh_complete(EditLine * el,int ch)8603ce4aa66Schristos _el_fn_sh_complete(EditLine *el, int ch)
8613ce4aa66Schristos {
8623ce4aa66Schristos 	return _el_fn_complete(el, ch);
8633ce4aa66Schristos }
864