complete.c revision 1.17 1 /* $NetBSD: complete.c,v 1.17 1999/02/07 12:27:50 lukem 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 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 #ifndef SMALL
40
41 #include <sys/cdefs.h>
42 #ifndef lint
43 __RCSID("$NetBSD: complete.c,v 1.17 1999/02/07 12:27:50 lukem Exp $");
44 #endif /* not lint */
45
46 /*
47 * FTP user program - command and file completion routines
48 */
49
50 #include <sys/stat.h>
51
52 #include <ctype.h>
53 #include <err.h>
54 #include <dirent.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58
59 #include "ftp_var.h"
60
61 static int comparstr __P((const void *, const void *));
62 static unsigned char complete_ambiguous __P((char *, int, StringList *));
63 static unsigned char complete_command __P((char *, int));
64 static unsigned char complete_local __P((char *, int));
65 static unsigned char complete_remote __P((char *, int));
66
67 static int
68 comparstr(a, b)
69 const void *a, *b;
70 {
71 return (strcmp(*(char **)a, *(char **)b));
72 }
73
74 /*
75 * Determine if complete is ambiguous. If unique, insert.
76 * If no choices, error. If unambiguous prefix, insert that.
77 * Otherwise, list choices. words is assumed to be filtered
78 * to only contain possible choices.
79 * Args:
80 * word word which started the match
81 * list list by default
82 * words stringlist containing possible matches
83 * Returns a result as per el_set(EL_ADDFN, ...)
84 */
85 static unsigned char
86 complete_ambiguous(word, list, words)
87 char *word;
88 int list;
89 StringList *words;
90 {
91 char insertstr[MAXPATHLEN];
92 char *lastmatch;
93 int i, j;
94 size_t matchlen, wordlen;
95
96 wordlen = strlen(word);
97 if (words->sl_cur == 0)
98 return (CC_ERROR); /* no choices available */
99
100 if (words->sl_cur == 1) { /* only once choice available */
101 (void)strcpy(insertstr, words->sl_str[0]);
102 if (el_insertstr(el, insertstr + wordlen) == -1)
103 return (CC_ERROR);
104 else
105 return (CC_REFRESH);
106 }
107
108 if (!list) {
109 matchlen = 0;
110 lastmatch = words->sl_str[0];
111 matchlen = strlen(lastmatch);
112 for (i = 1 ; i < words->sl_cur ; i++) {
113 for (j = wordlen ; j < strlen(words->sl_str[i]); j++)
114 if (lastmatch[j] != words->sl_str[i][j])
115 break;
116 if (j < matchlen)
117 matchlen = j;
118 }
119 if (matchlen > wordlen) {
120 (void)strncpy(insertstr, lastmatch, matchlen);
121 insertstr[matchlen] = '\0';
122 if (el_insertstr(el, insertstr + wordlen) == -1)
123 return (CC_ERROR);
124 else
125 return (CC_REFRESH_BEEP);
126 }
127 }
128
129 putc('\n', ttyout);
130 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
131 list_vertical(words);
132 return (CC_REDISPLAY);
133 }
134
135 /*
136 * Complete a command
137 */
138 static unsigned char
139 complete_command(word, list)
140 char *word;
141 int list;
142 {
143 struct cmd *c;
144 StringList *words;
145 size_t wordlen;
146 unsigned char rv;
147
148 words = sl_init();
149 wordlen = strlen(word);
150
151 for (c = cmdtab; c->c_name != NULL; c++) {
152 if (wordlen > strlen(c->c_name))
153 continue;
154 if (strncmp(word, c->c_name, wordlen) == 0)
155 sl_add(words, c->c_name);
156 }
157
158 rv = complete_ambiguous(word, list, words);
159 if (rv == CC_REFRESH) {
160 if (el_insertstr(el, " ") == -1)
161 rv = CC_ERROR;
162 }
163 sl_free(words, 0);
164 return (rv);
165 }
166
167 /*
168 * Complete a local file
169 */
170 static unsigned char
171 complete_local(word, list)
172 char *word;
173 int list;
174 {
175 StringList *words;
176 char dir[MAXPATHLEN];
177 char *file;
178 DIR *dd;
179 struct dirent *dp;
180 unsigned char rv;
181 size_t len;
182
183 if ((file = strrchr(word, '/')) == NULL) {
184 dir[0] = '.';
185 dir[1] = '\0';
186 file = word;
187 } else {
188 if (file == word) {
189 dir[0] = '/';
190 dir[1] = '\0';
191 } else {
192 (void)strncpy(dir, word, file - word);
193 dir[file - word] = '\0';
194 }
195 file++;
196 }
197 if (dir[0] == '~') {
198 char *p;
199
200 p = dir;
201 if (!globulize(&p))
202 return (CC_ERROR);
203 if (p != dir) {
204 strncpy(dir, p, sizeof(dir));
205 dir[sizeof(dir)-1] = '\0';
206 free(p);
207 }
208 }
209
210 if ((dd = opendir(dir)) == NULL)
211 return (CC_ERROR);
212
213 words = sl_init();
214
215 len = strlen(file);
216
217 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
218 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
219 continue;
220
221 #ifndef __SVR4
222 if (len > dp->d_namlen)
223 continue;
224 #else
225 if (len > strlen(dp->d_name))
226 continue;
227 #endif
228 if (strncmp(file, dp->d_name, len) == 0) {
229 char *tcp;
230
231 tcp = xstrdup(dp->d_name);
232 sl_add(words, tcp);
233 }
234 }
235 closedir(dd);
236
237 rv = complete_ambiguous(file, list, words);
238 if (rv == CC_REFRESH) {
239 struct stat sb;
240 char path[MAXPATHLEN];
241
242 snprintf(path, sizeof(path), "%s/%s", dir, words->sl_str[0]);
243 if (stat(path, &sb) >= 0) {
244 char suffix[2] = " ";
245
246 if (S_ISDIR(sb.st_mode))
247 suffix[0] = '/';
248 if (el_insertstr(el, suffix) == -1)
249 rv = CC_ERROR;
250 }
251 }
252 sl_free(words, 1);
253 return (rv);
254 }
255
256 /*
257 * Complete a remote file
258 */
259 static unsigned char
260 complete_remote(word, list)
261 char *word;
262 int list;
263 {
264 static StringList *dirlist;
265 static char lastdir[MAXPATHLEN];
266 StringList *words;
267 char dir[MAXPATHLEN];
268 char *file, *cp;
269 int i;
270 unsigned char rv;
271
272 char *dummyargv[] = { "complete", NULL, NULL };
273 dummyargv[1] = dir;
274
275 if ((file = strrchr(word, '/')) == NULL) {
276 dir[0] = '.';
277 dir[1] = '\0';
278 file = word;
279 } else {
280 cp = file;
281 while (*cp == '/' && cp > word)
282 cp--;
283 (void)strncpy(dir, word, cp - word + 1);
284 dir[cp - word + 1] = '\0';
285 file++;
286 }
287
288 if (dirchange || strcmp(dir, lastdir) != 0) { /* dir not cached */
289 char *emesg;
290
291 if (dirlist != NULL)
292 sl_free(dirlist, 1);
293 dirlist = sl_init();
294
295 mflag = 1;
296 emesg = NULL;
297 while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) {
298 char *tcp;
299
300 if (!mflag)
301 continue;
302 if (*cp == '\0') {
303 mflag = 0;
304 continue;
305 }
306 tcp = strrchr(cp, '/');
307 if (tcp)
308 tcp++;
309 else
310 tcp = cp;
311 tcp = xstrdup(tcp);
312 sl_add(dirlist, tcp);
313 }
314 if (emesg != NULL) {
315 fprintf(ttyout, "\n%s\n", emesg);
316 return (CC_REDISPLAY);
317 }
318 (void)strcpy(lastdir, dir);
319 dirchange = 0;
320 }
321
322 words = sl_init();
323 for (i = 0; i < dirlist->sl_cur; i++) {
324 cp = dirlist->sl_str[i];
325 if (strlen(file) > strlen(cp))
326 continue;
327 if (strncmp(file, cp, strlen(file)) == 0)
328 sl_add(words, cp);
329 }
330 rv = complete_ambiguous(file, list, words);
331 sl_free(words, 0);
332 return (rv);
333 }
334
335 /*
336 * Generic complete routine
337 */
338 unsigned char
339 complete(el, ch)
340 EditLine *el;
341 int ch;
342 {
343 static char word[FTPBUFLEN];
344 static int lastc_argc, lastc_argo;
345
346 struct cmd *c;
347 const LineInfo *lf;
348 int celems, dolist;
349 size_t len;
350
351 lf = el_line(el);
352 len = lf->lastchar - lf->buffer;
353 if (len >= sizeof(line))
354 return (CC_ERROR);
355 (void)strncpy(line, lf->buffer, len);
356 line[len] = '\0';
357 cursor_pos = line + (lf->cursor - lf->buffer);
358 lastc_argc = cursor_argc; /* remember last cursor pos */
359 lastc_argo = cursor_argo;
360 makeargv(); /* build argc/argv of current line */
361
362 if (cursor_argo >= sizeof(word))
363 return (CC_ERROR);
364
365 dolist = 0;
366 /* if cursor and word is same, list alternatives */
367 if (lastc_argc == cursor_argc && lastc_argo == cursor_argo
368 && strncmp(word, margv[cursor_argc], cursor_argo) == 0)
369 dolist = 1;
370 else
371 (void)strncpy(word, margv[cursor_argc], cursor_argo);
372 word[cursor_argo] = '\0';
373
374 if (cursor_argc == 0)
375 return (complete_command(word, dolist));
376
377 c = getcmd(margv[0]);
378 if (c == (struct cmd *)-1 || c == 0)
379 return (CC_ERROR);
380 celems = strlen(c->c_complete);
381
382 /* check for 'continuation' completes (which are uppercase) */
383 if ((cursor_argc > celems) && (celems > 0)
384 && isupper((unsigned char) c->c_complete[celems-1]))
385 cursor_argc = celems;
386
387 if (cursor_argc > celems)
388 return (CC_ERROR);
389
390 switch (c->c_complete[cursor_argc - 1]) {
391 case 'l': /* local complete */
392 case 'L':
393 return (complete_local(word, dolist));
394 case 'r': /* remote complete */
395 case 'R':
396 if (connected != -1) {
397 fputs("\nMust be logged in to complete.\n",
398 ttyout);
399 return (CC_REDISPLAY);
400 }
401 return (complete_remote(word, dolist));
402 case 'c': /* command complete */
403 case 'C':
404 return (complete_command(word, dolist));
405 case 'n': /* no complete */
406 default:
407 return (CC_ERROR);
408 }
409
410 return (CC_ERROR);
411 }
412
413 #endif /* !SMALL */
414