/* vifm * Copyright (C) 2020 xaizek. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "plugins.h" #include <stdarg.h> /* va_list va_start() va_end() vsnprintf() */ #include <stdlib.h> /* calloc() free() */ #include <string.h> /* strcmp() */ #include "compat/fs_limits.h" #include "compat/os.h" #include "engine/completion.h" #include "lua/vlua.h" #include "utils/darray.h" #include "utils/fs.h" #include "utils/macros.h" #include "utils/path.h" #include "utils/str.h" #include "utils/string_array.h" #include "utils/test_helpers.h" #include "utils/utils.h" /* State of the unit. */ struct plugs_t { struct vlua_t *vlua; /* Reference to vlua unit. */ strlist_t blacklist; /* List of plugins to skip. */ strlist_t whitelist; /* List of plugins to load. */ plug_t **plugs; /* Known plugins. */ DA_INSTANCE_FIELD(plugs); /* Declarations to enable use of DA_* on plugs. */ int loaded; /* Whether plugins were loaded, shouldn't load them twice. */ }; static void load_plugs_dir(plugs_t *plugs, const char plugin_path[]); static void plug_free(plug_t *plug); static plug_t * plug_from_name(plugs_t *plugs, const char name[]); static plug_t * plug_from_real_path(plugs_t *plugs, const char real_path[]); static int should_be_loaded(plugs_t *plugs, const char name[]); static void plug_logf(plug_t *plug, const char format[], ...) _gnuc_printf(2, 3); static void add_if_missing(strlist_t *strlist, const char item[]); TSTATIC void plugs_sort(plugs_t *plugs); static int plug_cmp(const void *a, const void *b); TSTATIC int plugs_loaded(const plugs_t *plugs); TSTATIC strlist_t plugs_get_blacklist(plugs_t *plugs); TSTATIC strlist_t plugs_get_whitelist(plugs_t *plugs); plugs_t * plugs_create(struct vlua_t *vlua) { plugs_t *plugs = calloc(1, sizeof(*plugs)); if(plugs == NULL) { return NULL; } plugs->vlua = vlua; return plugs; } void plugs_free(plugs_t *plugs) { if(plugs != NULL) { size_t i; for(i = 0U; i < DA_SIZE(plugs->plugs); ++i) { plug_free(plugs->plugs[i]); } DA_REMOVE_ALL(plugs->plugs); free_string_array(plugs->blacklist.items, plugs->blacklist.nitems); free_string_array(plugs->whitelist.items, plugs->whitelist.nitems); free(plugs); } } void plugs_load(plugs_t *plugs, strlist_t plugins_dirs) { if(plugs->loaded) { return; } plugs->loaded = 1; int i; for(i = 0; i < plugins_dirs.nitems; ++i) { load_plugs_dir(plugs, plugins_dirs.items[i]); } } /* Loads all plugins from a directory if it can be listed. */ static void load_plugs_dir(plugs_t *plugs, const char plugin_path[]) { DIR *dir = os_opendir(plugin_path); if(dir == NULL) { return; } struct dirent *entry; while((entry = os_readdir(dir)) != NULL) { char path[PATH_MAX + NAME_MAX + 4]; snprintf(path, sizeof(path), "%s/%s", plugin_path, entry->d_name); /* XXX: is_dirent_targets_dir() does slowfs checks, do they harm here? */ if(entry->d_name[0] == '.' || is_builtin_dir(entry->d_name) || !is_dirent_targets_dir(path, entry)) { continue; } plug_t **plug_ptr = DA_EXTEND(plugs->plugs); if(plug_ptr == NULL) { continue; } plug_t *plug = calloc(1, sizeof(*plug)); if(plug == NULL) { continue; } *plug_ptr = plug; /* TODO: reserve "vifm" name of the plugin and reject it here? */ /* TODO: reject plugins with "#" in their name. */ plug->name = strdup(entry->d_name); plug->path = strdup(path); if(plug->name == NULL || plug->path == NULL) { plug_free(plug); continue; } if(os_realpath(plug->path, path) == NULL) { plug->real_path = strdup(plug->path); } else { plug->real_path = strdup(path); } if(plug->real_path == NULL) { plug_free(plug); continue; } /* Perform the searches before committing this plugin. */ plug_t *name_duplicate = plug_from_name(plugs, plug->name); plug_t *real_duplicate = plug_from_real_path(plugs, plug->real_path); plug->status = PLS_FAILURE; DA_COMMIT(plugs->plugs); if(real_duplicate != NULL) { plug_logf(plug, "[vifm][error]: skipped as a duplicate of %s", real_duplicate->path); plug->status = PLS_SKIPPED; } else if(name_duplicate != NULL) { plug_logf(plug, "[vifm][error]: skipped as a conflicting with %s", name_duplicate->path); plug->status = PLS_SKIPPED; } else if(!should_be_loaded(plugs, plug->name)) { plug_log(plug, "[vifm][info]: skipped due to blacklist/whitelist"); plug->status = PLS_SKIPPED; } else if(vlua_load_plugin(plugs->vlua, plug) == 0) { plug_log(plug, "[vifm][info]: plugin was loaded successfully"); plug->status = PLS_SUCCESS; } else { plug_log(plug, "[vifm][error]: loading plugin has failed"); } } os_closedir(dir); } /* Frees a plugin. The argument can't be NULL. */ static void plug_free(plug_t *plug) { free(plug->name); free(plug->path); free(plug->real_path); free(plug->log); free(plug); } /* Looks up a plugin specified by name among already loaded plugins. Returns * pointer to it or NULL. */ static plug_t * plug_from_name(plugs_t *plugs, const char name[]) { size_t i; for(i = 0U; i < DA_SIZE(plugs->plugs); ++i) { if(strcasecmp(plugs->plugs[i]->name, name) == 0) { return plugs->plugs[i]; } } return NULL; } /* Looks up a plugin specified by real path among already loaded plugins. * Returns pointer to it or NULL. */ static plug_t * plug_from_real_path(plugs_t *plugs, const char real_path[]) { size_t i; for(i = 0U; i < DA_SIZE(plugs->plugs); ++i) { if(stroscmp(plugs->plugs[i]->real_path, real_path) == 0) { return plugs->plugs[i]; } } return NULL; } /* Checks with blacklist and whitelist to decide if plugin should be loaded. * Returns non-zero if loading is allowed. */ static int should_be_loaded(plugs_t *plugs, const char name[]) { if(plugs->whitelist.nitems != 0) { return (string_array_pos(plugs->whitelist.items, plugs->whitelist.nitems, name) != -1); } return (string_array_pos(plugs->blacklist.items, plugs->blacklist.nitems, name) == -1); } int plugs_get(const plugs_t *plugs, int idx, const plug_t **plug) { if(idx < 0 || idx >= (int)DA_SIZE(plugs->plugs)) { return 0; } *plug = plugs->plugs[idx]; return 1; } /* Adds formatted message to the log on a new line. */ static void plug_logf(plug_t *plug, const char format[], ...) { va_list ap; va_start(ap, format); char buf[1024]; vsnprintf(buf, sizeof(buf), format, ap); plug_log(plug, buf); va_end(ap); } void plugs_blacklist(plugs_t *plugs, const char name[]) { add_if_missing(&plugs->blacklist, name); } void plugs_whitelist(plugs_t *plugs, const char name[]) { add_if_missing(&plugs->whitelist, name); } /* Adds an item to the string list unless it's already there. */ static void add_if_missing(strlist_t *strlist, const char item[]) { if(string_array_pos(strlist->items, strlist->nitems, item) == -1) { strlist->nitems = add_to_string_array(&strlist->items, strlist->nitems, item); } } void plugs_complete(plugs_t *plugs, const char prefix[]) { size_t i; const size_t prefix_len = strlen(prefix); for(i = 0U; i < DA_SIZE(plugs->plugs); ++i) { const char *name = plugs->plugs[i]->name; if(strncmp(name, prefix, prefix_len) == 0) { vle_compl_add_match(name, ""); } } vle_compl_finish_group(); vle_compl_add_last_match(prefix); } void plug_log(plug_t *plug, const char msg[]) { if(plug->log_len != 0) { (void)strappendch(&plug->log, &plug->log_len, '\n'); } (void)strappend(&plug->log, &plug->log_len, msg); } /* Sorts plugins by their path. */ TSTATIC void plugs_sort(plugs_t *plugs) { safe_qsort(plugs->plugs, DA_SIZE(plugs->plugs), sizeof(*plugs->plugs), &plug_cmp); } /* Compares two plugins by their paths. Returns negative, zero or positive * number meaning less than, equal or greater than correspondingly. */ static int plug_cmp(const void *a, const void *b) { const plug_t *plug_a = *(const plug_t **)a; const plug_t *plug_b = *(const plug_t **)b; return strcmp(plug_a->path, plug_b->path); } TSTATIC int plugs_loaded(const plugs_t *plugs) { return plugs->loaded; } /* Retrieves blacklist. Returns the list. */ TSTATIC strlist_t plugs_get_blacklist(plugs_t *plugs) { return plugs->blacklist; } /* Retrieves whitelist. Returns the list. */ TSTATIC strlist_t plugs_get_whitelist(plugs_t *plugs) { return plugs->whitelist; } /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set cinoptions+=t0 : */