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