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