1 /* $NetBSD: complete.c,v 1.36 1999/11/28 06:32:04 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.36 1999/11/28 06:32:04 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 = xsl_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 xsl_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 = xsl_init(); 210 len = strlen(file); 211 212 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { 213 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 214 continue; 215 216 #if defined(DIRENT_MISSING_D_NAMLEN) 217 if (len > strlen(dp->d_name)) 218 continue; 219 #else 220 if (len > dp->d_namlen) 221 continue; 222 #endif 223 if (strncmp(file, dp->d_name, len) == 0) { 224 char *tcp; 225 226 tcp = xstrdup(dp->d_name); 227 xsl_add(words, tcp); 228 } 229 } 230 closedir(dd); 231 232 rv = complete_ambiguous(file, list, words); 233 if (rv == CC_REFRESH) { 234 struct stat sb; 235 char path[MAXPATHLEN]; 236 237 (void)strlcpy(path, dir, sizeof(path)); 238 (void)strlcat(path, "/", sizeof(path)); 239 (void)strlcat(path, words->sl_str[0], sizeof(path)); 240 241 if (stat(path, &sb) >= 0) { 242 char suffix[2] = " "; 243 244 if (S_ISDIR(sb.st_mode)) 245 suffix[0] = '/'; 246 if (el_insertstr(el, suffix) == -1) 247 rv = CC_ERROR; 248 } 249 } 250 sl_free(words, 1); 251 return (rv); 252 } 253 /* 254 * Complete an option 255 */ 256 static unsigned char 257 complete_option(word, list) 258 char *word; 259 int list; 260 { 261 struct option *o; 262 StringList *words; 263 size_t wordlen; 264 unsigned char rv; 265 266 words = xsl_init(); 267 wordlen = strlen(word); 268 269 for (o = optiontab; o->name != NULL; o++) { 270 if (wordlen > strlen(o->name)) 271 continue; 272 if (strncmp(word, o->name, wordlen) == 0) 273 xsl_add(words, o->name); 274 } 275 276 rv = complete_ambiguous(word, list, words); 277 if (rv == CC_REFRESH) { 278 if (el_insertstr(el, " ") == -1) 279 rv = CC_ERROR; 280 } 281 sl_free(words, 0); 282 return (rv); 283 } 284 285 /* 286 * Complete a remote file 287 */ 288 static unsigned char 289 complete_remote(word, list) 290 char *word; 291 int list; 292 { 293 static StringList *dirlist; 294 static char lastdir[MAXPATHLEN]; 295 StringList *words; 296 char dir[MAXPATHLEN]; 297 char *file, *cp; 298 int i; 299 unsigned char rv; 300 301 char *dummyargv[] = { "complete", NULL, NULL }; 302 dummyargv[1] = dir; 303 304 if ((file = strrchr(word, '/')) == NULL) { 305 dir[0] = '\0'; 306 file = word; 307 } else { 308 cp = file; 309 while (*cp == '/' && cp > word) 310 cp--; 311 (void)strlcpy(dir, word, cp - word + 2); 312 file++; 313 } 314 315 if (dirchange || dirlist == NULL || 316 strcmp(dir, lastdir) != 0) { /* dir not cached */ 317 char *emesg; 318 319 if (dirlist != NULL) 320 sl_free(dirlist, 1); 321 dirlist = xsl_init(); 322 323 mflag = 1; 324 emesg = NULL; 325 while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) { 326 char *tcp; 327 328 if (!mflag) 329 continue; 330 if (*cp == '\0') { 331 mflag = 0; 332 continue; 333 } 334 tcp = strrchr(cp, '/'); 335 if (tcp) 336 tcp++; 337 else 338 tcp = cp; 339 tcp = xstrdup(tcp); 340 xsl_add(dirlist, tcp); 341 } 342 if (emesg != NULL) { 343 fprintf(ttyout, "\n%s\n", emesg); 344 return (CC_REDISPLAY); 345 } 346 (void)strlcpy(lastdir, dir, sizeof(lastdir)); 347 dirchange = 0; 348 } 349 350 words = xsl_init(); 351 for (i = 0; i < dirlist->sl_cur; i++) { 352 cp = dirlist->sl_str[i]; 353 if (strlen(file) > strlen(cp)) 354 continue; 355 if (strncmp(file, cp, strlen(file)) == 0) 356 xsl_add(words, cp); 357 } 358 rv = complete_ambiguous(file, list, words); 359 sl_free(words, 0); 360 return (rv); 361 } 362 363 /* 364 * Generic complete routine 365 */ 366 unsigned char 367 complete(el, ch) 368 EditLine *el; 369 int ch; 370 { 371 static char word[FTPBUFLEN]; 372 static int lastc_argc, lastc_argo; 373 374 struct cmd *c; 375 const LineInfo *lf; 376 int celems, dolist, cmpltype; 377 size_t len; 378 379 lf = el_line(el); 380 len = lf->lastchar - lf->buffer; 381 if (len >= sizeof(line)) 382 return (CC_ERROR); 383 (void)strlcpy(line, lf->buffer, len + 1); 384 cursor_pos = line + (lf->cursor - lf->buffer); 385 lastc_argc = cursor_argc; /* remember last cursor pos */ 386 lastc_argo = cursor_argo; 387 makeargv(); /* build argc/argv of current line */ 388 389 if (cursor_argo >= sizeof(word)) 390 return (CC_ERROR); 391 392 dolist = 0; 393 /* if cursor and word is same, list alternatives */ 394 if (lastc_argc == cursor_argc && lastc_argo == cursor_argo 395 && strncmp(word, margv[cursor_argc] ? margv[cursor_argc] : "", 396 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