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