"Fossies" - the Fresh Open Source Software Archive

Member "tnftp-20200705/libedit/filecomplete.c" (4 Jul 2020, 20660 Bytes) of package /linux/privat/tnftp-20200705.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "filecomplete.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 20151004_vs_20200705.

    1 /*  $NetBSD: filecomplete.c,v 1.6 2020/07/04 13:43:21 lukem Exp $   */
    2 /*  from    NetBSD: filecomplete.c,v 1.64 2020/01/05 07:12:05 abhinav 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 Jaromir Dolecek.
   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  *
   20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
   21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
   22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
   24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   30  * POSSIBILITY OF SUCH DAMAGE.
   31  */
   32 
   33 #include "config.h"
   34 
   35 #if 0 /* tnftp */
   36 #if !defined(lint) && !defined(SCCSID)
   37 __RCSID(" NetBSD: filecomplete.c,v 1.64 2020/01/05 07:12:05 abhinav Exp  ");
   38 #endif /* not lint && not SCCSID */
   39 
   40 #include <sys/types.h>
   41 #include <sys/stat.h>
   42 #include <dirent.h>
   43 #include <errno.h>
   44 #include <fcntl.h>
   45 #include <limits.h>
   46 #include <pwd.h>
   47 #include <stdio.h>
   48 #include <stdlib.h>
   49 #include <string.h>
   50 #include <unistd.h>
   51 #endif /* tnftp */
   52 
   53 #include "el.h"
   54 #include "filecomplete.h"
   55 
   56 static const wchar_t break_chars[] = L" \t\n\"\\'`@$><=;|&{(";
   57 
   58 /********************************/
   59 /* completion functions */
   60 
   61 /*
   62  * does tilde expansion of strings of type ``~user/foo''
   63  * if ``user'' isn't valid user name or ``txt'' doesn't start
   64  * w/ '~', returns pointer to strdup()ed copy of ``txt''
   65  *
   66  * it's the caller's responsibility to free() the returned string
   67  */
   68 char *
   69 fn_tilde_expand(const char *txt)
   70 {
   71 #if defined(HAVE_GETPW_R_POSIX) || defined(HAVE_GETPW_R_DRAFT)
   72     struct passwd pwres;
   73     char pwbuf[1024];
   74 #endif
   75     struct passwd *pass;
   76     char *temp;
   77     size_t len = 0;
   78 
   79     if (txt[0] != '~')
   80         return strdup(txt);
   81 
   82     temp = strchr(txt + 1, '/');
   83     if (temp == NULL) {
   84         temp = strdup(txt + 1);
   85         if (temp == NULL)
   86             return NULL;
   87     } else {
   88         /* text until string after slash */
   89         len = (size_t)(temp - txt + 1);
   90         temp = el_calloc(len, sizeof(*temp));
   91         if (temp == NULL)
   92             return NULL;
   93         (void)strlcpy(temp, txt + 1, len - 1);
   94     }
   95     if (temp[0] == 0) {
   96 #ifdef HAVE_GETPW_R_POSIX
   97         if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf),
   98             &pass) != 0)
   99             pass = NULL;
  100 #elif HAVE_GETPW_R_DRAFT
  101         pass = getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf));
  102 #else
  103         pass = getpwuid(getuid());
  104 #endif
  105     } else {
  106 #ifdef HAVE_GETPW_R_POSIX
  107         if (getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf), &pass) != 0)
  108             pass = NULL;
  109 #elif HAVE_GETPW_R_DRAFT
  110         pass = getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf));
  111 #else
  112         pass = getpwnam(temp);
  113 #endif
  114     }
  115     el_free(temp);      /* value no more needed */
  116     if (pass == NULL)
  117         return strdup(txt);
  118 
  119     /* update pointer txt to point at string immedially following */
  120     /* first slash */
  121     txt += len;
  122 
  123     len = strlen(pass->pw_dir) + 1 + strlen(txt) + 1;
  124     temp = el_calloc(len, sizeof(*temp));
  125     if (temp == NULL)
  126         return NULL;
  127     (void)snprintf(temp, len, "%s/%s", pass->pw_dir, txt);
  128 
  129     return temp;
  130 }
  131 
  132 static int
  133 needs_escaping(char c)
  134 {
  135     switch (c) {
  136     case '\'':
  137     case '"':
  138     case '(':
  139     case ')':
  140     case '\\':
  141     case '<':
  142     case '>':
  143     case '$':
  144     case '#':
  145     case ' ':
  146     case '\n':
  147     case '\t':
  148     case '?':
  149     case ';':
  150     case '`':
  151     case '@':
  152     case '=':
  153     case '|':
  154     case '{':
  155     case '}':
  156     case '&':
  157     case '*':
  158     case '[':
  159         return 1;
  160     default:
  161         return 0;
  162     }
  163 }
  164 
  165 static int
  166 needs_dquote_escaping(char c)
  167 {
  168     switch (c) {
  169     case '"':
  170     case '\\':
  171     case '`':
  172     case '$':
  173         return 1;
  174     default:
  175         return 0;
  176     }
  177 }
  178 
  179 
  180 static wchar_t *
  181 unescape_string(const wchar_t *string, size_t length)
  182 {
  183     size_t i;
  184     size_t j = 0;
  185     wchar_t *unescaped = el_calloc(length + 1, sizeof(*string));
  186     if (unescaped == NULL)
  187         return NULL;
  188     for (i = 0; i < length ; i++) {
  189         if (string[i] == '\\')
  190             continue;
  191         unescaped[j++] = string[i];
  192     }
  193     unescaped[j] = 0;
  194     return unescaped;
  195 }
  196 
  197 static char *
  198 escape_filename(EditLine * el, const char *filename, int single_match,
  199         const char *(*app_func)(const char *))
  200 {
  201     size_t original_len = 0;
  202     size_t escaped_character_count = 0;
  203     size_t offset = 0;
  204     size_t newlen;
  205     const char *s;
  206     char c;
  207     size_t s_quoted = 0;    /* does the input contain a single quote */
  208     size_t d_quoted = 0;    /* does the input contain a double quote */
  209     char *escaped_str;
  210     wchar_t *temp = el->el_line.buffer;
  211     const char *append_char = NULL;
  212 
  213     if (filename == NULL)
  214         return NULL;
  215 
  216     while (temp != el->el_line.cursor) {
  217         /*
  218          * If we see a single quote but have not seen a double quote
  219          * so far set/unset s_quote
  220          */
  221         if (temp[0] == '\'' && !d_quoted)
  222             s_quoted = !s_quoted;
  223         /*
  224          * vice versa to the above condition
  225          */
  226         else if (temp[0] == '"' && !s_quoted)
  227             d_quoted = !d_quoted;
  228         temp++;
  229     }
  230 
  231     /* Count number of special characters so that we can calculate
  232      * number of extra bytes needed in the new string
  233      */
  234     for (s = filename; *s; s++, original_len++) {
  235         c = *s;
  236         /* Inside a single quote only single quotes need escaping */
  237         if (s_quoted && c == '\'') {
  238             escaped_character_count += 3;
  239             continue;
  240         }
  241         /* Inside double quotes only ", \, ` and $ need escaping */
  242         if (d_quoted && needs_dquote_escaping(c)) {
  243             escaped_character_count++;
  244             continue;
  245         }
  246         if (!s_quoted && !d_quoted && needs_escaping(c))
  247             escaped_character_count++;
  248     }
  249 
  250     newlen = original_len + escaped_character_count + 1;
  251     if (s_quoted || d_quoted)
  252         newlen++;
  253 
  254     if (single_match && app_func)
  255         newlen++;
  256 
  257     if ((escaped_str = el_malloc(newlen)) == NULL)
  258         return NULL;
  259 
  260     for (s = filename; *s; s++) {
  261         c = *s;
  262         if (!needs_escaping(c)) {
  263             /* no escaping is required continue as usual */
  264             escaped_str[offset++] = c;
  265             continue;
  266         }
  267 
  268         /* single quotes inside single quotes require special handling */
  269         if (c == '\'' && s_quoted) {
  270             escaped_str[offset++] = '\'';
  271             escaped_str[offset++] = '\\';
  272             escaped_str[offset++] = '\'';
  273             escaped_str[offset++] = '\'';
  274             continue;
  275         }
  276 
  277         /* Otherwise no escaping needed inside single quotes */
  278         if (s_quoted) {
  279             escaped_str[offset++] = c;
  280             continue;
  281         }
  282 
  283         /* No escaping needed inside a double quoted string either
  284          * unless we see a '$', '\', '`', or '"' (itself)
  285          */
  286         if (d_quoted && !needs_dquote_escaping(c)) {
  287             escaped_str[offset++] = c;
  288             continue;
  289         }
  290 
  291         /* If we reach here that means escaping is actually needed */
  292         escaped_str[offset++] = '\\';
  293         escaped_str[offset++] = c;
  294     }
  295 
  296     if (single_match && app_func) {
  297         escaped_str[offset] = 0;
  298         append_char = app_func(escaped_str);
  299         /* we want to append space only if we are not inside quotes */
  300         if (append_char[0] == ' ') {
  301             if (!s_quoted && !d_quoted)
  302                 escaped_str[offset++] = append_char[0];
  303         } else
  304             escaped_str[offset++] = append_char[0];
  305     }
  306 
  307     /* close the quotes if single match and the match is not a directory */
  308     if (single_match && (append_char && append_char[0] == ' ')) { 
  309         if (s_quoted)
  310             escaped_str[offset++] = '\'';
  311         else if (d_quoted)
  312             escaped_str[offset++] = '"';
  313     }
  314 
  315     escaped_str[offset] = 0;
  316     return escaped_str;
  317 }
  318 
  319 /*
  320  * return first found file name starting by the ``text'' or NULL if no
  321  * such file can be found
  322  * value of ``state'' is ignored
  323  *
  324  * it's the caller's responsibility to free the returned string
  325  */
  326 char *
  327 fn_filename_completion_function(const char *text, int state)
  328 {
  329     static DIR *dir = NULL;
  330     static char *filename = NULL, *dirname = NULL, *dirpath = NULL;
  331     static size_t filename_len = 0;
  332     struct dirent *entry;
  333     char *temp;
  334     size_t len;
  335 
  336     if (state == 0 || dir == NULL) {
  337         temp = strrchr(text, '/');
  338         if (temp) {
  339             char *nptr;
  340             temp++;
  341             nptr = el_realloc(filename, (strlen(temp) + 1) *
  342                 sizeof(*nptr));
  343             if (nptr == NULL) {
  344                 el_free(filename);
  345                 filename = NULL;
  346                 return NULL;
  347             }
  348             filename = nptr;
  349             (void)strcpy(filename, temp);
  350             len = (size_t)(temp - text);    /* including last slash */
  351 
  352             nptr = el_realloc(dirname, (len + 1) *
  353                 sizeof(*nptr));
  354             if (nptr == NULL) {
  355                 el_free(dirname);
  356                 dirname = NULL;
  357                 return NULL;
  358             }
  359             dirname = nptr;
  360             (void)strlcpy(dirname, text, len + 1);
  361         } else {
  362             el_free(filename);
  363             if (*text == 0)
  364                 filename = NULL;
  365             else {
  366                 filename = strdup(text);
  367                 if (filename == NULL)
  368                     return NULL;
  369             }
  370             el_free(dirname);
  371             dirname = NULL;
  372         }
  373 
  374         if (dir != NULL) {
  375             (void)closedir(dir);
  376             dir = NULL;
  377         }
  378 
  379         /* support for ``~user'' syntax */
  380 
  381         el_free(dirpath);
  382         dirpath = NULL;
  383         if (dirname == NULL) {
  384             if ((dirname = strdup("")) == NULL)
  385                 return NULL;
  386             dirpath = strdup("./");
  387         } else if (*dirname == '~')
  388             dirpath = fn_tilde_expand(dirname);
  389         else
  390             dirpath = strdup(dirname);
  391 
  392         if (dirpath == NULL)
  393             return NULL;
  394 
  395         dir = opendir(dirpath);
  396         if (!dir)
  397             return NULL;    /* cannot open the directory */
  398 
  399         /* will be used in cycle */
  400         filename_len = filename ? strlen(filename) : 0;
  401     }
  402 
  403     /* find the match */
  404     while ((entry = readdir(dir)) != NULL) {
  405         /* skip . and .. */
  406         if (entry->d_name[0] == '.' && (!entry->d_name[1]
  407             || (entry->d_name[1] == '.' && !entry->d_name[2])))
  408             continue;
  409         if (filename_len == 0)
  410             break;
  411         /* otherwise, get first entry where first */
  412         /* filename_len characters are equal      */
  413         if (entry->d_name[0] == filename[0]
  414 #if HAVE_STRUCT_DIRENT_D_NAMLEN
  415             && entry->d_namlen >= filename_len
  416 #else
  417             && strlen(entry->d_name) >= filename_len
  418 #endif
  419             && strncmp(entry->d_name, filename,
  420             filename_len) == 0)
  421             break;
  422     }
  423 
  424     if (entry) {        /* match found */
  425 
  426 #if HAVE_STRUCT_DIRENT_D_NAMLEN
  427         len = entry->d_namlen;
  428 #else
  429         len = strlen(entry->d_name);
  430 #endif
  431 
  432         len = strlen(dirname) + len + 1;
  433         temp = el_calloc(len, sizeof(*temp));
  434         if (temp == NULL)
  435             return NULL;
  436         (void)snprintf(temp, len, "%s%s", dirname, entry->d_name);
  437     } else {
  438         (void)closedir(dir);
  439         dir = NULL;
  440         temp = NULL;
  441     }
  442 
  443     return temp;
  444 }
  445 
  446 
  447 static const char *
  448 append_char_function(const char *name)
  449 {
  450     struct stat stbuf;
  451     char *expname = *name == '~' ? fn_tilde_expand(name) : NULL;
  452     const char *rs = " ";
  453 
  454     if (stat(expname ? expname : name, &stbuf) == -1)
  455         goto out;
  456     if (S_ISDIR(stbuf.st_mode))
  457         rs = "/";
  458 out:
  459     if (expname)
  460         el_free(expname);
  461     return rs;
  462 }
  463 /*
  464  * returns list of completions for text given
  465  * non-static for readline.
  466  */
  467 char ** completion_matches(const char *, char *(*)(const char *, int));
  468 char **
  469 completion_matches(const char *text, char *(*genfunc)(const char *, int))
  470 {
  471     char **match_list = NULL, *retstr, *prevstr;
  472     size_t match_list_len, max_equal, which, i;
  473     size_t matches;
  474 
  475     matches = 0;
  476     match_list_len = 1;
  477     while ((retstr = (*genfunc) (text, (int)matches)) != NULL) {
  478         /* allow for list terminator here */
  479         if (matches + 3 >= match_list_len) {
  480             char **nmatch_list;
  481             while (matches + 3 >= match_list_len)
  482                 match_list_len <<= 1;
  483             nmatch_list = el_realloc(match_list,
  484                 match_list_len * sizeof(*nmatch_list));
  485             if (nmatch_list == NULL) {
  486                 el_free(match_list);
  487                 return NULL;
  488             }
  489             match_list = nmatch_list;
  490 
  491         }
  492         match_list[++matches] = retstr;
  493     }
  494 
  495     if (!match_list)
  496         return NULL;    /* nothing found */
  497 
  498     /* find least denominator and insert it to match_list[0] */
  499     which = 2;
  500     prevstr = match_list[1];
  501     max_equal = strlen(prevstr);
  502     for (; which <= matches; which++) {
  503         for (i = 0; i < max_equal &&
  504             prevstr[i] == match_list[which][i]; i++)
  505             continue;
  506         max_equal = i;
  507     }
  508 
  509     retstr = el_calloc(max_equal + 1, sizeof(*retstr));
  510     if (retstr == NULL) {
  511         el_free(match_list);
  512         return NULL;
  513     }
  514     (void)strlcpy(retstr, match_list[1], max_equal + 1);
  515     match_list[0] = retstr;
  516 
  517     /* add NULL as last pointer to the array */
  518     match_list[matches + 1] = NULL;
  519 
  520     return match_list;
  521 }
  522 
  523 /*
  524  * Sort function for qsort(). Just wrapper around strcasecmp().
  525  */
  526 static int
  527 _fn_qsort_string_compare(const void *i1, const void *i2)
  528 {
  529     const char *s1 = ((const char * const *)i1)[0];
  530     const char *s2 = ((const char * const *)i2)[0];
  531 
  532     return strcasecmp(s1, s2);
  533 }
  534 
  535 /*
  536  * Display list of strings in columnar format on readline's output stream.
  537  * 'matches' is list of strings, 'num' is number of strings in 'matches',
  538  * 'width' is maximum length of string in 'matches'.
  539  *
  540  * matches[0] is not one of the match strings, but it is counted in
  541  * num, so the strings are matches[1] *through* matches[num-1].
  542  */
  543 void
  544 fn_display_match_list(EditLine * el, char **matches, size_t num, size_t width,
  545     const char *(*app_func) (const char *))
  546 {
  547     size_t line, lines, col, cols, thisguy;
  548     int screenwidth = el->el_terminal.t_size.h;
  549     if (app_func == NULL)
  550         app_func = append_char_function;
  551 
  552     /* Ignore matches[0]. Avoid 1-based array logic below. */
  553     matches++;
  554     num--;
  555 
  556     /*
  557      * Find out how many entries can be put on one line; count
  558      * with one space between strings the same way it's printed.
  559      */
  560     cols = (size_t)screenwidth / (width + 2);
  561     if (cols == 0)
  562         cols = 1;
  563 
  564     /* how many lines of output, rounded up */
  565     lines = (num + cols - 1) / cols;
  566 
  567     /* Sort the items. */
  568     qsort(matches, num, sizeof(char *), _fn_qsort_string_compare);
  569 
  570     /*
  571      * On the ith line print elements i, i+lines, i+lines*2, etc.
  572      */
  573     for (line = 0; line < lines; line++) {
  574         for (col = 0; col < cols; col++) {
  575             thisguy = line + col * lines;
  576             if (thisguy >= num)
  577                 break;
  578             (void)fprintf(el->el_outfile, "%s%s%s",
  579                 col == 0 ? "" : " ", matches[thisguy],
  580                 (*app_func)(matches[thisguy]));
  581             (void)fprintf(el->el_outfile, "%-*s",
  582                 (int) (width - strlen(matches[thisguy])), "");
  583         }
  584         (void)fprintf(el->el_outfile, "\n");
  585     }
  586 }
  587 
  588 static wchar_t *
  589 find_word_to_complete(const wchar_t * cursor, const wchar_t * buffer,
  590     const wchar_t * word_break, const wchar_t * special_prefixes, size_t * length,
  591     int do_unescape)
  592 {
  593     /* We now look backwards for the start of a filename/variable word */
  594     const wchar_t *ctemp = cursor;
  595     wchar_t *temp;
  596     size_t len;
  597 
  598     /* if the cursor is placed at a slash or a quote, we need to find the
  599      * word before it
  600      */
  601     if (ctemp > buffer) {
  602         switch (ctemp[-1]) {
  603         case '\\':
  604         case '\'':
  605         case '"':
  606             ctemp--;
  607             break;
  608         default:
  609             break;
  610         }
  611     }
  612 
  613     for (;;) {
  614         if (ctemp <= buffer)
  615             break;
  616         if (wcschr(word_break, ctemp[-1])) {
  617             if (ctemp - buffer >= 2 && ctemp[-2] == '\\') {
  618                 ctemp -= 2;
  619                 continue;
  620             }
  621             break;
  622         }
  623         if (special_prefixes && wcschr(special_prefixes, ctemp[-1]))
  624             break;
  625         ctemp--;
  626     }
  627 
  628     len = (size_t) (cursor - ctemp);
  629     if (len == 1 && (ctemp[0] == '\'' || ctemp[0] == '"')) {
  630         len = 0;
  631         ctemp++;
  632     }
  633     *length = len;
  634     if (do_unescape) {
  635         wchar_t *unescaped_word = unescape_string(ctemp, len);
  636         if (unescaped_word == NULL)
  637             return NULL;
  638         return unescaped_word;
  639     }
  640     temp = el_malloc((len + 1) * sizeof(*temp));
  641     (void) wcsncpy(temp, ctemp, len);
  642     temp[len] = '\0';
  643     return temp;
  644 }
  645 
  646 /*
  647  * Complete the word at or before point,
  648  * 'what_to_do' says what to do with the completion.
  649  * \t   means do standard completion.
  650  * `?' means list the possible completions.
  651  * `*' means insert all of the possible completions.
  652  * `!' means to do standard completion, and list all possible completions if
  653  * there is more than one.
  654  *
  655  * Note: '*' support is not implemented
  656  *       '!' could never be invoked
  657  */
  658 int
  659 fn_complete(EditLine *el,
  660     char *(*complet_func)(const char *, int),
  661     char **(*attempted_completion_function)(const char *, int, int),
  662     const wchar_t *word_break, const wchar_t *special_prefixes,
  663     const char *(*app_func)(const char *), size_t query_items,
  664     int *completion_type, int *over, int *point, int *end)
  665 {
  666     const LineInfoW *li;
  667     wchar_t *temp;
  668     char **matches;
  669     char *completion;
  670     size_t len;
  671     int what_to_do = '\t';
  672     int retval = CC_NORM;
  673     int do_unescape = attempted_completion_function == NULL? 1: 0;
  674 
  675     if (el->el_state.lastcmd == el->el_state.thiscmd)
  676         what_to_do = '?';
  677 
  678     /* readline's rl_complete() has to be told what we did... */
  679     if (completion_type != NULL)
  680         *completion_type = what_to_do;
  681 
  682     if (!complet_func)
  683         complet_func = fn_filename_completion_function;
  684     if (!app_func)
  685         app_func = append_char_function;
  686 
  687     li = el_wline(el);
  688     temp = find_word_to_complete(li->cursor,
  689         li->buffer, word_break, special_prefixes, &len, do_unescape);
  690     if (temp == NULL)
  691         goto out;
  692 
  693     /* these can be used by function called in completion_matches() */
  694     /* or (*attempted_completion_function)() */
  695     if (point != NULL)
  696         *point = (int)(li->cursor - li->buffer);
  697     if (end != NULL)
  698         *end = (int)(li->lastchar - li->buffer);
  699 
  700     if (attempted_completion_function) {
  701         int cur_off = (int)(li->cursor - li->buffer);
  702         matches = (*attempted_completion_function)(
  703             ct_encode_string(temp, &el->el_scratch),
  704             cur_off - (int)len, cur_off);
  705     } else
  706         matches = NULL;
  707     if (!attempted_completion_function ||
  708         (over != NULL && !*over && !matches))
  709         matches = completion_matches(
  710             ct_encode_string(temp, &el->el_scratch), complet_func);
  711 
  712     if (over != NULL)
  713         *over = 0;
  714 
  715     if (matches == NULL) {
  716         goto out;
  717     }
  718     int i;
  719     size_t matches_num, maxlen, match_len, match_display=1;
  720     int single_match = matches[2] == NULL &&
  721         (matches[1] == NULL || strcmp(matches[0], matches[1]) == 0);
  722 
  723     retval = CC_REFRESH;
  724 
  725     if (matches[0][0] != '\0') {
  726         el_deletestr(el, (int)len);
  727         if (!attempted_completion_function)
  728             completion = escape_filename(el, matches[0],
  729                 single_match, app_func);
  730         else
  731             completion = strdup(matches[0]);
  732         if (completion == NULL)
  733             goto out;
  734 
  735         /*
  736          * Replace the completed string with the common part of
  737          * all possible matches if there is a possible completion.
  738          */
  739         el_winsertstr(el,
  740             ct_decode_string(completion, &el->el_scratch));
  741 
  742         if (single_match && attempted_completion_function) {
  743             /*
  744              * We found an exact match. Add a space after
  745              * it, unless we do filename completion and the
  746              * object is a directory. Also do necessary
  747              * escape quoting
  748              */
  749             el_winsertstr(el, ct_decode_string(
  750                 (*app_func)(completion), &el->el_scratch));
  751         }
  752         free(completion);
  753     }
  754 
  755 
  756     if (!single_match && (what_to_do == '!' || what_to_do == '?')) {
  757         /*
  758          * More than one match and requested to list possible
  759          * matches.
  760          */
  761 
  762         for(i = 1, maxlen = 0; matches[i]; i++) {
  763             match_len = strlen(matches[i]);
  764             if (match_len > maxlen)
  765                 maxlen = match_len;
  766         }
  767         /* matches[1] through matches[i-1] are available */
  768         matches_num = (size_t)(i - 1);
  769 
  770         /* newline to get on next line from command line */
  771         (void)fprintf(el->el_outfile, "\n");
  772 
  773         /*
  774          * If there are too many items, ask user for display
  775          * confirmation.
  776          */
  777         if (matches_num > query_items) {
  778             (void)fprintf(el->el_outfile,
  779                 "Display all %zu possibilities? (y or n) ",
  780                 matches_num);
  781             (void)fflush(el->el_outfile);
  782             if (getc(stdin) != 'y')
  783                 match_display = 0;
  784             (void)fprintf(el->el_outfile, "\n");
  785         }
  786 
  787         if (match_display) {
  788             /*
  789              * Interface of this function requires the
  790              * strings be matches[1..num-1] for compat.
  791              * We have matches_num strings not counting
  792              * the prefix in matches[0], so we need to
  793              * add 1 to matches_num for the call.
  794              */
  795             fn_display_match_list(el, matches,
  796                 matches_num+1, maxlen, app_func);
  797         }
  798         retval = CC_REDISPLAY;
  799     } else if (matches[0][0]) {
  800         /*
  801          * There was some common match, but the name was
  802          * not complete enough. Next tab will print possible
  803          * completions.
  804          */
  805         el_beep(el);
  806     } else {
  807         /* lcd is not a valid object - further specification */
  808         /* is needed */
  809         el_beep(el);
  810         retval = CC_NORM;
  811     }
  812 
  813     /* free elements of array and the array itself */
  814     for (i = 0; matches[i]; i++)
  815         el_free(matches[i]);
  816     el_free(matches);
  817     matches = NULL;
  818 
  819 out:
  820     el_free(temp);
  821     return retval;
  822 }
  823 
  824 /*
  825  * el-compatible wrapper around rl_complete; needed for key binding
  826  */
  827 /* ARGSUSED */
  828 unsigned char
  829 _el_fn_complete(EditLine *el, int ch __attribute__((__unused__)))
  830 {
  831     return (unsigned char)fn_complete(el, NULL, NULL,
  832         break_chars, NULL, NULL, (size_t)100,
  833         NULL, NULL, NULL, NULL);
  834 }