/* * plugin.c: plugin handling * * Copyright (c) 2011 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include plugin_main_t vlib_plugin_main; #define PLUGIN_LOG_DBG(...) \ do {vlib_log_debug (vlib_plugin_main.logger, __VA_ARGS__);} while(0) #define PLUGIN_LOG_ERR(...) \ do {vlib_log_err (vlib_plugin_main.logger, __VA_ARGS__);} while(0) #define PLUGIN_LOG_NOTICE(...) \ do {vlib_log_notice (vlib_plugin_main.logger, __VA_ARGS__);} while(0) char *vlib_plugin_path __attribute__ ((weak)); char *vlib_plugin_path = ""; char *vlib_plugin_app_version __attribute__ ((weak)); char *vlib_plugin_app_version = ""; void * vlib_get_plugin_symbol (const char *plugin_name, const char *symbol_name) { plugin_main_t *pm = &vlib_plugin_main; uword *p; plugin_info_t *pi; if ((p = hash_get_mem (pm->plugin_by_name_hash, plugin_name)) == 0) return 0; pi = vec_elt_at_index (pm->plugin_info, p[0]); return dlsym (pi->handle, symbol_name); } static char * str_array_to_vec (char *array, int len) { char c, *r = 0; int n = 0; do { c = array[n]; vec_add1 (r, c); } while (c && ++n < len); if (c) vec_add1 (r, 0); return r; } static u8 * extract (u8 * sp, u8 * ep) { u8 *rv = 0; while (sp <= ep) { vec_add1 (rv, *sp); sp++; } vec_add1 (rv, 0); return rv; } /* * If a plugin .so contains a ".vlib_plugin_r2" section, * this function converts the vlib_plugin_r2_t to * a vlib_plugin_registration_t. */ static clib_error_t * r2_to_reg (elf_main_t * em, vlib_plugin_r2_t * r2, vlib_plugin_registration_t * reg) { clib_error_t *error; elf_section_t *section; uword data_segment_offset; u8 *data; /* It turns out that the strings land in the ".data" section */ error = elf_get_section_by_name (em, ".data", §ion); if (error) return error; data = elf_get_section_contents (em, section->index, 1); /* * Offsets in the ".vlib_plugin_r2" section * need to have the data section base subtracted from them. * The offset is in the first 8 bytes of the ".data" section */ data_segment_offset = *((uword *) data); /* Relocate pointers, subtract data_segment_offset */ #define _(a) r2->a.data_segment_offset -= data_segment_offset; foreach_r2_string_field; #undef _ if (r2->version.length >= ARRAY_LEN (reg->version) - 1) return clib_error_return (0, "Version string too long"); if (r2->version_required.length >= ARRAY_LEN (reg->version_required) - 1) return clib_error_return (0, "Version-required string too long"); if (r2->overrides.length >= ARRAY_LEN (reg->overrides) - 1) return clib_error_return (0, "Override string too long"); /* Compatibility with C-initializer */ memcpy ((void *) reg->version, data + r2->version.data_segment_offset, r2->version.length); memcpy ((void *) reg->version_required, data + r2->version_required.data_segment_offset, r2->version_required.length); memcpy ((void *) reg->overrides, data + r2->overrides.data_segment_offset, r2->overrides.length); if (r2->early_init.length > 0) { u8 *ei = 0; vec_validate (ei, r2->early_init.length + 1); memcpy (ei, data + r2->early_init.data_segment_offset, r2->early_init.length); reg->early_init = (void *) ei; } if (r2->description.length > 0) { u8 *desc = 0; vec_validate (desc, r2->description.length + 1); memcpy (desc, data + r2->description.data_segment_offset, r2->description.length); reg->description = (void *) desc; } vec_free (data); return 0; } static int load_one_plugin (plugin_main_t * pm, plugin_info_t * pi, int from_early_init) { void *handle; int reread_reg = 1; clib_error_t *error; elf_main_t em = { 0 }; elf_section_t *section; u8 *data; char *version_required; vlib_plugin_registration_t *reg; vlib_plugin_r2_t *r2; plugin_config_t *pc = 0; uword *p; if (elf_read_file (&em, (char *) pi->filename)) return -1; /* New / improved (well, not really) registration structure? */ error = elf_get_section_by_name (&em, ".vlib_plugin_r2", §ion); if (error == 0) { data = elf_get_section_contents (&em, section->index, 1); r2 = (vlib_plugin_r2_t *) data; reg = clib_mem_alloc (sizeof (*reg)); memset (reg, 0, sizeof (*reg)); reg->default_disabled = r2->default_disabled != 0; error = r2_to_reg (&em, r2, reg); if (error) { PLUGIN_LOG_ERR ("Bad r2 registration: %s\n", (char *) pi->name); return -1; } if (pm->plugins_default_disable) reg->default_disabled = 1; reread_reg = 0; goto process_reg; } else clib_error_free (error); error = elf_get_section_by_name (&em, ".vlib_plugin_registration", §ion); if (error) { PLUGIN_LOG_ERR ("Not a plugin: %s\n", (char *) pi->name); return -1; } data = elf_get_section_contents (&em, section->index, 1); reg = (vlib_plugin_registration_t *) data; if (vec_len (data) != sizeof (*reg)) { PLUGIN_LOG_ERR ("vlib_plugin_registration size mismatch in plugin %s\n", (char *) pi->name); goto error; } if (pm->plugins_default_disable) reg->default_disabled = 1; process_reg: p = hash_get_mem (pm->config_index_by_name, pi->name); if (p) { pc = vec_elt_at_index (pm->configs, p[0]); if (pc->is_disabled) { PLUGIN_LOG_NOTICE ("Plugin disabled: %s", pi->name); goto error; } if (reg->default_disabled && pc->is_enabled == 0) { PLUGIN_LOG_NOTICE ("Plugin disabled (default): %s", pi->name); goto error; } } else if (reg->default_disabled) { PLUGIN_LOG_NOTICE ("Plugin disabled (default): %s", pi->name); goto error; } version_required = str_array_to_vec ((char *) ®->version_required, sizeof (reg->version_required)); if ((strlen (version_required) > 0) && (strncmp (vlib_plugin_app_version, version_required, strlen (version_required)))) { PLUGIN_LOG_ERR ("Plugin %s version mismatch: %s != %s", pi->name, vlib_plugin_app_version, reg->version_required); if (!(pc && pc->skip_version_check == 1)) { vec_free (version_required); goto error; } } /* * Collect names of plugins overridden (disabled) by the * current plugin. */ if (reg->overrides[0]) { const char *overrides = reg->overrides; u8 *override_name_copy, *overridden_by_name_copy; u8 *sp, *ep; uword *p; sp = ep = (u8 *) overrides; while (1) { if (*sp == 0 || (sp >= (u8 *) overrides + ARRAY_LEN (reg->overrides))) break; if (*sp == ' ' || *sp == ',') { sp++; continue; } ep = sp; while (*ep && *ep != ' ' && *ep != ',' && ep < (u8 *) overrides + ARRAY_LEN (reg->overrides)) ep++; if (*ep == ' ' || *ep == ',') ep--; override_name_copy = extract (sp, ep); p = hash_get_mem (pm->plugin_overrides_by_name_hash, override_name_copy); /* Already overridden... */ if (p) vec_free (override_name_copy); else { overridden_by_name_copy = format (0, "%s%c", pi->name, 0); hash_set_mem (pm->plugin_overrides_by_name_hash, override_name_copy, overridden_by_name_copy); } sp = *ep ? ep + 1 : ep; } } vec_free (version_required); handle = dlopen ((char *) pi->filename, RTLD_LAZY | (reg->deep_bind ? RTLD_DEEPBIND : 0)); if (handle == 0) { PLUGIN_LOG_ERR ("%s", dlerror ()); PLUGIN_LOG_ERR ("Failed to load plugin '%s'", pi->name); goto error; } pi->handle = handle; if (reread_reg) reg = dlsym (pi->handle, "vlib_plugin_registration"); pi->reg = reg; pi->version = str_array_to_vec ((char *) ®->version, sizeof (reg->version)); if (reg->early_init) { clib_error_t *(*ei) (vlib_main_t *); void *h; h = dlsym (pi->handle, reg->early_init); if (h) { ei = h; error = (*ei) (pm->vlib_main); if (error) { u8 *err = format (0, "%s: %U%c", pi->name, format_clib_error, error, 0); PLUGIN_LOG_ERR ((char *) err); clib_error_free (error); dlclose (pi->handle); pi->handle = 0; goto error; } } else PLUGIN_LOG_ERR ("Plugin %s: early init function %s set but not found", (char *) pi->name, reg->early_init); } if (reg->description) PLUGIN_LOG_NOTICE ("Loaded plugin: %s (%s)", pi->name, reg->description); else PLUGIN_LOG_NOTICE ("Loaded plugin: %s", pi->name); vec_free (data); elf_main_free (&em); return 0; error: vec_free (data); elf_main_free (&em); return -1; } static u8 ** split_plugin_path (plugin_main_t * pm) { int i; u8 **rv = 0; u8 *path = pm->plugin_path; u8 *this = 0; for (i = 0; i < vec_len (pm->plugin_path); i++) { if (path[i] != ':') { vec_add1 (this, path[i]); continue; } vec_add1 (this, 0); vec_add1 (rv, this); this = 0; } if (this) { vec_add1 (this, 0); vec_add1 (rv, this); } return rv; } static int plugin_name_sort_cmp (void *a1, void *a2) { plugin_info_t *p1 = a1; plugin_info_t *p2 = a2; return strcmp ((char *) p1->name, (char *) p2->name); } static int index_cmp (void *a1, void *a2) { uword *i1 = (uword *) a1, *i2 = (uword *) a2; if (*i1 < *i2) return -1; else if (*i1 > *i2) return 1; else return 0; } int vlib_load_new_plugins (plugin_main_t * pm, int from_early_init) { DIR *dp; struct dirent *entry; struct stat statb; uword *p; plugin_info_t *pi; u8 **plugin_path; uword *not_loaded_indices = 0; int i; plugin_path = split_plugin_path (pm); for (i = 0; i < vec_len (plugin_path); i++) { dp = opendir ((char *) plugin_path[i]); if (dp == 0) continue; while ((entry = readdir (dp))) { u8 *plugin_name; u8 *filename; if (pm->plugin_name_filter) { int j; for (j = 0; j < vec_len (pm->plugin_name_filter); j++) if (entry->d_name[j] != pm->plugin_name_filter[j]) goto next; } filename = format (0, "%s/%s%c", plugin_path[i], entry->d_name, 0); /* Only accept .so */ char *ext = strrchr ((const char *) filename, '.'); /* unreadable */ if (!ext || (strcmp (ext, ".so") != 0) || stat ((char *) filename, &statb) < 0) { ignore: vec_free (filename); continue; } /* a dir or other things which aren't plugins */ if (!S_ISREG (statb.st_mode)) goto ignore; plugin_name = format (0, "%s%c", entry->d_name, 0); /* Have we seen this plugin already? */ p = hash_get_mem (pm->plugin_by_name_hash, plugin_name); if (p == 0) { /* No, add it to the plugin vector */ vec_add2 (pm->plugin_info, pi, 1); pi->name = plugin_name; pi->filename = filename; pi->file_info = statb; pi->handle = 0; hash_set_mem (pm->plugin_by_name_hash, plugin_name, pi - pm->plugin_info); } next: ; } closedir (dp); vec_free (plugin_path[i]); } vec_free (plugin_path); /* * Sort the plugins by name. This is important. * API traces contain absolute message numbers. * Loading plugins in directory (vs. alphabetical) order * makes trace replay incredibly fragile. */ vec_sort_with_function (pm->plugin_info, plugin_name_sort_cmp); /* * Attempt to load the plugins */ for (i = 0; i < vec_len (pm->plugin_info); i++) { pi = vec_elt_at_index (pm->plugin_info, i); if (load_one_plugin (pm, pi, from_early_init)) { /* Make a note of any which fail to load */ vec_add1 (not_loaded_indices, i); } } /* * Honor override list */ for (i = 0; i < vec_len (pm->plugin_info); i++) { uword *p; pi = vec_elt_at_index (pm->plugin_info, i); p = hash_get_mem (pm->plugin_overrides_by_name_hash, pi->name); /* Plugin overridden? */ if (p) { PLUGIN_LOG_NOTICE ("Plugin '%s' overridden by '%s'", pi->name, p[0]); vec_add1 (not_loaded_indices, i); } } /* * Sort the vector of indices to delete to avoid screwing up * the indices as we delete them. */ vec_sort_with_function (not_loaded_indices, index_cmp); /* * Remove duplicates, which can happen if a plugin is * disabled from the command line and disabled by * a plugin which is loaded. */ for (i = 0; i < vec_len (not_loaded_indices); i++) { if (i < vec_len (not_loaded_indices) - 1) { if (not_loaded_indices[i + 1] == not_loaded_indices[i]) { vec_delete (not_loaded_indices, 1, i); i--; } } } /* Remove plugin info vector elements corresponding to load failures */ if (vec_len (not_loaded_indices) > 0) { for (i = vec_len (not_loaded_indices) - 1; i >= 0; i--) { pi = vec_elt_at_index (pm->plugin_info, not_loaded_indices[i]); hash_unset_mem (pm->plugin_by_name_hash, pi->name); if (pi->handle) { dlclose (pi->handle); PLUGIN_LOG_NOTICE ("Unloaded plugin: %s", pi->name); } vec_free (pi->name); vec_free (pi->filename); vec_delete (pm->plugin_info, 1, not_loaded_indices[i]); } vec_free (not_loaded_indices); } /* Recreate the plugin name hash */ hash_free (pm->plugin_by_name_hash); pm->plugin_by_name_hash = hash_create_string (0, sizeof (uword)); for (i = 0; i < vec_len (pm->plugin_info); i++) { pi = vec_elt_at_index (pm->plugin_info, i); hash_set_mem (pm->plugin_by_name_hash, pi->name, pi - pm->plugin_info); } return 0; } int vlib_plugin_early_init (vlib_main_t * vm) { plugin_main_t *pm = &vlib_plugin_main; pm->logger = vlib_log_register_class_rate_limit ("plugin", "load", 0x7FFFFFFF /* aka no rate limit */ ); if (pm->plugin_path == 0) pm->plugin_path = format (0, "%s", vlib_plugin_path); if (pm->plugin_path_add) pm->plugin_path = format (pm->plugin_path, ":%s", pm->plugin_path_add); pm->plugin_path = format (pm->plugin_path, "%c", 0); PLUGIN_LOG_DBG ("plugin path %s", pm->plugin_path); pm->plugin_by_name_hash = hash_create_string (0, sizeof (uword)); pm->plugin_overrides_by_name_hash = hash_create_string (0, sizeof (uword)); pm->vlib_main = vm; return vlib_load_new_plugins (pm, 1 /* from_early_init */ ); } u8 * vlib_get_vat_plugin_path (void) { plugin_main_t *pm = &vlib_plugin_main; return (pm->vat_plugin_path); } u8 * vlib_get_vat_plugin_name_filter (void) { plugin_main_t *pm = &vlib_plugin_main; return (pm->vat_plugin_name_filter); } static clib_error_t * vlib_plugins_show_cmd_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { plugin_main_t *pm = &vlib_plugin_main; u8 *s = 0; u8 *key = 0; uword value = 0; int index = 1; plugin_info_t *pi; s = format (s, " Plugin path is: %s\n\n", pm->plugin_path); s = format (s, " %-41s%-33s%s\n", "Plugin", "Version", "Description"); hash_foreach_mem (key, value, pm->plugin_by_name_hash, { if (key != 0) { pi = vec_elt_at_index (pm->plugin_info, value); s = format (s, "%3d. %-40s %-32s %s\n", index, key, pi->version, (pi->reg && pi->reg->description) ? pi->reg->description : ""); index++; } }); vlib_cli_output (vm, "%v", s); vec_free (s); return 0; } VLIB_CLI_COMMAND (plugins_show_cmd, static) = { .path = "show plugins", .short_help = "show loaded plugins", .function = vlib_plugins_show_cmd_fn, }; static clib_error_t * config_one_plugin (vlib_main_t * vm, char *name, unformat_input_t * input) { plugin_main_t *pm = &vlib_plugin_main; plugin_config_t *pc; clib_error_t *error = 0; uword *p; int is_enable = 0; int is_disable = 0; int skip_version_check = 0; if (pm->config_index_by_name == 0) pm->config_index_by_name = hash_create_string (0, sizeof (uword)); p = hash_get_mem (pm->config_index_by_name, name); if (p) { error = clib_error_return (0, "plugin '%s' already configured", name); goto done; } while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "enable")) is_enable = 1; else if (unformat (input, "disable")) is_disable = 1; else if (unformat (input, "skip-version-check")) skip_version_check = 1; else { error = clib_error_return (0, "unknown input '%U'", format_unformat_error, input); goto done; } } if (is_enable && is_disable) { error = clib_error_return (0, "please specify either enable or disable" " for plugin '%s'", name); goto done; } vec_add2 (pm->configs, pc, 1); hash_set_mem (pm->config_index_by_name, name, pc - pm->configs); pc->is_enabled = is_enable; pc->is_disabled = is_disable; pc->skip_version_check = skip_version_check; pc->name = name; done: return error; } clib_error_t * vlib_plugin_config (vlib_main_t * vm, unformat_input_t * input) { plugin_main_t *pm = &vlib_plugin_main; clib_error_t *error = 0; unformat_input_t in; unformat_init (&in, 0, 0); while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { u8 *s, *v; if (unformat (input, "%s %v", &s, &v)) { if (strncmp ((const char *) s, "plugins", 8) == 0) { if (vec_len (in.buffer) > 0) vec_add1 (in.buffer, ' '); vec_add (in.buffer, v, vec_len (v)); } } else { error = clib_error_return (0, "unknown input '%U'", format_unformat_error, input); goto done; } vec_free (v); vec_free (s); } done: input = ∈ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { unformat_input_t sub_input; u8 *s = 0; if (unformat (input, "path %s", &s)) pm->plugin_path = s; else if (unformat (input, "add-path %s", &s)) pm->plugin_path_add = s; else if (unformat (input, "name-filter %s", &s)) pm->plugin_name_filter = s; else if (unformat (input, "vat-path %s", &s)) pm->vat_plugin_path = s; else if (unformat (input, "vat-name-filter %s", &s)) pm->vat_plugin_name_filter = s; else if (unformat (input, "plugin default %U", unformat_vlib_cli_sub_input, &sub_input)) { pm->plugins_default_disable = unformat (&sub_input, "disable") ? 1 : 0; unformat_free (&sub_input); } else if (unformat (input, "plugin %s %U", &s, unformat_vlib_cli_sub_input, &sub_input)) { error = config_one_plugin (vm, (char *) s, &sub_input); unformat_free (&sub_input); if (error) goto done2; } else { error = clib_error_return (0, "unknown input '%U'", format_unformat_error, input); { vec_free (s); goto done2; } } } done2: unformat_free (&in); return error; } /* discard whole 'plugins' section, as it is already consumed prior to plugin load */ static clib_error_t * plugins_config (vlib_main_t * vm, unformat_input_t * input) { u8 *junk; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "%s", &junk)) { vec_free (junk); return 0; } else return clib_error_return (0, "unknown input '%U'", format_unformat_error, input); } return 0; } VLIB_CONFIG_FUNCTION (plugins_config, "plugins"); /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */