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