712 lines
15 KiB
C
712 lines
15 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 <errno.h>
|
|
#include "event-parse.h"
|
|
#include "event-parse-local.h"
|
|
#include "event-utils.h"
|
|
#include "trace-seq.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 tep_plugin_list {
|
|
struct tep_plugin_list *next;
|
|
char *name;
|
|
void *handle;
|
|
};
|
|
|
|
struct tep_plugins_dir {
|
|
struct tep_plugins_dir *next;
|
|
char *path;
|
|
enum tep_plugin_load_priority prio;
|
|
};
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int parse_option_name(char **option, char **plugin)
|
|
{
|
|
char *p;
|
|
|
|
*plugin = NULL;
|
|
|
|
if ((p = strstr(*option, ":"))) {
|
|
*plugin = *option;
|
|
*p = '\0';
|
|
*option = strdup(p + 1);
|
|
if (!*option)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct tep_plugin_option *
|
|
find_registered_option(const char *plugin, const char *option)
|
|
{
|
|
struct registered_plugin_options *reg;
|
|
struct tep_plugin_option *op;
|
|
const char *op_plugin;
|
|
|
|
for (reg = registered_options; reg; reg = reg->next) {
|
|
for (op = reg->options; op->name; op++) {
|
|
if (op->plugin_alias)
|
|
op_plugin = op->plugin_alias;
|
|
else
|
|
op_plugin = op->file;
|
|
|
|
if (plugin && strcmp(plugin, op_plugin) != 0)
|
|
continue;
|
|
if (strcmp(option, op->name) != 0)
|
|
continue;
|
|
|
|
return op;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int process_option(const char *plugin, const char *option, const char *val)
|
|
{
|
|
struct tep_plugin_option *op;
|
|
|
|
op = find_registered_option(plugin, option);
|
|
if (!op)
|
|
return 0;
|
|
|
|
return update_option_value(op, val);
|
|
}
|
|
|
|
/**
|
|
* tep_plugin_add_option - add an option/val pair to set plugin options
|
|
* @name: The name of the option (format: <plugin>:<option> or just <option>)
|
|
* @val: (optional) the value for the option
|
|
*
|
|
* Modify a plugin option. If @val is given than the value of the option
|
|
* is set (note, some options just take a boolean, so @val must be either
|
|
* "1" or "0" or "true" or "false").
|
|
*/
|
|
int tep_plugin_add_option(const char *name, const char *val)
|
|
{
|
|
struct trace_plugin_options *op;
|
|
char *option_str;
|
|
char *plugin;
|
|
|
|
option_str = strdup(name);
|
|
if (!option_str)
|
|
return -ENOMEM;
|
|
|
|
if (parse_option_name(&option_str, &plugin) < 0)
|
|
return -ENOMEM;
|
|
|
|
/* If the option exists, update the val */
|
|
for (op = trace_plugin_options; op; op = op->next) {
|
|
/* Both must be NULL or not NULL */
|
|
if ((!plugin || !op->plugin) && plugin != op->plugin)
|
|
continue;
|
|
if (plugin && strcmp(plugin, op->plugin) != 0)
|
|
continue;
|
|
if (strcmp(op->option, option_str) != 0)
|
|
continue;
|
|
|
|
/* update option */
|
|
free(op->value);
|
|
if (val) {
|
|
op->value = strdup(val);
|
|
if (!op->value)
|
|
goto out_free;
|
|
} else
|
|
op->value = NULL;
|
|
|
|
/* plugin and option_str don't get freed at the end */
|
|
free(plugin);
|
|
free(option_str);
|
|
|
|
plugin = op->plugin;
|
|
option_str = op->option;
|
|
break;
|
|
}
|
|
|
|
/* If not found, create */
|
|
if (!op) {
|
|
op = malloc(sizeof(*op));
|
|
if (!op)
|
|
goto out_free;
|
|
memset(op, 0, sizeof(*op));
|
|
op->plugin = plugin;
|
|
op->option = option_str;
|
|
if (val) {
|
|
op->value = strdup(val);
|
|
if (!op->value) {
|
|
free(op);
|
|
goto out_free;
|
|
}
|
|
}
|
|
op->next = trace_plugin_options;
|
|
trace_plugin_options = op;
|
|
}
|
|
|
|
return process_option(plugin, option_str, val);
|
|
|
|
out_free:
|
|
free(plugin);
|
|
free(option_str);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void print_op_data(struct trace_seq *s, const char *name,
|
|
const char *op)
|
|
{
|
|
if (op)
|
|
trace_seq_printf(s, "%8s:\t%s\n", name, op);
|
|
}
|
|
|
|
/**
|
|
* tep_plugin_print_options - print out the registered plugin options
|
|
* @s: The trace_seq descriptor to write the plugin options into
|
|
*
|
|
* Writes a list of options into trace_seq @s.
|
|
*/
|
|
void tep_plugin_print_options(struct trace_seq *s)
|
|
{
|
|
struct registered_plugin_options *reg;
|
|
struct tep_plugin_option *op;
|
|
|
|
for (reg = registered_options; reg; reg = reg->next) {
|
|
if (reg != registered_options)
|
|
trace_seq_printf(s, "============\n");
|
|
for (op = reg->options; op->name; op++) {
|
|
if (op != reg->options)
|
|
trace_seq_printf(s, "------------\n");
|
|
print_op_data(s, "file", op->file);
|
|
print_op_data(s, "plugin", op->plugin_alias);
|
|
print_op_data(s, "option", op->name);
|
|
print_op_data(s, "desc", op->description);
|
|
print_op_data(s, "value", op->value);
|
|
trace_seq_printf(s, "%8s:\t%d\n", "set", op->set);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 tep_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 *tep, const char *path,
|
|
const char *file, void *data)
|
|
{
|
|
struct tep_plugin_list **plugin_list = data;
|
|
struct tep_plugin_option *options;
|
|
tep_plugin_load_func func;
|
|
struct tep_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;
|
|
|
|
options = dlsym(handle, TEP_PLUGIN_OPTIONS_NAME);
|
|
if (options) {
|
|
while (options->name) {
|
|
ret = update_option(alias, options);
|
|
if (ret < 0)
|
|
goto out_free;
|
|
options++;
|
|
}
|
|
}
|
|
|
|
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(tep);
|
|
return;
|
|
|
|
out_free:
|
|
free(plugin);
|
|
}
|
|
|
|
static void
|
|
load_plugins_dir(struct tep_handle *tep, const char *suffix,
|
|
const char *path,
|
|
void (*load_plugin)(struct tep_handle *tep,
|
|
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(tep, path, name, data);
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
/**
|
|
* tep_load_plugins_hook - call a user specified callback to load a plugin
|
|
* @tep: handler to traceevent context
|
|
* @suffix: filter only plugin files with given suffix
|
|
* @load_plugin: user specified callback, called for each plugin file
|
|
* @data: custom context, passed to @load_plugin
|
|
*
|
|
* Searches for traceevent plugin files and calls @load_plugin for each
|
|
* The order of plugins search is:
|
|
* - Directories, specified in @tep->plugins_dir and priority TEP_PLUGIN_FIRST
|
|
* - Directory, specified at compile time with PLUGIN_TRACEEVENT_DIR
|
|
* - Directory, specified by environment variable TRACEEVENT_PLUGIN_DIR
|
|
* - In user's home: ~/.local/lib/traceevent/plugins/
|
|
* - Directories, specified in @tep->plugins_dir and priority TEP_PLUGIN_LAST
|
|
*
|
|
*/
|
|
void tep_load_plugins_hook(struct tep_handle *tep, const char *suffix,
|
|
void (*load_plugin)(struct tep_handle *tep,
|
|
const char *path,
|
|
const char *name,
|
|
void *data),
|
|
void *data)
|
|
{
|
|
struct tep_plugins_dir *dir = NULL;
|
|
char *home;
|
|
char *path;
|
|
char *envdir;
|
|
int ret;
|
|
|
|
if (tep && tep->flags & TEP_DISABLE_PLUGINS)
|
|
return;
|
|
|
|
if (tep)
|
|
dir = tep->plugins_dir;
|
|
while (dir) {
|
|
if (dir->prio == TEP_PLUGIN_FIRST)
|
|
load_plugins_dir(tep, suffix, dir->path,
|
|
load_plugin, data);
|
|
dir = dir->next;
|
|
}
|
|
|
|
/*
|
|
* If a system plugin directory was defined,
|
|
* check that first.
|
|
*/
|
|
#ifdef PLUGIN_DIR
|
|
if (!tep || !(tep->flags & TEP_DISABLE_SYS_PLUGINS))
|
|
load_plugins_dir(tep, 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(tep, 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(tep, suffix, path, load_plugin, data);
|
|
|
|
if (tep)
|
|
dir = tep->plugins_dir;
|
|
while (dir) {
|
|
if (dir->prio == TEP_PLUGIN_LAST)
|
|
load_plugins_dir(tep, suffix, dir->path,
|
|
load_plugin, data);
|
|
dir = dir->next;
|
|
}
|
|
|
|
free(path);
|
|
}
|
|
|
|
struct tep_plugin_list*
|
|
tep_load_plugins(struct tep_handle *tep)
|
|
{
|
|
struct tep_plugin_list *list = NULL;
|
|
|
|
tep_load_plugins_hook(tep, ".so", load_plugin, &list);
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* tep_add_plugin_path - Add a new plugin directory.
|
|
* @tep: Trace event handler.
|
|
* @path: Path to a directory. All plugin files in that
|
|
* directory will be loaded.
|
|
*@prio: Load priority of the plugins in that directory.
|
|
*
|
|
* Returns -1 in case of an error, 0 otherwise.
|
|
*/
|
|
int tep_add_plugin_path(struct tep_handle *tep, char *path,
|
|
enum tep_plugin_load_priority prio)
|
|
{
|
|
struct tep_plugins_dir *dir;
|
|
|
|
if (!tep || !path)
|
|
return -1;
|
|
|
|
dir = calloc(1, sizeof(*dir));
|
|
if (!dir)
|
|
return -1;
|
|
|
|
dir->path = strdup(path);
|
|
if (!dir->path) {
|
|
free(dir);
|
|
return -1;
|
|
}
|
|
dir->prio = prio;
|
|
dir->next = tep->plugins_dir;
|
|
tep->plugins_dir = dir;
|
|
|
|
return 0;
|
|
}
|
|
|
|
__hidden void free_tep_plugin_paths(struct tep_handle *tep)
|
|
{
|
|
struct tep_plugins_dir *dir;
|
|
|
|
if (!tep)
|
|
return;
|
|
|
|
dir = tep->plugins_dir;
|
|
while (dir) {
|
|
tep->plugins_dir = tep->plugins_dir->next;
|
|
free(dir->path);
|
|
free(dir);
|
|
dir = tep->plugins_dir;
|
|
}
|
|
}
|
|
|
|
void
|
|
tep_unload_plugins(struct tep_plugin_list *plugin_list, struct tep_handle *tep)
|
|
{
|
|
tep_plugin_unload_func func;
|
|
struct tep_plugin_list *list;
|
|
|
|
while (plugin_list) {
|
|
list = plugin_list;
|
|
plugin_list = list->next;
|
|
func = dlsym(list->handle, TEP_PLUGIN_UNLOADER_NAME);
|
|
if (func)
|
|
func(tep);
|
|
dlclose(list->handle);
|
|
free(list->name);
|
|
free(list);
|
|
}
|
|
}
|