| // SPDX-License-Identifier: LGPL-2.1 |
| /* |
| * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <[email protected]> |
| * |
| */ |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <dlfcn.h> |
| #include <sys/stat.h> |
| #include <libgen.h> |
| #include "trace-cmd.h" |
| #include "trace-local.h" |
| #include "trace-cmd-local.h" |
| |
| #define LOCAL_PLUGIN_DIR ".local/lib/trace-cmd/plugins/" |
| |
| struct trace_plugin_list { |
| struct trace_plugin_list *next; |
| char *name; |
| void *handle; |
| }; |
| |
| struct trace_plugin_context { |
| enum tracecmd_context context; |
| enum tracecmd_plugin_flag flags; |
| union { |
| void *data; |
| struct tracecmd_input *trace_input; |
| struct tracecmd_output *trace_output; |
| }; |
| }; |
| |
| /** |
| * tracecmd_plugin_context_create - Create and initialize tracecmd plugins context. |
| * @context: Context of the trace-cmd command. |
| * @data: Pointer to the context specific data, which will be passed to plugins. |
| * |
| * Returns a pointer to created tracecmd plugins context, or NULL in case memory |
| * allocation fails. The returned pointer should be freed by free (). |
| */ |
| struct trace_plugin_context * |
| tracecmd_plugin_context_create(enum tracecmd_context context, void *data) |
| { |
| struct trace_plugin_context *trace; |
| |
| trace = calloc(1, sizeof(struct trace_plugin_context)); |
| if (!trace) |
| return NULL; |
| trace->context = context; |
| trace->data = data; |
| return trace; |
| } |
| |
| /** |
| * tracecmd_plugin_set_flag - Set a flag to tracecmd plugins context. |
| * @context: Context of the trace-cmd command. |
| * @flag: Flag, whil will be set. |
| * |
| */ |
| void tracecmd_plugin_set_flag(struct trace_plugin_context *context, |
| enum tracecmd_plugin_flag flag) |
| { |
| if (context) |
| context->flags |= flag; |
| } |
| |
| /** |
| * tracecmd_plugin_context_input - Get a tracecmd_input plugin context. |
| * @context: Context of the trace-cmd command. |
| * |
| * Returns pointer to tracecmd_input, if such context is available or |
| * NULL otherwise. |
| */ |
| struct tracecmd_input * |
| tracecmd_plugin_context_input(struct trace_plugin_context *context) |
| { |
| if (!context || context->context != TRACECMD_INPUT) |
| return NULL; |
| return context->trace_input; |
| } |
| |
| /** |
| * tracecmd_plugin_context_output - Get a tracecmd_output plugin context |
| * @context: Context of the trace-cmd command. |
| * |
| * Returns pointer to tracecmd_output, if such context is available or |
| * NULL otherwise. |
| */ |
| struct tracecmd_output * |
| tracecmd_plugin_context_output(struct trace_plugin_context *context) |
| { |
| if (!context || context->context != TRACECMD_OUTPUT) |
| return NULL; |
| return context->trace_output; |
| } |
| |
| static void |
| load_plugin(struct trace_plugin_context *trace, const char *path, |
| const char *file, void *data) |
| { |
| struct trace_plugin_list **plugin_list = data; |
| tracecmd_plugin_load_func func; |
| struct trace_plugin_list *list; |
| const char *alias; |
| char *plugin; |
| void *handle; |
| int ret; |
| |
| ret = asprintf(&plugin, "%s/%s", path, file); |
| if (ret < 0) { |
| tracecmd_warning("could not allocate plugin memory"); |
| return; |
| } |
| |
| handle = dlopen(plugin, RTLD_NOW | RTLD_GLOBAL); |
| if (!handle) { |
| tracecmd_warning("could not load plugin '%s'\n%s", plugin, dlerror()); |
| goto out_free; |
| } |
| |
| alias = dlsym(handle, TRACECMD_PLUGIN_ALIAS_NAME); |
| if (!alias) |
| alias = file; |
| |
| func = dlsym(handle, TRACECMD_PLUGIN_LOADER_NAME); |
| if (!func) { |
| tracecmd_warning("could not find func '%s' in plugin '%s'\n%s", |
| TRACECMD_PLUGIN_LOADER_NAME, plugin, dlerror()); |
| goto out_free; |
| } |
| |
| list = malloc(sizeof(*list)); |
| if (!list) { |
| tracecmd_warning("could not allocate plugin memory"); |
| goto out_free; |
| } |
| |
| list->next = *plugin_list; |
| list->handle = handle; |
| list->name = plugin; |
| *plugin_list = list; |
| |
| tracecmd_info("registering plugin: %s", plugin); |
| func(trace); |
| return; |
| |
| out_free: |
| free(plugin); |
| } |
| |
| static void |
| load_plugins_dir(struct trace_plugin_context *trace, const char *suffix, |
| const char *path, |
| void (*load_plugin)(struct trace_plugin_context *trace, |
| const char *path, |
| const char *name, |
| void *data), |
| void *data) |
| { |
| struct dirent *dent; |
| struct stat st; |
| DIR *dir; |
| int ret; |
| |
| ret = stat(path, &st); |
| if (ret < 0) |
| return; |
| |
| if (!S_ISDIR(st.st_mode)) |
| return; |
| |
| dir = opendir(path); |
| if (!dir) |
| return; |
| |
| while ((dent = readdir(dir))) { |
| const char *name = dent->d_name; |
| |
| if (strcmp(name, ".") == 0 || |
| strcmp(name, "..") == 0) |
| continue; |
| |
| /* Only load plugins that end in suffix */ |
| if (strcmp(name + (strlen(name) - strlen(suffix)), suffix) != 0) |
| continue; |
| |
| load_plugin(trace, path, name, data); |
| } |
| |
| closedir(dir); |
| } |
| |
| static char *get_source_plugins_dir(void) |
| { |
| char *p, path[PATH_MAX+1]; |
| int ret; |
| |
| ret = readlink("/proc/self/exe", path, PATH_MAX); |
| if (ret > PATH_MAX || ret < 0) |
| return NULL; |
| |
| path[ret] = 0; |
| dirname(path); |
| p = strrchr(path, '/'); |
| if (!p) |
| return NULL; |
| /* Check if we are in the the source tree */ |
| if (strcmp(p, "/tracecmd") != 0) |
| return NULL; |
| |
| strcpy(p, "/lib/trace-cmd/plugins"); |
| return strdup(path); |
| } |
| |
| static void |
| load_plugins_hook(struct trace_plugin_context *trace, const char *suffix, |
| void (*load_plugin)(struct trace_plugin_context *trace, |
| const char *path, |
| const char *name, |
| void *data), |
| void *data) |
| { |
| char *home; |
| char *path; |
| char *envdir; |
| int ret; |
| |
| if (trace && trace->flags & TRACECMD_DISABLE_PLUGINS) |
| return; |
| |
| /* |
| * If a system plugin directory was defined, |
| * check that first. |
| */ |
| #ifdef PLUGIN_TRACECMD_DIR |
| if (!trace || !(trace->flags & TRACECMD_DISABLE_SYS_PLUGINS)) |
| load_plugins_dir(trace, suffix, PLUGIN_TRACECMD_DIR, |
| load_plugin, data); |
| #endif |
| |
| /* |
| * Next let the environment-set plugin directory |
| * override the system defaults. |
| */ |
| envdir = getenv("TRACECMD_PLUGIN_DIR"); |
| if (envdir) |
| load_plugins_dir(trace, suffix, envdir, load_plugin, data); |
| |
| /* |
| * Now let the home directory override the environment |
| * or system defaults. |
| */ |
| home = getenv("HOME"); |
| if (!home) |
| return; |
| |
| ret = asprintf(&path, "%s/%s", home, LOCAL_PLUGIN_DIR); |
| if (ret < 0) { |
| tracecmd_warning("could not allocate plugin memory"); |
| return; |
| } |
| |
| load_plugins_dir(trace, suffix, path, load_plugin, data); |
| |
| free(path); |
| |
| path = get_source_plugins_dir(); |
| if (path) { |
| load_plugins_dir(trace, suffix, path, load_plugin, data); |
| free(path); |
| } |
| } |
| |
| /** |
| * tracecmd_load_plugins - Load trace-cmd specific plugins. |
| * @context: Context of the trace-cmd command, will be passed to the plugins |
| * at load time. |
| * |
| * Returns a list of loaded plugins |
| */ |
| struct trace_plugin_list* |
| tracecmd_load_plugins(struct trace_plugin_context *trace) |
| { |
| struct trace_plugin_list *list = NULL; |
| |
| load_plugins_hook(trace, ".so", load_plugin, &list); |
| return list; |
| } |
| |
| /** |
| * tracecmd_unload_plugins - Unload trace-cmd specific plugins. |
| * @plugin_list - List of plugins, previously loaded with tracecmd_load_plugins. |
| * @context: Context of the trace-cmd command, will be passed to the plugins |
| * at unload time. |
| * |
| */ |
| void |
| tracecmd_unload_plugins(struct trace_plugin_list *plugin_list, |
| struct trace_plugin_context *trace) |
| { |
| tracecmd_plugin_unload_func func; |
| struct trace_plugin_list *list; |
| |
| while (plugin_list) { |
| list = plugin_list; |
| plugin_list = list->next; |
| func = dlsym(list->handle, TRACECMD_PLUGIN_UNLOADER_NAME); |
| if (func) |
| func(trace); |
| dlclose(list->handle); |
| free(list->name); |
| free(list); |
| } |
| } |