loader: Use ast_cli_completion_add for 'module load' completion.
authorCorey Farrell <git@cfware.com>
Thu, 25 Jan 2018 18:06:12 +0000 (13:06 -0500)
committerCorey Farrell <git@cfware.com>
Sat, 27 Jan 2018 20:18:45 +0000 (15:18 -0500)
This addresses all performance issues with 'module load' completion.  In
addition to using ast_cli_completion_add we stop using libedit's
filename_completion_function, instead using ast_file_read_dir.  This
ensures all results are produced from a single call to opendir.

Change-Id: I8bf51ffaa7ef1606f3bd1b5bb13f1905d72c6134

include/asterisk/file.h
main/Makefile
main/loader.c

index 453dc07..c17cb32 100644 (file)
@@ -143,6 +143,11 @@ int ast_filecopy(const char *oldname, const char *newname, const char *fmt);
  * \param filename the name of the file
  * \param obj user data object
  * \return non-zero to stop reading, otherwise zero to continue
+ *
+ * \note dir_name is not processed by realpath or other functions,
+ *       symbolic links are not resolved.  This ensures dir_name
+ *       always starts with the exact string originally passed to
+ *       \ref ast_file_read_dir or \ref ast_file_read_dirs.
  */
 typedef int (*ast_file_on_file)(const char *dir_name, const char *filename, void *obj);
 
index b148b6f..7e9624e 100644 (file)
@@ -154,7 +154,6 @@ endif
 
 db.o: _ASTCFLAGS+=$(SQLITE3_INCLUDE)
 asterisk.o: _ASTCFLAGS+=$(LIBEDIT_INCLUDE)
-loader.o: _ASTCFLAGS+=$(LIBEDIT_INCLUDE)
 json.o: _ASTCFLAGS+=$(JANSSON_INCLUDE)
 bucket.o: _ASTCFLAGS+=$(URIPARSER_INCLUDE)
 crypt.o: _ASTCFLAGS+=$(CRYPT_INCLUDE)
index da508f3..e664a23 100644 (file)
@@ -36,7 +36,6 @@
 #include "asterisk/_private.h"
 #include "asterisk/paths.h"    /* use ast_config_AST_MODULE_DIR */
 #include <dirent.h>
-#include <editline/readline.h>
 
 #include "asterisk/dlinkedlists.h"
 #include "asterisk/module.h"
@@ -56,6 +55,7 @@
 #include "asterisk/app.h"
 #include "asterisk/test.h"
 #include "asterisk/sounds_index.h"
+#include "asterisk/cli.h"
 
 #include <dlfcn.h>
 
@@ -1036,57 +1036,55 @@ static int module_matches_helper_type(struct ast_module *mod, enum ast_module_he
        }
 }
 
-static char *module_load_helper(const char *word, int state)
+struct module_load_word {
+       const char *word;
+       size_t len;
+       size_t moddir_len;
+};
+
+static int module_load_helper_on_file(const char *dir_name, const char *filename, void *obj)
 {
+       struct module_load_word *word = obj;
        struct ast_module *mod;
-       int which = 0;
-       char *name;
-       char *ret = NULL;
-       char *editline_ret;
-       char fullpath[PATH_MAX];
-       int idx = 0;
-       /* This is needed to avoid listing modules that are already running. */
-       AST_VECTOR(, char *) running_modules;
+       char *filename_merged = NULL;
 
-       AST_VECTOR_INIT(&running_modules, 200);
+       /* dir_name will never be shorter than word->moddir_len. */
+       dir_name += word->moddir_len;
+       if (!ast_strlen_zero(dir_name)) {
+               ast_assert(dir_name[0] == '/');
 
-       AST_DLLIST_LOCK(&module_list);
-       AST_DLLIST_TRAVERSE(&module_list, mod, entry) {
-               if (mod->flags.running) {
-                       AST_VECTOR_APPEND(&running_modules, mod->resource);
+               dir_name += 1;
+               if (ast_asprintf(&filename_merged, "%s/%s", dir_name, filename) < 0) {
+                       /* If we can't allocate the string just give up! */
+                       return -1;
                }
+               filename = filename_merged;
        }
 
-       if (word[0] == '/') {
-               /* BUGBUG: we should not support this. */
-               ast_copy_string(fullpath, word, sizeof(fullpath));
-       } else {
-               snprintf(fullpath, sizeof(fullpath), "%s/%s", ast_config_AST_MODULE_DIR, word);
+       if (!strncasecmp(filename, word->word, word->len)) {
+               /* Don't list files that are already loaded! */
+               mod = find_resource(filename, 0);
+               if (!mod || !mod->flags.running) {
+                       ast_cli_completion_add(ast_strdup(filename));
+               }
        }
 
-       /*
-        * This is ugly that we keep calling filename_completion_function.
-        * The only way to avoid this would be to make a copy of the function
-        * that skips matches found in the running_modules vector.
-        */
-       while (!ret && (name = editline_ret = filename_completion_function(fullpath, idx++))) {
-               if (word[0] != '/') {
-                       name += (strlen(ast_config_AST_MODULE_DIR) + 1);
-               }
+       ast_free(filename_merged);
 
-               /* Don't list files that are already loaded! */
-               if (!AST_VECTOR_GET_CMP(&running_modules, name, !strcasecmp) && ++which > state) {
-                       ret = ast_strdup(name);
-               }
+       return 0;
+}
 
-               ast_std_free(editline_ret);
-       }
+static void module_load_helper(const char *word)
+{
+       struct module_load_word word_l = {
+               .word = word,
+               .len = strlen(word),
+               .moddir_len = strlen(ast_config_AST_MODULE_DIR),
+       };
 
-       /* Do not clean-up the elements, they belong to module_list. */
-       AST_VECTOR_FREE(&running_modules);
+       AST_DLLIST_LOCK(&module_list);
+       ast_file_read_dirs(ast_config_AST_MODULE_DIR, module_load_helper_on_file, &word_l, -1);
        AST_DLLIST_UNLOCK(&module_list);
-
-       return ret;
 }
 
 char *ast_module_helper(const char *line, const char *word, int pos, int state, int rpos, enum ast_module_helper_type type)
@@ -1101,7 +1099,9 @@ char *ast_module_helper(const char *line, const char *word, int pos, int state,
        }
 
        if (type == AST_MODULE_HELPER_LOAD) {
-               return module_load_helper(word, state);
+               module_load_helper(word);
+
+               return NULL;
        }
 
        if (type == AST_MODULE_HELPER_RELOAD) {