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