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