1*bafbd613Schristos /* $NetBSD: complete.c,v 1.48 2024/09/25 16:53:58 christos Exp $ */ 27a7fa807Slukem 37a7fa807Slukem /*- 43d4efc35Slukem * Copyright (c) 1997-2009 The NetBSD Foundation, Inc. 57a7fa807Slukem * All rights reserved. 67a7fa807Slukem * 77a7fa807Slukem * This code is derived from software contributed to The NetBSD Foundation 87a7fa807Slukem * by Luke Mewburn. 97a7fa807Slukem * 107a7fa807Slukem * Redistribution and use in source and binary forms, with or without 117a7fa807Slukem * modification, are permitted provided that the following conditions 127a7fa807Slukem * are met: 137a7fa807Slukem * 1. Redistributions of source code must retain the above copyright 147a7fa807Slukem * notice, this list of conditions and the following disclaimer. 157a7fa807Slukem * 2. Redistributions in binary form must reproduce the above copyright 167a7fa807Slukem * notice, this list of conditions and the following disclaimer in the 177a7fa807Slukem * documentation and/or other materials provided with the distribution. 187a7fa807Slukem * 197a7fa807Slukem * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 207a7fa807Slukem * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 217a7fa807Slukem * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 222fec2a28Slukem * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 232fec2a28Slukem * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 247a7fa807Slukem * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 257a7fa807Slukem * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 267a7fa807Slukem * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 277a7fa807Slukem * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 287a7fa807Slukem * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 297a7fa807Slukem * POSSIBILITY OF SUCH DAMAGE. 307a7fa807Slukem */ 317a7fa807Slukem 32b9d5554dSlukem #include <sys/cdefs.h> 337a7fa807Slukem #ifndef lint 34*bafbd613Schristos __RCSID("$NetBSD: complete.c,v 1.48 2024/09/25 16:53:58 christos Exp $"); 357a7fa807Slukem #endif /* not lint */ 367a7fa807Slukem 377a7fa807Slukem /* 387a7fa807Slukem * FTP user program - command and file completion routines 397a7fa807Slukem */ 407a7fa807Slukem 41881bf877Slukem #include <sys/stat.h> 42881bf877Slukem 437a7fa807Slukem #include <ctype.h> 447a7fa807Slukem #include <err.h> 457a7fa807Slukem #include <dirent.h> 467a7fa807Slukem #include <stdio.h> 477a7fa807Slukem #include <stdlib.h> 487a7fa807Slukem #include <string.h> 497a7fa807Slukem 507a7fa807Slukem #include "ftp_var.h" 517a7fa807Slukem 52d78b6bd3Scgd #ifndef NO_EDITCOMPLETE 53d78b6bd3Scgd 542c9a4cf5Slukem static int comparstr (const void *, const void *); 552c9a4cf5Slukem static unsigned char complete_ambiguous (char *, int, StringList *); 562c9a4cf5Slukem static unsigned char complete_command (char *, int); 572c9a4cf5Slukem static unsigned char complete_local (char *, int); 582c9a4cf5Slukem static unsigned char complete_option (char *, int); 592c9a4cf5Slukem static unsigned char complete_remote (char *, int); 60b9d5554dSlukem 617a7fa807Slukem static int 622c9a4cf5Slukem comparstr(const void *a, const void *b) 637a7fa807Slukem { 64947172faSlukem return (strcmp(*(const char * const *)a, *(const char * const *)b)); 657a7fa807Slukem } 667a7fa807Slukem 677a7fa807Slukem /* 687a7fa807Slukem * Determine if complete is ambiguous. If unique, insert. 697a7fa807Slukem * If no choices, error. If unambiguous prefix, insert that. 707a7fa807Slukem * Otherwise, list choices. words is assumed to be filtered 717a7fa807Slukem * to only contain possible choices. 727a7fa807Slukem * Args: 737a7fa807Slukem * word word which started the match 747a7fa807Slukem * list list by default 757a7fa807Slukem * words stringlist containing possible matches 76881bf877Slukem * Returns a result as per el_set(EL_ADDFN, ...) 777a7fa807Slukem */ 787a7fa807Slukem static unsigned char 792c9a4cf5Slukem complete_ambiguous(char *word, int list, StringList *words) 807a7fa807Slukem { 813b505473Slukem char insertstr[MAXPATHLEN]; 82b5a4e862Slukem char *lastmatch, *p; 833d4efc35Slukem size_t i, j; 8487453d32Slukem size_t matchlen, wordlen; 857a7fa807Slukem 867a7fa807Slukem wordlen = strlen(word); 877a7fa807Slukem if (words->sl_cur == 0) 88ae076412Slukem return (CC_ERROR); /* no choices available */ 897a7fa807Slukem 907a7fa807Slukem if (words->sl_cur == 1) { /* only once choice available */ 91b5a4e862Slukem p = words->sl_str[0] + wordlen; 92b5a4e862Slukem if (*p == '\0') /* at end of word? */ 93b5a4e862Slukem return (CC_REFRESH); 946cc6d5d2Slukem ftpvis(insertstr, sizeof(insertstr), p, strlen(p)); 956cc6d5d2Slukem if (el_insertstr(el, insertstr) == -1) 96ae076412Slukem return (CC_ERROR); 977a7fa807Slukem else 98ae076412Slukem return (CC_REFRESH); 997a7fa807Slukem } 1007a7fa807Slukem 1017a7fa807Slukem if (!list) { 1027a7fa807Slukem lastmatch = words->sl_str[0]; 1037a7fa807Slukem matchlen = strlen(lastmatch); 1047a7fa807Slukem for (i = 1 ; i < words->sl_cur ; i++) { 1057a7fa807Slukem for (j = wordlen; j < strlen(words->sl_str[i]); j++) 1067a7fa807Slukem if (lastmatch[j] != words->sl_str[i][j]) 1077a7fa807Slukem break; 1087a7fa807Slukem if (j < matchlen) 1097a7fa807Slukem matchlen = j; 1107a7fa807Slukem } 1117a7fa807Slukem if (matchlen > wordlen) { 1126cc6d5d2Slukem ftpvis(insertstr, sizeof(insertstr), 113b0e36dc1Slukem lastmatch + wordlen, matchlen - wordlen); 1146cc6d5d2Slukem if (el_insertstr(el, insertstr) == -1) 115ae076412Slukem return (CC_ERROR); 1167a7fa807Slukem else 117881bf877Slukem return (CC_REFRESH_BEEP); 1187a7fa807Slukem } 1197a7fa807Slukem } 1207a7fa807Slukem 1219a6e9b2cSlukem putc('\n', ttyout); 1227a7fa807Slukem qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr); 1237a7fa807Slukem list_vertical(words); 124ae076412Slukem return (CC_REDISPLAY); 1257a7fa807Slukem } 1267a7fa807Slukem 1277a7fa807Slukem /* 1287a7fa807Slukem * Complete a command 1297a7fa807Slukem */ 1307a7fa807Slukem static unsigned char 1312c9a4cf5Slukem complete_command(char *word, int list) 1327a7fa807Slukem { 1337a7fa807Slukem struct cmd *c; 1347a7fa807Slukem StringList *words; 13587453d32Slukem size_t wordlen; 1367a7fa807Slukem unsigned char rv; 1377a7fa807Slukem 138e2279410Schristos words = ftp_sl_init(); 1397a7fa807Slukem wordlen = strlen(word); 1407a7fa807Slukem 1417a7fa807Slukem for (c = cmdtab; c->c_name != NULL; c++) { 1427a7fa807Slukem if (wordlen > strlen(c->c_name)) 1437a7fa807Slukem continue; 1447a7fa807Slukem if (strncmp(word, c->c_name, wordlen) == 0) 1453d4efc35Slukem ftp_sl_add(words, ftp_strdup(c->c_name)); 1467a7fa807Slukem } 1477a7fa807Slukem 1487a7fa807Slukem rv = complete_ambiguous(word, list, words); 149881bf877Slukem if (rv == CC_REFRESH) { 150881bf877Slukem if (el_insertstr(el, " ") == -1) 151881bf877Slukem rv = CC_ERROR; 152881bf877Slukem } 1533d4efc35Slukem sl_free(words, 1); 154ae076412Slukem return (rv); 1557a7fa807Slukem } 1567a7fa807Slukem 1577a7fa807Slukem /* 1587a7fa807Slukem * Complete a local file 1597a7fa807Slukem */ 1607a7fa807Slukem static unsigned char 1612c9a4cf5Slukem complete_local(char *word, int list) 1627a7fa807Slukem { 1637a7fa807Slukem StringList *words; 1643b505473Slukem char dir[MAXPATHLEN]; 1657a7fa807Slukem char *file; 1667a7fa807Slukem DIR *dd; 1677a7fa807Slukem struct dirent *dp; 1687a7fa807Slukem unsigned char rv; 169da64e19cSchristos size_t len; 1707a7fa807Slukem 1717a7fa807Slukem if ((file = strrchr(word, '/')) == NULL) { 172ae076412Slukem dir[0] = '.'; 173ae076412Slukem dir[1] = '\0'; 1747a7fa807Slukem file = word; 1757a7fa807Slukem } else { 176ae076412Slukem if (file == word) { 177ae076412Slukem dir[0] = '/'; 178ae076412Slukem dir[1] = '\0'; 179256fc138Slukem } else 180256fc138Slukem (void)strlcpy(dir, word, file - word + 1); 181ae076412Slukem file++; 1827a7fa807Slukem } 183b8ebb2baSlukem if (dir[0] == '~') { 184b8ebb2baSlukem char *p; 185b8ebb2baSlukem 186256fc138Slukem if ((p = globulize(dir)) == NULL) 187b8ebb2baSlukem return (CC_ERROR); 188256fc138Slukem (void)strlcpy(dir, p, sizeof(dir)); 189b8ebb2baSlukem free(p); 190b8ebb2baSlukem } 1917a7fa807Slukem 1927a7fa807Slukem if ((dd = opendir(dir)) == NULL) 193ae076412Slukem return (CC_ERROR); 1947a7fa807Slukem 195e2279410Schristos words = ftp_sl_init(); 196da64e19cSchristos len = strlen(file); 197da64e19cSchristos 1987a7fa807Slukem for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { 1997a7fa807Slukem if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) 2007a7fa807Slukem continue; 201da64e19cSchristos 20275e3195cSlukem #if defined(DIRENT_MISSING_D_NAMLEN) 20341c6b016Schristos if (len > strlen(dp->d_name)) 2047a7fa807Slukem continue; 205da64e19cSchristos #else 20641c6b016Schristos if (len > dp->d_namlen) 207da64e19cSchristos continue; 208da64e19cSchristos #endif 209da64e19cSchristos if (strncmp(file, dp->d_name, len) == 0) { 2107a7fa807Slukem char *tcp; 2117a7fa807Slukem 212e2279410Schristos tcp = ftp_strdup(dp->d_name); 213e2279410Schristos ftp_sl_add(words, tcp); 2147a7fa807Slukem } 2157a7fa807Slukem } 2167a7fa807Slukem closedir(dd); 2177a7fa807Slukem 2187a7fa807Slukem rv = complete_ambiguous(file, list, words); 219881bf877Slukem if (rv == CC_REFRESH) { 220881bf877Slukem struct stat sb; 221881bf877Slukem char path[MAXPATHLEN]; 222881bf877Slukem 223256fc138Slukem (void)strlcpy(path, dir, sizeof(path)); 224256fc138Slukem (void)strlcat(path, "/", sizeof(path)); 225256fc138Slukem (void)strlcat(path, words->sl_str[0], sizeof(path)); 2265d3667b7Slukem 227881bf877Slukem if (stat(path, &sb) >= 0) { 228881bf877Slukem char suffix[2] = " "; 229881bf877Slukem 230881bf877Slukem if (S_ISDIR(sb.st_mode)) 231881bf877Slukem suffix[0] = '/'; 232881bf877Slukem if (el_insertstr(el, suffix) == -1) 233881bf877Slukem rv = CC_ERROR; 234881bf877Slukem } 235881bf877Slukem } 2367a7fa807Slukem sl_free(words, 1); 237ae076412Slukem return (rv); 2387a7fa807Slukem } 2394f2d3550Slukem /* 2404f2d3550Slukem * Complete an option 2414f2d3550Slukem */ 2424f2d3550Slukem static unsigned char 2432c9a4cf5Slukem complete_option(char *word, int list) 2444f2d3550Slukem { 2454f2d3550Slukem struct option *o; 2464f2d3550Slukem StringList *words; 2474f2d3550Slukem size_t wordlen; 2484f2d3550Slukem unsigned char rv; 2494f2d3550Slukem 250e2279410Schristos words = ftp_sl_init(); 2514f2d3550Slukem wordlen = strlen(word); 2524f2d3550Slukem 2534f2d3550Slukem for (o = optiontab; o->name != NULL; o++) { 2544f2d3550Slukem if (wordlen > strlen(o->name)) 2554f2d3550Slukem continue; 2564f2d3550Slukem if (strncmp(word, o->name, wordlen) == 0) 2573d4efc35Slukem ftp_sl_add(words, ftp_strdup(o->name)); 2584f2d3550Slukem } 2594f2d3550Slukem 2604f2d3550Slukem rv = complete_ambiguous(word, list, words); 2614f2d3550Slukem if (rv == CC_REFRESH) { 2624f2d3550Slukem if (el_insertstr(el, " ") == -1) 2634f2d3550Slukem rv = CC_ERROR; 2644f2d3550Slukem } 2653d4efc35Slukem sl_free(words, 1); 2664f2d3550Slukem return (rv); 2674f2d3550Slukem } 2687a7fa807Slukem 2697a7fa807Slukem /* 2707a7fa807Slukem * Complete a remote file 2717a7fa807Slukem */ 2727a7fa807Slukem static unsigned char 2732c9a4cf5Slukem complete_remote(char *word, int list) 2747a7fa807Slukem { 2757a7fa807Slukem static StringList *dirlist; 2763b505473Slukem static char lastdir[MAXPATHLEN]; 2777a7fa807Slukem StringList *words; 2783b505473Slukem char dir[MAXPATHLEN]; 2797a7fa807Slukem char *file, *cp; 2803d4efc35Slukem size_t i; 2817a7fa807Slukem unsigned char rv; 2823d4efc35Slukem char cmdbuf[MAX_C_NAME]; 2833d4efc35Slukem char *dummyargv[3] = { NULL, NULL, NULL }; 2847a7fa807Slukem 2853d4efc35Slukem (void)strlcpy(cmdbuf, "complete", sizeof(cmdbuf)); 2863d4efc35Slukem dummyargv[0] = cmdbuf; 2872bcc4d08Slukem dummyargv[1] = dir; 2887a7fa807Slukem 2897a7fa807Slukem if ((file = strrchr(word, '/')) == NULL) { 290f2544927Slukem dir[0] = '\0'; 2917a7fa807Slukem file = word; 2927a7fa807Slukem } else { 2933b505473Slukem cp = file; 2943b505473Slukem while (*cp == '/' && cp > word) 2953b505473Slukem cp--; 296256fc138Slukem (void)strlcpy(dir, word, cp - word + 2); 2977a7fa807Slukem file++; 2987a7fa807Slukem } 2997a7fa807Slukem 300f2544927Slukem if (dirchange || dirlist == NULL || 301f2544927Slukem strcmp(dir, lastdir) != 0) { /* dir not cached */ 302947172faSlukem const char *emesg; 3033b505473Slukem 3047a7fa807Slukem if (dirlist != NULL) 3057a7fa807Slukem sl_free(dirlist, 1); 306e2279410Schristos dirlist = ftp_sl_init(); 3077a7fa807Slukem 3087a7fa807Slukem mflag = 1; 3093b505473Slukem emesg = NULL; 3103b505473Slukem while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) { 3117a7fa807Slukem char *tcp; 3127a7fa807Slukem 3137a7fa807Slukem if (!mflag) 3147a7fa807Slukem continue; 3157a7fa807Slukem if (*cp == '\0') { 3167a7fa807Slukem mflag = 0; 3177a7fa807Slukem continue; 3187a7fa807Slukem } 3198b0030a6Slukem tcp = strrchr(cp, '/'); 3208b0030a6Slukem if (tcp) 3218b0030a6Slukem tcp++; 3228b0030a6Slukem else 3238b0030a6Slukem tcp = cp; 324e2279410Schristos tcp = ftp_strdup(tcp); 325e2279410Schristos ftp_sl_add(dirlist, tcp); 3267a7fa807Slukem } 3273b505473Slukem if (emesg != NULL) { 3289a6e9b2cSlukem fprintf(ttyout, "\n%s\n", emesg); 329ae076412Slukem return (CC_REDISPLAY); 3303b505473Slukem } 331256fc138Slukem (void)strlcpy(lastdir, dir, sizeof(lastdir)); 3327a7fa807Slukem dirchange = 0; 3337a7fa807Slukem } 3347a7fa807Slukem 335e2279410Schristos words = ftp_sl_init(); 3367a7fa807Slukem for (i = 0; i < dirlist->sl_cur; i++) { 3377a7fa807Slukem cp = dirlist->sl_str[i]; 3383b505473Slukem if (strlen(file) > strlen(cp)) 3397a7fa807Slukem continue; 3403b505473Slukem if (strncmp(file, cp, strlen(file)) == 0) 341e2279410Schristos ftp_sl_add(words, cp); 3427a7fa807Slukem } 3437a7fa807Slukem rv = complete_ambiguous(file, list, words); 3447a7fa807Slukem sl_free(words, 0); 345ae076412Slukem return (rv); 3467a7fa807Slukem } 3477a7fa807Slukem 3487a7fa807Slukem /* 3497a7fa807Slukem * Generic complete routine 3507a7fa807Slukem */ 3517a7fa807Slukem unsigned char 352*bafbd613Schristos complete(EditLine *cel, int ch __unused) 3537a7fa807Slukem { 3547a7fa807Slukem static char word[FTPBUFLEN]; 3553d4efc35Slukem static size_t lastc_argc, lastc_argo; 3567a7fa807Slukem 3577a7fa807Slukem struct cmd *c; 3587a7fa807Slukem const LineInfo *lf; 3593d4efc35Slukem int dolist, cmpltype; 3603d4efc35Slukem size_t celems, len; 3617a7fa807Slukem 362d8b47884Slukem lf = el_line(cel); 3637a7fa807Slukem len = lf->lastchar - lf->buffer; 3647a7fa807Slukem if (len >= sizeof(line)) 365ae076412Slukem return (CC_ERROR); 366256fc138Slukem (void)strlcpy(line, lf->buffer, len + 1); 3677a7fa807Slukem cursor_pos = line + (lf->cursor - lf->buffer); 3687a7fa807Slukem lastc_argc = cursor_argc; /* remember last cursor pos */ 3697a7fa807Slukem lastc_argo = cursor_argo; 3707a7fa807Slukem makeargv(); /* build argc/argv of current line */ 3717a7fa807Slukem 3727a7fa807Slukem if (cursor_argo >= sizeof(word)) 373ae076412Slukem return (CC_ERROR); 3747a7fa807Slukem 3757a7fa807Slukem dolist = 0; 3767a7fa807Slukem /* if cursor and word is same, list alternatives */ 3777a7fa807Slukem if (lastc_argc == cursor_argc && lastc_argo == cursor_argo 378d0090fb7Slukem && strncmp(word, margv[cursor_argc] ? margv[cursor_argc] : "", 379d0090fb7Slukem cursor_argo) == 0) 3807a7fa807Slukem dolist = 1; 3813d4efc35Slukem else if (cursor_argc < (size_t)margc) 382256fc138Slukem (void)strlcpy(word, margv[cursor_argc], cursor_argo + 1); 3837a7fa807Slukem word[cursor_argo] = '\0'; 3847a7fa807Slukem 3857a7fa807Slukem if (cursor_argc == 0) 386ae076412Slukem return (complete_command(word, dolist)); 3877a7fa807Slukem 3887a7fa807Slukem c = getcmd(margv[0]); 3897a7fa807Slukem if (c == (struct cmd *)-1 || c == 0) 390ae076412Slukem return (CC_ERROR); 3917a7fa807Slukem celems = strlen(c->c_complete); 3927a7fa807Slukem 3937a7fa807Slukem /* check for 'continuation' completes (which are uppercase) */ 3947a7fa807Slukem if ((cursor_argc > celems) && (celems > 0) 395da64e19cSchristos && isupper((unsigned char) c->c_complete[celems-1])) 3967a7fa807Slukem cursor_argc = celems; 3977a7fa807Slukem 3987a7fa807Slukem if (cursor_argc > celems) 399ae076412Slukem return (CC_ERROR); 4007a7fa807Slukem 4014f2d3550Slukem cmpltype = c->c_complete[cursor_argc - 1]; 4024f2d3550Slukem switch (cmpltype) { 4034f2d3550Slukem case 'c': /* command complete */ 4044f2d3550Slukem case 'C': 4054f2d3550Slukem return (complete_command(word, dolist)); 4067a7fa807Slukem case 'l': /* local complete */ 4077a7fa807Slukem case 'L': 408ae076412Slukem return (complete_local(word, dolist)); 4094f2d3550Slukem case 'n': /* no complete */ 4104f2d3550Slukem case 'N': /* no complete */ 4114f2d3550Slukem return (CC_ERROR); 4124f2d3550Slukem case 'o': /* local complete */ 4134f2d3550Slukem case 'O': 4144f2d3550Slukem return (complete_option(word, dolist)); 4157a7fa807Slukem case 'r': /* remote complete */ 4167a7fa807Slukem case 'R': 417a9a78ba4Slukem if (connected != -1) { 4189a6e9b2cSlukem fputs("\nMust be logged in to complete.\n", 4199a6e9b2cSlukem ttyout); 420ae076412Slukem return (CC_REDISPLAY); 4217a7fa807Slukem } 422ae076412Slukem return (complete_remote(word, dolist)); 4237a7fa807Slukem default: 4248a06b9bfSlukem errx(1, "complete: unknown complete type `%c'", 4258a06b9bfSlukem cmpltype); 426ae076412Slukem return (CC_ERROR); 4277a7fa807Slukem } 428a222db7cSlukem /* NOTREACHED */ 4297a7fa807Slukem } 430b9d5554dSlukem 431d78b6bd3Scgd #endif /* !NO_EDITCOMPLETE */ 432