1 /* $OpenBSD: complete.c,v 1.19 2006/06/23 20:35:25 steven Exp $ */ 2 /* $NetBSD: complete.c,v 1.10 1997/08/18 10:20:18 lukem Exp $ */ 3 4 /*- 5 * Copyright (c) 1997 The NetBSD Foundation, Inc. 6 * All rights reserved. 7 * 8 * This code is derived from software contributed to The NetBSD Foundation 9 * by Luke Mewburn. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. All advertising materials mentioning features or use of this software 20 * must display the following acknowledgement: 21 * This product includes software developed by the NetBSD 22 * Foundation, Inc. and its contributors. 23 * 4. Neither the name of The NetBSD Foundation nor the names of its 24 * contributors may be used to endorse or promote products derived 25 * from this software without specific prior written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 30 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 * POSSIBILITY OF SUCH DAMAGE. 38 */ 39 40 #ifndef SMALL 41 #ifndef lint 42 static const char rcsid[] = "$OpenBSD: complete.c,v 1.19 2006/06/23 20:35:25 steven Exp $"; 43 #endif /* not lint */ 44 45 /* 46 * FTP user program - command and file completion routines 47 */ 48 49 #include <ctype.h> 50 #include <err.h> 51 #include <dirent.h> 52 #include <stdio.h> 53 #include <stdlib.h> 54 #include <string.h> 55 56 #include "ftp_var.h" 57 58 static int comparstr(const void *, const void *); 59 static unsigned char complete_ambiguous(char *, int, StringList *); 60 static unsigned char complete_command(char *, int); 61 static unsigned char complete_local(char *, int); 62 static unsigned char complete_remote(char *, int); 63 64 static int 65 comparstr(const void *a, const void *b) 66 { 67 return (strcmp(*(char **)a, *(char **)b)); 68 } 69 70 /* 71 * Determine if complete is ambiguous. If unique, insert. 72 * If no choices, error. If unambiguous prefix, insert that. 73 * Otherwise, list choices. words is assumed to be filtered 74 * to only contain possible choices. 75 * Args: 76 * word word which started the match 77 * list list by default 78 * words stringlist containing possible matches 79 */ 80 static unsigned char 81 complete_ambiguous(char *word, int list, StringList *words) 82 { 83 char insertstr[MAXPATHLEN]; 84 char *lastmatch; 85 int i, j; 86 size_t matchlen, wordlen; 87 88 wordlen = strlen(word); 89 if (words->sl_cur == 0) 90 return (CC_ERROR); /* no choices available */ 91 92 if (words->sl_cur == 1) { /* only once choice available */ 93 (void)strlcpy(insertstr, words->sl_str[0], sizeof insertstr); 94 if (el_insertstr(el, insertstr + wordlen) == -1) 95 return (CC_ERROR); 96 else 97 return (CC_REFRESH); 98 } 99 100 if (!list) { 101 matchlen = 0; 102 lastmatch = words->sl_str[0]; 103 matchlen = strlen(lastmatch); 104 for (i = 1 ; i < words->sl_cur ; i++) { 105 for (j = wordlen ; j < strlen(words->sl_str[i]); j++) 106 if (lastmatch[j] != words->sl_str[i][j]) 107 break; 108 if (j < matchlen) 109 matchlen = j; 110 } 111 if (matchlen > wordlen) { 112 (void)strlcpy(insertstr, lastmatch, matchlen+1); 113 if (el_insertstr(el, insertstr + wordlen) == -1) 114 return (CC_ERROR); 115 else 116 /* 117 * XXX: really want CC_REFRESH_BEEP 118 */ 119 return (CC_REFRESH); 120 } 121 } 122 123 putc('\n', ttyout); 124 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr); 125 list_vertical(words); 126 return (CC_REDISPLAY); 127 } 128 129 /* 130 * Complete a command 131 */ 132 static unsigned char 133 complete_command(char *word, int list) 134 { 135 struct cmd *c; 136 StringList *words; 137 size_t wordlen; 138 unsigned char rv; 139 140 words = sl_init(); 141 wordlen = strlen(word); 142 143 for (c = cmdtab; c->c_name != NULL; c++) { 144 if (wordlen > strlen(c->c_name)) 145 continue; 146 if (strncmp(word, c->c_name, wordlen) == 0) 147 sl_add(words, c->c_name); 148 } 149 150 rv = complete_ambiguous(word, list, words); 151 sl_free(words, 0); 152 return (rv); 153 } 154 155 /* 156 * Complete a local file 157 */ 158 static unsigned char 159 complete_local(char *word, int list) 160 { 161 StringList *words; 162 char dir[MAXPATHLEN]; 163 char *file; 164 DIR *dd; 165 struct dirent *dp; 166 unsigned char rv; 167 168 if ((file = strrchr(word, '/')) == NULL) { 169 dir[0] = '.'; 170 dir[1] = '\0'; 171 file = word; 172 } else { 173 if (file == word) { 174 dir[0] = '/'; 175 dir[1] = '\0'; 176 } else { 177 (void)strlcpy(dir, word, (size_t)(file - word) + 1); 178 } 179 file++; 180 } 181 182 if ((dd = opendir(dir)) == NULL) 183 return (CC_ERROR); 184 185 words = sl_init(); 186 187 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { 188 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 189 continue; 190 if (strlen(file) > dp->d_namlen) 191 continue; 192 if (strncmp(file, dp->d_name, strlen(file)) == 0) { 193 char *tcp; 194 195 tcp = strdup(dp->d_name); 196 if (tcp == NULL) 197 errx(1, "Can't allocate memory for local dir"); 198 sl_add(words, tcp); 199 } 200 } 201 closedir(dd); 202 203 rv = complete_ambiguous(file, list, words); 204 sl_free(words, 1); 205 return (rv); 206 } 207 208 /* 209 * Complete a remote file 210 */ 211 static unsigned char 212 complete_remote(char *word, int list) 213 { 214 static StringList *dirlist; 215 static char lastdir[MAXPATHLEN]; 216 StringList *words; 217 char dir[MAXPATHLEN]; 218 char *file, *cp; 219 int i; 220 unsigned char rv; 221 222 char *dummyargv[] = { "complete", dir, NULL }; 223 224 if ((file = strrchr(word, '/')) == NULL) { 225 dir[0] = '.'; 226 dir[1] = '\0'; 227 file = word; 228 } else { 229 cp = file; 230 while (*cp == '/' && cp > word) 231 cp--; 232 (void)strlcpy(dir, word, (size_t)(cp - word + 2)); 233 file++; 234 } 235 236 if (dirchange || strcmp(dir, lastdir) != 0) { /* dir not cached */ 237 char *emesg; 238 239 sl_free(dirlist, 1); 240 dirlist = sl_init(); 241 242 mflag = 1; 243 emesg = NULL; 244 if (debug) 245 (void)putc('\n', ttyout); 246 while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) { 247 char *tcp; 248 249 if (!mflag) 250 continue; 251 if (*cp == '\0') { 252 mflag = 0; 253 continue; 254 } 255 tcp = strrchr(cp, '/'); 256 if (tcp) 257 tcp++; 258 else 259 tcp = cp; 260 tcp = strdup(tcp); 261 if (tcp == NULL) 262 errx(1, "Can't allocate memory for remote dir"); 263 sl_add(dirlist, tcp); 264 } 265 if (emesg != NULL) { 266 fprintf(ttyout, "\n%s\n", emesg); 267 return (CC_REDISPLAY); 268 } 269 (void)strlcpy(lastdir, dir, sizeof lastdir); 270 dirchange = 0; 271 } 272 273 words = sl_init(); 274 for (i = 0; i < dirlist->sl_cur; i++) { 275 cp = dirlist->sl_str[i]; 276 if (strlen(file) > strlen(cp)) 277 continue; 278 if (strncmp(file, cp, strlen(file)) == 0) 279 sl_add(words, cp); 280 } 281 rv = complete_ambiguous(file, list, words); 282 sl_free(words, 0); 283 return (rv); 284 } 285 286 /* 287 * Generic complete routine 288 */ 289 unsigned char 290 complete(EditLine *el, int ch) 291 { 292 static char word[FTPBUFLEN]; 293 static int lastc_argc, lastc_argo; 294 struct cmd *c; 295 const LineInfo *lf; 296 int celems, dolist; 297 size_t len; 298 299 ch = ch; /* not used */ 300 lf = el_line(el); 301 len = lf->lastchar - lf->buffer; 302 if (len >= sizeof(line)) 303 return (CC_ERROR); 304 (void)memcpy(line, lf->buffer, len); 305 line[len] = '\0'; 306 cursor_pos = line + (lf->cursor - lf->buffer); 307 lastc_argc = cursor_argc; /* remember last cursor pos */ 308 lastc_argo = cursor_argo; 309 makeargv(); /* build argc/argv of current line */ 310 311 if (cursor_argo >= sizeof(word)) 312 return (CC_ERROR); 313 314 dolist = 0; 315 /* if cursor and word is same, list alternatives */ 316 if (lastc_argc == cursor_argc && lastc_argo == cursor_argo 317 && strncmp(word, margv[cursor_argc], cursor_argo) == 0) 318 dolist = 1; 319 else if (cursor_argo) 320 memcpy(word, margv[cursor_argc], cursor_argo); 321 word[cursor_argo] = '\0'; 322 323 if (cursor_argc == 0) 324 return (complete_command(word, dolist)); 325 326 c = getcmd(margv[0]); 327 if (c == (struct cmd *)-1 || c == 0) 328 return (CC_ERROR); 329 celems = strlen(c->c_complete); 330 331 /* check for 'continuation' completes (which are uppercase) */ 332 if ((cursor_argc > celems) && (celems > 0) 333 && isupper(c->c_complete[celems-1])) 334 cursor_argc = celems; 335 336 if (cursor_argc > celems) 337 return (CC_ERROR); 338 339 switch (c->c_complete[cursor_argc - 1]) { 340 case 'l': /* local complete */ 341 case 'L': 342 return (complete_local(word, dolist)); 343 case 'r': /* remote complete */ 344 case 'R': 345 if (connected != -1) { 346 fputs("\nMust be logged in to complete.\n", ttyout); 347 return (CC_REDISPLAY); 348 } 349 return (complete_remote(word, dolist)); 350 case 'c': /* command complete */ 351 case 'C': 352 return (complete_command(word, dolist)); 353 case 'n': /* no complete */ 354 return (CC_ERROR); 355 } 356 357 return (CC_ERROR); 358 } 359 360 #endif /* !SMALL */ 361