445 lines
9 KiB
C
445 lines
9 KiB
C
|
// SPDX-License-Identifier: LGPL-2.1
|
||
|
/*
|
||
|
* Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <ctype.h>
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <dlfcn.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <unistd.h>
|
||
|
#include <dirent.h>
|
||
|
#include "event-parse.h"
|
||
|
#include "event-utils.h"
|
||
|
|
||
|
#define LOCAL_PLUGIN_DIR ".local/lib/traceevent/plugins/"
|
||
|
|
||
|
static struct registered_plugin_options {
|
||
|
struct registered_plugin_options *next;
|
||
|
struct tep_plugin_option *options;
|
||
|
} *registered_options;
|
||
|
|
||
|
static struct trace_plugin_options {
|
||
|
struct trace_plugin_options *next;
|
||
|
char *plugin;
|
||
|
char *option;
|
||
|
char *value;
|
||
|
} *trace_plugin_options;
|
||
|
|
||
|
struct plugin_list {
|
||
|
struct plugin_list *next;
|
||
|
char *name;
|
||
|
void *handle;
|
||
|
};
|
||
|
|
||
|
static void lower_case(char *str)
|
||
|
{
|
||
|
if (!str)
|
||
|
return;
|
||
|
for (; *str; str++)
|
||
|
*str = tolower(*str);
|
||
|
}
|
||
|
|
||
|
static int update_option_value(struct tep_plugin_option *op, const char *val)
|
||
|
{
|
||
|
char *op_val;
|
||
|
|
||
|
if (!val) {
|
||
|
/* toggle, only if option is boolean */
|
||
|
if (op->value)
|
||
|
/* Warn? */
|
||
|
return 0;
|
||
|
op->set ^= 1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If the option has a value then it takes a string
|
||
|
* otherwise the option is a boolean.
|
||
|
*/
|
||
|
if (op->value) {
|
||
|
op->value = val;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Option is boolean, must be either "1", "0", "true" or "false" */
|
||
|
|
||
|
op_val = strdup(val);
|
||
|
if (!op_val)
|
||
|
return -1;
|
||
|
lower_case(op_val);
|
||
|
|
||
|
if (strcmp(val, "1") == 0 || strcmp(val, "true") == 0)
|
||
|
op->set = 1;
|
||
|
else if (strcmp(val, "0") == 0 || strcmp(val, "false") == 0)
|
||
|
op->set = 0;
|
||
|
free(op_val);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* tep_plugin_list_options - get list of plugin options
|
||
|
*
|
||
|
* Returns an array of char strings that list the currently registered
|
||
|
* plugin options in the format of <plugin>:<option>. This list can be
|
||
|
* used by toggling the option.
|
||
|
*
|
||
|
* Returns NULL if there's no options registered. On error it returns
|
||
|
* INVALID_PLUGIN_LIST_OPTION
|
||
|
*
|
||
|
* Must be freed with tep_plugin_free_options_list().
|
||
|
*/
|
||
|
char **tep_plugin_list_options(void)
|
||
|
{
|
||
|
struct registered_plugin_options *reg;
|
||
|
struct tep_plugin_option *op;
|
||
|
char **list = NULL;
|
||
|
char *name;
|
||
|
int count = 0;
|
||
|
|
||
|
for (reg = registered_options; reg; reg = reg->next) {
|
||
|
for (op = reg->options; op->name; op++) {
|
||
|
char *alias = op->plugin_alias ? op->plugin_alias : op->file;
|
||
|
char **temp = list;
|
||
|
int ret;
|
||
|
|
||
|
ret = asprintf(&name, "%s:%s", alias, op->name);
|
||
|
if (ret < 0)
|
||
|
goto err;
|
||
|
|
||
|
list = realloc(list, count + 2);
|
||
|
if (!list) {
|
||
|
list = temp;
|
||
|
free(name);
|
||
|
goto err;
|
||
|
}
|
||
|
list[count++] = name;
|
||
|
list[count] = NULL;
|
||
|
}
|
||
|
}
|
||
|
return list;
|
||
|
|
||
|
err:
|
||
|
while (--count >= 0)
|
||
|
free(list[count]);
|
||
|
free(list);
|
||
|
|
||
|
return INVALID_PLUGIN_LIST_OPTION;
|
||
|
}
|
||
|
|
||
|
void tep_plugin_free_options_list(char **list)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if (!list)
|
||
|
return;
|
||
|
|
||
|
if (list == INVALID_PLUGIN_LIST_OPTION)
|
||
|
return;
|
||
|
|
||
|
for (i = 0; list[i]; i++)
|
||
|
free(list[i]);
|
||
|
|
||
|
free(list);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
update_option(const char *file, struct tep_plugin_option *option)
|
||
|
{
|
||
|
struct trace_plugin_options *op;
|
||
|
char *plugin;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (option->plugin_alias) {
|
||
|
plugin = strdup(option->plugin_alias);
|
||
|
if (!plugin)
|
||
|
return -1;
|
||
|
} else {
|
||
|
char *p;
|
||
|
plugin = strdup(file);
|
||
|
if (!plugin)
|
||
|
return -1;
|
||
|
p = strstr(plugin, ".");
|
||
|
if (p)
|
||
|
*p = '\0';
|
||
|
}
|
||
|
|
||
|
/* first look for named options */
|
||
|
for (op = trace_plugin_options; op; op = op->next) {
|
||
|
if (!op->plugin)
|
||
|
continue;
|
||
|
if (strcmp(op->plugin, plugin) != 0)
|
||
|
continue;
|
||
|
if (strcmp(op->option, option->name) != 0)
|
||
|
continue;
|
||
|
|
||
|
ret = update_option_value(option, op->value);
|
||
|
if (ret)
|
||
|
goto out;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* first look for unnamed options */
|
||
|
for (op = trace_plugin_options; op; op = op->next) {
|
||
|
if (op->plugin)
|
||
|
continue;
|
||
|
if (strcmp(op->option, option->name) != 0)
|
||
|
continue;
|
||
|
|
||
|
ret = update_option_value(option, op->value);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
free(plugin);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* tep_plugin_add_options - Add a set of options by a plugin
|
||
|
* @name: The name of the plugin adding the options
|
||
|
* @options: The set of options being loaded
|
||
|
*
|
||
|
* Sets the options with the values that have been added by user.
|
||
|
*/
|
||
|
int tep_plugin_add_options(const char *name,
|
||
|
struct tep_plugin_option *options)
|
||
|
{
|
||
|
struct registered_plugin_options *reg;
|
||
|
|
||
|
reg = malloc(sizeof(*reg));
|
||
|
if (!reg)
|
||
|
return -1;
|
||
|
reg->next = registered_options;
|
||
|
reg->options = options;
|
||
|
registered_options = reg;
|
||
|
|
||
|
while (options->name) {
|
||
|
update_option(name, options);
|
||
|
options++;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* tep_plugin_remove_options - remove plugin options that were registered
|
||
|
* @options: Options to removed that were registered with tep_plugin_add_options
|
||
|
*/
|
||
|
void tep_plugin_remove_options(struct tep_plugin_option *options)
|
||
|
{
|
||
|
struct registered_plugin_options **last;
|
||
|
struct registered_plugin_options *reg;
|
||
|
|
||
|
for (last = ®istered_options; *last; last = &(*last)->next) {
|
||
|
if ((*last)->options == options) {
|
||
|
reg = *last;
|
||
|
*last = reg->next;
|
||
|
free(reg);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* tep_print_plugins - print out the list of plugins loaded
|
||
|
* @s: the trace_seq descripter to write to
|
||
|
* @prefix: The prefix string to add before listing the option name
|
||
|
* @suffix: The suffix string ot append after the option name
|
||
|
* @list: The list of plugins (usually returned by tep_load_plugins()
|
||
|
*
|
||
|
* Writes to the trace_seq @s the list of plugins (files) that is
|
||
|
* returned by tep_load_plugins(). Use @prefix and @suffix for formating:
|
||
|
* @prefix = " ", @suffix = "\n".
|
||
|
*/
|
||
|
void tep_print_plugins(struct trace_seq *s,
|
||
|
const char *prefix, const char *suffix,
|
||
|
const struct plugin_list *list)
|
||
|
{
|
||
|
while (list) {
|
||
|
trace_seq_printf(s, "%s%s%s", prefix, list->name, suffix);
|
||
|
list = list->next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
load_plugin(struct tep_handle *pevent, const char *path,
|
||
|
const char *file, void *data)
|
||
|
{
|
||
|
struct plugin_list **plugin_list = data;
|
||
|
tep_plugin_load_func func;
|
||
|
struct plugin_list *list;
|
||
|
const char *alias;
|
||
|
char *plugin;
|
||
|
void *handle;
|
||
|
int ret;
|
||
|
|
||
|
ret = asprintf(&plugin, "%s/%s", path, file);
|
||
|
if (ret < 0) {
|
||
|
warning("could not allocate plugin memory\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
handle = dlopen(plugin, RTLD_NOW | RTLD_GLOBAL);
|
||
|
if (!handle) {
|
||
|
warning("could not load plugin '%s'\n%s\n",
|
||
|
plugin, dlerror());
|
||
|
goto out_free;
|
||
|
}
|
||
|
|
||
|
alias = dlsym(handle, TEP_PLUGIN_ALIAS_NAME);
|
||
|
if (!alias)
|
||
|
alias = file;
|
||
|
|
||
|
func = dlsym(handle, TEP_PLUGIN_LOADER_NAME);
|
||
|
if (!func) {
|
||
|
warning("could not find func '%s' in plugin '%s'\n%s\n",
|
||
|
TEP_PLUGIN_LOADER_NAME, plugin, dlerror());
|
||
|
goto out_free;
|
||
|
}
|
||
|
|
||
|
list = malloc(sizeof(*list));
|
||
|
if (!list) {
|
||
|
warning("could not allocate plugin memory\n");
|
||
|
goto out_free;
|
||
|
}
|
||
|
|
||
|
list->next = *plugin_list;
|
||
|
list->handle = handle;
|
||
|
list->name = plugin;
|
||
|
*plugin_list = list;
|
||
|
|
||
|
pr_stat("registering plugin: %s", plugin);
|
||
|
func(pevent);
|
||
|
return;
|
||
|
|
||
|
out_free:
|
||
|
free(plugin);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
load_plugins_dir(struct tep_handle *pevent, const char *suffix,
|
||
|
const char *path,
|
||
|
void (*load_plugin)(struct tep_handle *pevent,
|
||
|
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(pevent, path, name, data);
|
||
|
}
|
||
|
|
||
|
closedir(dir);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
load_plugins(struct tep_handle *pevent, const char *suffix,
|
||
|
void (*load_plugin)(struct tep_handle *pevent,
|
||
|
const char *path,
|
||
|
const char *name,
|
||
|
void *data),
|
||
|
void *data)
|
||
|
{
|
||
|
char *home;
|
||
|
char *path;
|
||
|
char *envdir;
|
||
|
int ret;
|
||
|
|
||
|
if (pevent->flags & TEP_DISABLE_PLUGINS)
|
||
|
return;
|
||
|
|
||
|
/*
|
||
|
* If a system plugin directory was defined,
|
||
|
* check that first.
|
||
|
*/
|
||
|
#ifdef PLUGIN_DIR
|
||
|
if (!(pevent->flags & TEP_DISABLE_SYS_PLUGINS))
|
||
|
load_plugins_dir(pevent, suffix, PLUGIN_DIR,
|
||
|
load_plugin, data);
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Next let the environment-set plugin directory
|
||
|
* override the system defaults.
|
||
|
*/
|
||
|
envdir = getenv("TRACEEVENT_PLUGIN_DIR");
|
||
|
if (envdir)
|
||
|
load_plugins_dir(pevent, 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) {
|
||
|
warning("could not allocate plugin memory\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
load_plugins_dir(pevent, suffix, path, load_plugin, data);
|
||
|
|
||
|
free(path);
|
||
|
}
|
||
|
|
||
|
struct plugin_list*
|
||
|
tep_load_plugins(struct tep_handle *pevent)
|
||
|
{
|
||
|
struct plugin_list *list = NULL;
|
||
|
|
||
|
load_plugins(pevent, ".so", load_plugin, &list);
|
||
|
return list;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
tep_unload_plugins(struct plugin_list *plugin_list, struct tep_handle *pevent)
|
||
|
{
|
||
|
tep_plugin_unload_func func;
|
||
|
struct plugin_list *list;
|
||
|
|
||
|
while (plugin_list) {
|
||
|
list = plugin_list;
|
||
|
plugin_list = list->next;
|
||
|
func = dlsym(list->handle, TEP_PLUGIN_UNLOADER_NAME);
|
||
|
if (func)
|
||
|
func(pevent);
|
||
|
dlclose(list->handle);
|
||
|
free(list->name);
|
||
|
free(list);
|
||
|
}
|
||
|
}
|