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