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