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