filecomplete.c revision 1.36
1/*	$NetBSD: filecomplete.c,v 1.36 2016/02/16 14:08:25 christos Exp $	*/
2
3/*-
4 * Copyright (c) 1997 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jaromir Dolecek.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#if !defined(lint) && !defined(SCCSID)
34__RCSID("$NetBSD: filecomplete.c,v 1.36 2016/02/16 14:08:25 christos Exp $");
35#endif /* not lint && not SCCSID */
36
37#include <sys/types.h>
38#include <sys/stat.h>
39#include <stdio.h>
40#include <dirent.h>
41#include <string.h>
42#include <pwd.h>
43#include <ctype.h>
44#include <stdlib.h>
45#include <unistd.h>
46#include <limits.h>
47#include <errno.h>
48#include <fcntl.h>
49
50#include "histedit.h"
51#include "chartype.h"
52#include "el.h"
53#include "fcns.h"		/* for EL_NUM_FCNS */
54#include "filecomplete.h"
55
56static const Char break_chars[] = { ' ', '\t', '\n', '"', '\\', '\'', '`', '@',
57    '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0' };
58
59
60/********************************/
61/* completion functions */
62
63/*
64 * does tilde expansion of strings of type ``~user/foo''
65 * if ``user'' isn't valid user name or ``txt'' doesn't start
66 * w/ '~', returns pointer to strdup()ed copy of ``txt''
67 *
68 * it's the caller's responsibility to free() the returned string
69 */
70char *
71fn_tilde_expand(const char *txt)
72{
73#if defined(HAVE_GETPW_R_POSIX) || defined(HAVE_GETPW_R_DRAFT)
74	struct passwd pwres;
75	char pwbuf[1024];
76#endif
77	struct passwd *pass;
78	char *temp;
79	size_t len = 0;
80
81	if (txt[0] != '~')
82		return strdup(txt);
83
84	temp = strchr(txt + 1, '/');
85	if (temp == NULL) {
86		temp = strdup(txt + 1);
87		if (temp == NULL)
88			return NULL;
89	} else {
90		/* text until string after slash */
91		len = (size_t)(temp - txt + 1);
92		temp = el_malloc(len * sizeof(*temp));
93		if (temp == NULL)
94			return NULL;
95		(void)strncpy(temp, txt + 1, len - 2);
96		temp[len - 2] = '\0';
97	}
98	if (temp[0] == 0) {
99#ifdef HAVE_GETPW_R_POSIX
100 		if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf),
101		    &pass) != 0)
102 			pass = NULL;
103#elif HAVE_GETPW_R_DRAFT
104		pass = getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf));
105#else
106		pass = getpwuid(getuid());
107#endif
108	} else {
109#ifdef HAVE_GETPW_R_POSIX
110		if (getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf), &pass) != 0)
111			pass = NULL;
112#elif HAVE_GETPW_R_DRAFT
113		pass = getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf));
114#else
115		pass = getpwnam(temp);
116#endif
117	}
118	el_free(temp);		/* value no more needed */
119	if (pass == NULL)
120		return strdup(txt);
121
122	/* update pointer txt to point at string immedially following */
123	/* first slash */
124	txt += len;
125
126	len = strlen(pass->pw_dir) + 1 + strlen(txt) + 1;
127	temp = el_malloc(len * sizeof(*temp));
128	if (temp == NULL)
129		return NULL;
130	(void)snprintf(temp, len, "%s/%s", pass->pw_dir, txt);
131
132	return temp;
133}
134
135
136/*
137 * return first found file name starting by the ``text'' or NULL if no
138 * such file can be found
139 * value of ``state'' is ignored
140 *
141 * it's the caller's responsibility to free the returned string
142 */
143char *
144fn_filename_completion_function(const char *text, int state)
145{
146	static DIR *dir = NULL;
147	static char *filename = NULL, *dirname = NULL, *dirpath = NULL;
148	static size_t filename_len = 0;
149	struct dirent *entry;
150	char *temp;
151	size_t len;
152
153	if (state == 0 || dir == NULL) {
154		temp = strrchr(text, '/');
155		if (temp) {
156			char *nptr;
157			temp++;
158			nptr = el_realloc(filename, (strlen(temp) + 1) *
159			    sizeof(*nptr));
160			if (nptr == NULL) {
161				el_free(filename);
162				filename = NULL;
163				return NULL;
164			}
165			filename = nptr;
166			(void)strcpy(filename, temp);
167			len = (size_t)(temp - text);	/* including last slash */
168
169			nptr = el_realloc(dirname, (len + 1) *
170			    sizeof(*nptr));
171			if (nptr == NULL) {
172				el_free(dirname);
173				dirname = NULL;
174				return NULL;
175			}
176			dirname = nptr;
177			(void)strncpy(dirname, text, len);
178			dirname[len] = '\0';
179		} else {
180			el_free(filename);
181			if (*text == 0)
182				filename = NULL;
183			else {
184				filename = strdup(text);
185				if (filename == NULL)
186					return NULL;
187			}
188			el_free(dirname);
189			dirname = NULL;
190		}
191
192		if (dir != NULL) {
193			(void)closedir(dir);
194			dir = NULL;
195		}
196
197		/* support for ``~user'' syntax */
198
199		el_free(dirpath);
200		dirpath = NULL;
201		if (dirname == NULL) {
202			if ((dirname = strdup("")) == NULL)
203				return NULL;
204			dirpath = strdup("./");
205		} else if (*dirname == '~')
206			dirpath = fn_tilde_expand(dirname);
207		else
208			dirpath = strdup(dirname);
209
210		if (dirpath == NULL)
211			return NULL;
212
213		dir = opendir(dirpath);
214		if (!dir)
215			return NULL;	/* cannot open the directory */
216
217		/* will be used in cycle */
218		filename_len = filename ? strlen(filename) : 0;
219	}
220
221	/* find the match */
222	while ((entry = readdir(dir)) != NULL) {
223		/* skip . and .. */
224		if (entry->d_name[0] == '.' && (!entry->d_name[1]
225		    || (entry->d_name[1] == '.' && !entry->d_name[2])))
226			continue;
227		if (filename_len == 0)
228			break;
229		/* otherwise, get first entry where first */
230		/* filename_len characters are equal	  */
231		if (entry->d_name[0] == filename[0]
232#if HAVE_STRUCT_DIRENT_D_NAMLEN
233		    && entry->d_namlen >= filename_len
234#else
235		    && strlen(entry->d_name) >= filename_len
236#endif
237		    && strncmp(entry->d_name, filename,
238			filename_len) == 0)
239			break;
240	}
241
242	if (entry) {		/* match found */
243
244#if HAVE_STRUCT_DIRENT_D_NAMLEN
245		len = entry->d_namlen;
246#else
247		len = strlen(entry->d_name);
248#endif
249
250		len = strlen(dirname) + len + 1;
251		temp = el_malloc(len * sizeof(*temp));
252		if (temp == NULL)
253			return NULL;
254		(void)snprintf(temp, len, "%s%s", dirname, entry->d_name);
255	} else {
256		(void)closedir(dir);
257		dir = NULL;
258		temp = NULL;
259	}
260
261	return temp;
262}
263
264
265static const char *
266append_char_function(const char *name)
267{
268	struct stat stbuf;
269	char *expname = *name == '~' ? fn_tilde_expand(name) : NULL;
270	const char *rs = " ";
271
272	if (stat(expname ? expname : name, &stbuf) == -1)
273		goto out;
274	if (S_ISDIR(stbuf.st_mode))
275		rs = "/";
276out:
277	if (expname)
278		el_free(expname);
279	return rs;
280}
281/*
282 * returns list of completions for text given
283 * non-static for readline.
284 */
285char ** completion_matches(const char *, char *(*)(const char *, int));
286char **
287completion_matches(const char *text, char *(*genfunc)(const char *, int))
288{
289	char **match_list = NULL, *retstr, *prevstr;
290	size_t match_list_len, max_equal, which, i;
291	size_t matches;
292
293	matches = 0;
294	match_list_len = 1;
295	while ((retstr = (*genfunc) (text, (int)matches)) != NULL) {
296		/* allow for list terminator here */
297		if (matches + 3 >= match_list_len) {
298			char **nmatch_list;
299			while (matches + 3 >= match_list_len)
300				match_list_len <<= 1;
301			nmatch_list = el_realloc(match_list,
302			    match_list_len * sizeof(*nmatch_list));
303			if (nmatch_list == NULL) {
304				el_free(match_list);
305				return NULL;
306			}
307			match_list = nmatch_list;
308
309		}
310		match_list[++matches] = retstr;
311	}
312
313	if (!match_list)
314		return NULL;	/* nothing found */
315
316	/* find least denominator and insert it to match_list[0] */
317	which = 2;
318	prevstr = match_list[1];
319	max_equal = strlen(prevstr);
320	for (; which <= matches; which++) {
321		for (i = 0; i < max_equal &&
322		    prevstr[i] == match_list[which][i]; i++)
323			continue;
324		max_equal = i;
325	}
326
327	retstr = el_malloc((max_equal + 1) * sizeof(*retstr));
328	if (retstr == NULL) {
329		el_free(match_list);
330		return NULL;
331	}
332	(void)strncpy(retstr, match_list[1], max_equal);
333	retstr[max_equal] = '\0';
334	match_list[0] = retstr;
335
336	/* add NULL as last pointer to the array */
337	match_list[matches + 1] = NULL;
338
339	return match_list;
340}
341
342/*
343 * Sort function for qsort(). Just wrapper around strcasecmp().
344 */
345static int
346_fn_qsort_string_compare(const void *i1, const void *i2)
347{
348	const char *s1 = ((const char * const *)i1)[0];
349	const char *s2 = ((const char * const *)i2)[0];
350
351	return strcasecmp(s1, s2);
352}
353
354/*
355 * Display list of strings in columnar format on readline's output stream.
356 * 'matches' is list of strings, 'num' is number of strings in 'matches',
357 * 'width' is maximum length of string in 'matches'.
358 *
359 * matches[0] is not one of the match strings, but it is counted in
360 * num, so the strings are matches[1] *through* matches[num-1].
361 */
362void
363fn_display_match_list (EditLine *el, char **matches, size_t num, size_t width)
364{
365	size_t line, lines, col, cols, thisguy;
366	int screenwidth = el->el_terminal.t_size.h;
367
368	/* Ignore matches[0]. Avoid 1-based array logic below. */
369	matches++;
370	num--;
371
372	/*
373	 * Find out how many entries can be put on one line; count
374	 * with one space between strings the same way it's printed.
375	 */
376	cols = (size_t)screenwidth / (width + 1);
377	if (cols == 0)
378		cols = 1;
379
380	/* how many lines of output, rounded up */
381	lines = (num + cols - 1) / cols;
382
383	/* Sort the items. */
384	qsort(matches, num, sizeof(char *), _fn_qsort_string_compare);
385
386	/*
387	 * On the ith line print elements i, i+lines, i+lines*2, etc.
388	 */
389	for (line = 0; line < lines; line++) {
390		for (col = 0; col < cols; col++) {
391			thisguy = line + col * lines;
392			if (thisguy >= num)
393				break;
394			(void)fprintf(el->el_outfile, "%s%-*s",
395			    col == 0 ? "" : " ", (int)width, matches[thisguy]);
396		}
397		(void)fprintf(el->el_outfile, "\n");
398	}
399}
400
401/*
402 * Complete the word at or before point,
403 * 'what_to_do' says what to do with the completion.
404 * \t   means do standard completion.
405 * `?' means list the possible completions.
406 * `*' means insert all of the possible completions.
407 * `!' means to do standard completion, and list all possible completions if
408 * there is more than one.
409 *
410 * Note: '*' support is not implemented
411 *       '!' could never be invoked
412 */
413int
414fn_complete(EditLine *el,
415	char *(*complet_func)(const char *, int),
416	char **(*attempted_completion_function)(const char *, int, int),
417	const Char *word_break, const Char *special_prefixes,
418	const char *(*app_func)(const char *), size_t query_items,
419	int *completion_type, int *over, int *point, int *end)
420{
421	const TYPE(LineInfo) *li;
422	Char *temp;
423        char **matches;
424	const Char *ctemp;
425	size_t len;
426	int what_to_do = '\t';
427	int retval = CC_NORM;
428
429	if (el->el_state.lastcmd == el->el_state.thiscmd)
430		what_to_do = '?';
431
432	/* readline's rl_complete() has to be told what we did... */
433	if (completion_type != NULL)
434		*completion_type = what_to_do;
435
436	if (!complet_func)
437		complet_func = fn_filename_completion_function;
438	if (!app_func)
439		app_func = append_char_function;
440
441	/* We now look backwards for the start of a filename/variable word */
442	li = FUN(el,line)(el);
443	ctemp = li->cursor;
444	while (ctemp > li->buffer
445	    && !Strchr(word_break, ctemp[-1])
446	    && (!special_prefixes || !Strchr(special_prefixes, ctemp[-1]) ) )
447		ctemp--;
448
449	len = (size_t)(li->cursor - ctemp);
450	temp = el_malloc((len + 1) * sizeof(*temp));
451	(void)Strncpy(temp, ctemp, len);
452	temp[len] = '\0';
453
454	/* these can be used by function called in completion_matches() */
455	/* or (*attempted_completion_function)() */
456	if (point != 0)
457		*point = (int)(li->cursor - li->buffer);
458	if (end != NULL)
459		*end = (int)(li->lastchar - li->buffer);
460
461	if (attempted_completion_function) {
462		int cur_off = (int)(li->cursor - li->buffer);
463		matches = (*attempted_completion_function)(
464		    ct_encode_string(temp, &el->el_scratch),
465		    cur_off - (int)len, cur_off);
466	} else
467		matches = 0;
468	if (!attempted_completion_function ||
469	    (over != NULL && !*over && !matches))
470		matches = completion_matches(
471		    ct_encode_string(temp, &el->el_scratch), complet_func);
472
473	if (over != NULL)
474		*over = 0;
475
476	if (matches) {
477		int i;
478		size_t matches_num, maxlen, match_len, match_display=1;
479
480		retval = CC_REFRESH;
481		/*
482		 * Only replace the completed string with common part of
483		 * possible matches if there is possible completion.
484		 */
485		if (matches[0][0] != '\0') {
486			el_deletestr(el, (int) len);
487			FUN(el,insertstr)(el,
488			    ct_decode_string(matches[0], &el->el_scratch));
489		}
490
491		if (what_to_do == '?')
492			goto display_matches;
493
494		if (matches[2] == NULL &&
495		    (matches[1] == NULL || strcmp(matches[0], matches[1]) == 0)) {
496			/*
497			 * We found exact match. Add a space after
498			 * it, unless we do filename completion and the
499			 * object is a directory.
500			 */
501			FUN(el,insertstr)(el,
502			    ct_decode_string((*app_func)(matches[0]),
503			    &el->el_scratch));
504		} else if (what_to_do == '!') {
505    display_matches:
506			/*
507			 * More than one match and requested to list possible
508			 * matches.
509			 */
510
511			for(i = 1, maxlen = 0; matches[i]; i++) {
512				match_len = strlen(matches[i]);
513				if (match_len > maxlen)
514					maxlen = match_len;
515			}
516			/* matches[1] through matches[i-1] are available */
517			matches_num = (size_t)(i - 1);
518
519			/* newline to get on next line from command line */
520			(void)fprintf(el->el_outfile, "\n");
521
522			/*
523			 * If there are too many items, ask user for display
524			 * confirmation.
525			 */
526			if (matches_num > query_items) {
527				(void)fprintf(el->el_outfile,
528				    "Display all %zu possibilities? (y or n) ",
529				    matches_num);
530				(void)fflush(el->el_outfile);
531				if (getc(stdin) != 'y')
532					match_display = 0;
533				(void)fprintf(el->el_outfile, "\n");
534			}
535
536			if (match_display) {
537				/*
538				 * Interface of this function requires the
539				 * strings be matches[1..num-1] for compat.
540				 * We have matches_num strings not counting
541				 * the prefix in matches[0], so we need to
542				 * add 1 to matches_num for the call.
543				 */
544				fn_display_match_list(el, matches,
545				    matches_num+1, maxlen);
546			}
547			retval = CC_REDISPLAY;
548		} else if (matches[0][0]) {
549			/*
550			 * There was some common match, but the name was
551			 * not complete enough. Next tab will print possible
552			 * completions.
553			 */
554			el_beep(el);
555		} else {
556			/* lcd is not a valid object - further specification */
557			/* is needed */
558			el_beep(el);
559			retval = CC_NORM;
560		}
561
562		/* free elements of array and the array itself */
563		for (i = 0; matches[i]; i++)
564			el_free(matches[i]);
565		el_free(matches);
566		matches = NULL;
567	}
568	el_free(temp);
569	return retval;
570}
571
572/*
573 * el-compatible wrapper around rl_complete; needed for key binding
574 */
575/* ARGSUSED */
576unsigned char
577_el_fn_complete(EditLine *el, int ch __attribute__((__unused__)))
578{
579	return (unsigned char)fn_complete(el, NULL, NULL,
580	    break_chars, NULL, NULL, (size_t)100,
581	    NULL, NULL, NULL, NULL);
582}
583