blob: ae3c5a070641e806cd0ee6bb83cd55dee695fbe5 [file] [log] [blame]
/*
* Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License (not later!)
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, see <http://www.gnu.org/licenses>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <ctype.h>
#include <errno.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "trace-cmd.h"
#define LOCAL_PLUGIN_DIR ".trace-cmd/plugins"
#define TRACEFS_PATH "/sys/kernel/tracing"
#define DEBUGFS_PATH "/sys/kernel/debug"
int tracecmd_disable_sys_plugins;
int tracecmd_disable_plugins;
static struct registered_plugin_options {
struct registered_plugin_options *next;
struct plugin_option *options;
} *registered_options;
static struct trace_plugin_options {
struct trace_plugin_options *next;
char *plugin;
char *option;
char *value;
} *trace_plugin_options;
#define _STR(x) #x
#define STR(x) _STR(x)
#ifndef MAX_PATH
# define MAX_PATH 1024
#endif
struct plugin_list {
struct plugin_list *next;
char *name;
void *handle;
};
/**
* trace_util_list_plugin_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.
*
* Must be freed with trace_util_free_plugin_options_list().
*/
char **trace_util_list_plugin_options(void)
{
struct registered_plugin_options *reg;
struct 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;
name = malloc_or_die(strlen(op->name) + strlen(alias) + 2);
sprintf(name, "%s:%s", alias, op->name);
list = realloc(list, count + 2);
if (!list)
die("realloc");
list[count++] = name;
list[count] = NULL;
}
}
if (!count)
return NULL;
return list;
}
void trace_util_free_plugin_options_list(char **list)
{
tracecmd_free_list(list);
}
static int process_option(const char *plugin, const char *option, const char *val);
static void update_option(const char *file, struct plugin_option *option);
/**
* trace_util_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.
*/
void trace_util_add_options(const char *name, struct plugin_option *options)
{
struct registered_plugin_options *reg;
reg = malloc_or_die(sizeof(*reg));
reg->next = registered_options;
reg->options = options;
registered_options = reg;
while (options->name) {
update_option("ftrace", options);
options++;
}
}
/**
* trace_util_remove_options - remove plugin options that were registered
* @options: Options to removed that were registered with trace_util_add_options
*/
void trace_util_remove_options(struct plugin_option *options)
{
struct registered_plugin_options **last;
struct registered_plugin_options *reg;
for (last = &registered_options; *last; last = &(*last)->next) {
if ((*last)->options == options) {
reg = *last;
*last = reg->next;
free(reg);
return;
}
}
}
/**
* trace_util_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 tracecmd_load_plugins()
*
* Writes to the trace_seq @s the list of plugins (files) that is
* returned by tracecmd_load_plugins(). Use @prefix and @suffix for formating:
* @prefix = " ", @suffix = "\n".
*/
void trace_util_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 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)
die("malloc");
}
}
static struct plugin_option *
find_registered_option(const char *plugin, const char *option)
{
struct registered_plugin_options *reg;
struct 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 struct plugin_option *
find_registered_option_parse(const char *name)
{
struct plugin_option *option;
char *option_str;
char *plugin;
option_str = strdup(name);
if (!option_str)
die("malloc");
parse_option_name(&option_str, &plugin);
option = find_registered_option(plugin, option_str);
free(option_str);
free(plugin);
return option;
}
/**
* trace_util_plugin_option_value - return a plugin option value
* @name: The name of the plugin option to find (format: <plugin>:<option>)
*
* Returns the value char string of the option. If the option is only
* boolean, then it returns "1" if set, and "0" if not.
*
* Returns NULL if the option is not found.
*/
const char *trace_util_plugin_option_value(const char *name)
{
struct plugin_option *option;
option = find_registered_option_parse(name);
if (!option)
return NULL;
if (option->value)
return option->value;
return option->set ? "1" : "0";
}
/**
* trace_util_add_option - add an option/val pair to set plugin options
* @name: The name of the option (format: <plugin>:<option> or just <option>)
* @val: (optiona) 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").
*/
void trace_util_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)
die("malloc");
parse_option_name(&option_str, &plugin);
/* 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)
die("malloc");
} 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_or_die(sizeof(*op));
memset(op, 0, sizeof(*op));
op->next = trace_plugin_options;
trace_plugin_options = op;
op->plugin = plugin;
op->option = option_str;
if (val) {
op->value = strdup(val);
if (!op->value)
die("malloc");
}
}
process_option(plugin, option_str, val);
}
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);
}
/**
* trace_util_print_plugin_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 trace_util_print_plugin_options(struct trace_seq *s)
{
struct registered_plugin_options *reg;
struct 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);
}
}
}
void parse_cmdlines(struct pevent *pevent,
char *file, int size __maybe_unused)
{
char *comm;
char *line;
char *next = NULL;
int pid;
line = strtok_r(file, "\n", &next);
while (line) {
sscanf(line, "%d %ms", &pid, &comm);
pevent_register_comm(pevent, comm, pid);
free(comm);
line = strtok_r(NULL, "\n", &next);
}
}
static void extract_trace_clock(struct pevent *pevent, char *line)
{
char *data;
char *clock;
char *next = NULL;
data = strtok_r(line, "[]", &next);
sscanf(data, "%ms", &clock);
pevent_register_trace_clock(pevent, clock);
free(clock);
}
void parse_trace_clock(struct pevent *pevent,
char *file, int size __maybe_unused)
{
char *line;
char *next = NULL;
line = strtok_r(file, " ", &next);
while (line) {
/* current trace_clock is shown as "[local]". */
if (*line == '[')
return extract_trace_clock(pevent, line);
line = strtok_r(NULL, " ", &next);
}
}
void parse_proc_kallsyms(struct pevent *pevent,
char *file, unsigned int size __maybe_unused)
{
unsigned long long addr;
char *func;
char *line;
char *next = NULL;
char *addr_str;
char *mod;
char ch;
line = strtok_r(file, "\n", &next);
while (line) {
mod = NULL;
errno = 0;
sscanf(line, "%ms %c %ms\t[%ms",
&addr_str, &ch, &func, &mod);
if (errno) {
free(addr_str);
free(func);
free(mod);
perror("sscanf");
return;
}
addr = strtoull(addr_str, NULL, 16);
free(addr_str);
/* truncate the extra ']' */
if (mod)
mod[strlen(mod) - 1] = 0;
/* Hack for arm arch that adds a lot of bogus '$a' functions */
if (func[0] != '$')
pevent_register_function(pevent, func, addr, mod);
free(func);
free(mod);
line = strtok_r(NULL, "\n", &next);
}
}
void parse_ftrace_printk(struct pevent *pevent,
char *file, unsigned int size __maybe_unused)
{
unsigned long long addr;
char *printk;
char *line;
char *next = NULL;
char *addr_str;
char *fmt;
line = strtok_r(file, "\n", &next);
while (line) {
addr_str = strtok_r(line, ":", &fmt);
if (!addr_str) {
warning("printk format with empty entry");
break;
}
addr = strtoull(addr_str, NULL, 16);
/* fmt still has a space, skip it */
printk = strdup(fmt+1);
line = strtok_r(NULL, "\n", &next);
pevent_register_print_string(pevent, printk, addr);
free(printk);
}
}
static void lower_case(char *str)
{
if (!str)
return;
for (; *str; str++)
*str = tolower(*str);
}
static int update_option_value(struct plugin_option *op, const char *val)
{
char *op_val;
int ret = 1;
if (!val) {
/* toggle, only if option is boolean */
if (op->value)
/* Warn? */
return 0;
op->set ^= 1;
return 1;
}
/*
* If the option has a value then it takes a string
* otherwise the option is a boolean.
*/
if (op->value) {
op->value = val;
return 1;
}
/* Option is boolean, must be either "1", "0", "true" or "false" */
op_val = strdup(val);
if (!op_val)
die("malloc");
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;
else
/* Warn on else? */
ret = 0;
free(op_val);
return ret;
}
static int process_option(const char *plugin, const char *option, const char *val)
{
struct plugin_option *op;
op = find_registered_option(plugin, option);
if (!op)
return 0;
return update_option_value(op, val);
}
static void update_option(const char *file, struct plugin_option *option)
{
struct trace_plugin_options *op;
char *plugin;
if (option->plugin_alias) {
plugin = strdup(option->plugin_alias);
if (!plugin)
die("malloc");
} else {
char *p;
plugin = strdup(file);
if (!plugin)
die("malloc");
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;
option->value = op->value;
option->set ^= 1;
goto out;
}
/* 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;
option->value = op->value;
option->set ^= 1;
break;
}
out:
free(plugin);
}
static void load_plugin(struct pevent *pevent, const char *path,
const char *file, void *data)
{
struct plugin_list **plugin_list = data;
pevent_plugin_load_func func;
struct plugin_list *list;
struct plugin_option *options;
const char *alias;
char *plugin;
void *handle;
plugin = malloc_or_die(strlen(path) + strlen(file) + 2);
strcpy(plugin, path);
strcat(plugin, "/");
strcat(plugin, file);
handle = dlopen(plugin, RTLD_NOW | RTLD_GLOBAL);
if (!handle) {
warning("cound not load plugin '%s'\n%s\n",
plugin, dlerror());
goto out_free;
}
alias = dlsym(handle, PEVENT_PLUGIN_ALIAS_NAME);
if (!alias)
alias = file;
options = dlsym(handle, PEVENT_PLUGIN_OPTIONS_NAME);
if (options) {
while (options->name) {
update_option(alias, options);
options++;
}
}
func = dlsym(handle, PEVENT_PLUGIN_LOADER_NAME);
if (!func) {
warning("cound not find func '%s' in plugin '%s'\n%s\n",
PEVENT_PLUGIN_LOADER_NAME, plugin, dlerror());
goto out_free;
}
list = malloc_or_die(sizeof(*list));
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 int mount_debugfs(void)
{
struct stat st;
int ret;
/* make sure debugfs exists */
ret = stat(DEBUGFS_PATH, &st);
if (ret < 0)
die("debugfs is not configured on this kernel");
ret = mount("nodev", DEBUGFS_PATH,
"debugfs", 0, NULL);
return ret;
}
static int mount_tracefs(void)
{
struct stat st;
int ret;
/* make sure debugfs exists */
ret = stat(TRACEFS_PATH, &st);
if (ret < 0)
return -1;
ret = mount("nodev", TRACEFS_PATH,
"tracefs", 0, NULL);
return ret;
}
char *tracecmd_find_tracing_dir(void)
{
char *debug_str = NULL;
char fspath[MAX_PATH+1];
char *tracing_dir;
char type[100];
int use_debug = 0;
FILE *fp;
if ((fp = fopen("/proc/mounts","r")) == NULL) {
warning("Can't open /proc/mounts for read");
return NULL;
}
while (fscanf(fp, "%*s %"
STR(MAX_PATH)
"s %99s %*s %*d %*d\n",
fspath, type) == 2) {
if (strcmp(type, "tracefs") == 0)
break;
if (!debug_str && strcmp(type, "debugfs") == 0) {
debug_str = strdup(fspath);
if (!debug_str)
return NULL;
}
}
fclose(fp);
if (strcmp(type, "tracefs") != 0) {
if (mount_tracefs() < 0) {
if (debug_str) {
strncpy(fspath, debug_str, MAX_PATH);
fspath[MAX_PATH] = 0;
} else {
if (mount_debugfs() < 0) {
warning("debugfs not mounted, please mount");
return NULL;
}
strcpy(fspath, DEBUGFS_PATH);
}
use_debug = 1;
} else
strcpy(fspath, TRACEFS_PATH);
}
free(debug_str);
if (use_debug) {
tracing_dir = malloc(strlen(fspath) + 9);
if (!tracing_dir)
return NULL;
sprintf(tracing_dir, "%s/tracing", fspath);
} else {
tracing_dir = strdup(fspath);
if (!tracing_dir)
return NULL;
}
return tracing_dir;
}
const char *tracecmd_get_tracing_dir(void)
{
static const char *tracing_dir;
if (tracing_dir)
return tracing_dir;
tracing_dir = tracecmd_find_tracing_dir();
return tracing_dir;
}
static char *append_file(const char *dir, const char *name)
{
char *file;
file = malloc_or_die(strlen(dir) + strlen(name) + 2);
if (!file)
return NULL;
sprintf(file, "%s/%s", dir, name);
return file;
}
/**
* tracecmd_add_list - add an new string to a string list.
* @list: list to add the string to (may be NULL)
* @name: the string to add
* @len: current length of list of strings.
*
* The typical usage is:
*
* systems = tracecmd_add_list(systems, name, len++);
*
* Returns the new allocated list with an allocated name added.
* The list will end with NULL.
*/
char **tracecmd_add_list(char **list, const char *name, int len)
{
if (!list)
list = malloc_or_die(sizeof(*list) * 2);
else {
list = realloc(list, sizeof(*list) * (len + 2));
if (!list)
die("Can not allocate list");
}
list[len] = strdup(name);
if (!list[len])
die("Can not allocate list");
list[len + 1] = NULL;
return list;
}
/**
* tracecmd_free_list - free a list created with tracecmd_add_list.
* @list: The list to free.
*
* Frees the list as well as the names within the list.
*/
void tracecmd_free_list(char **list)
{
int i;
if (!list)
return;
for (i = 0; list[i]; i++)
free(list[i]);
free(list);
}
/**
* tracecmd_add_id - add an int to the event id list
* @list: list to add the id to
* @id: id to add
* @len: current length of list of ids.
*
* The typical usage is:
*
* events = tracecmd_add_id(events, id, len++);
*
* Returns the new allocated list with the id included.
* the list will contain a '-1' at the end.
*
* The returned list should be freed with free().
*/
int *tracecmd_add_id(int *list, int id, int len)
{
if (!list)
list = malloc_or_die(sizeof(*list) * 2);
else {
list = realloc(list, sizeof(*list) * (len + 2));
if (!list)
die("Can ont allocate list");
}
list[len++] = id;
list[len] = -1;
return list;
}
/**
* tracecmd_event_systems - return list of systems for tracing
* @tracing_dir: directory holding the "events" directory
*
* Returns an allocated list of system names. Both the names and
* the list must be freed with free().
* The list returned ends with a "NULL" pointer.
*/
char **tracecmd_event_systems(const char *tracing_dir)
{
struct dirent *dent;
char **systems = NULL;
char *events_dir;
struct stat st;
DIR *dir;
int len = 0;
int ret;
if (!tracing_dir)
return NULL;
events_dir = append_file(tracing_dir, "events");
if (!events_dir)
return NULL;
/*
* Search all the directories in the events directory,
* and collect the ones that have the "enable" file.
*/
ret = stat(events_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
goto out_free;
dir = opendir(events_dir);
if (!dir)
goto out_free;
while ((dent = readdir(dir))) {
const char *name = dent->d_name;
char *enable;
char *sys;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
sys = append_file(events_dir, name);
ret = stat(sys, &st);
if (ret < 0 || !S_ISDIR(st.st_mode)) {
free(sys);
continue;
}
enable = append_file(sys, "enable");
ret = stat(enable, &st);
if (ret >= 0)
systems = tracecmd_add_list(systems, name, len++);
free(enable);
free(sys);
}
closedir(dir);
out_free:
free(events_dir);
return systems;
}
/**
* tracecmd_system_events - return list of events for system
* @tracing_dir: directory holding the "events" directory
* @system: the system to return the events for
*
* Returns an allocated list of event names. Both the names and
* the list must be freed with free().
* The list returned ends with a "NULL" pointer.
*/
char **tracecmd_system_events(const char *tracing_dir, const char *system)
{
struct dirent *dent;
char **events = NULL;
char *events_dir;
char *system_dir;
struct stat st;
DIR *dir;
int len = 0;
int ret;
if (!tracing_dir || !system)
return NULL;
events_dir = append_file(tracing_dir, "events");
if (!events_dir)
return NULL;
/*
* Search all the directories in the systems directory,
* and collect the ones that have the "enable" file.
*/
ret = stat(events_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
goto out_free;
system_dir = append_file(events_dir, system);
if (!system_dir)
goto out_free;
ret = stat(system_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
goto out_free_sys;
dir = opendir(system_dir);
if (!dir)
goto out_free_sys;
while ((dent = readdir(dir))) {
const char *name = dent->d_name;
char *enable;
char *event;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
event = append_file(system_dir, name);
ret = stat(event, &st);
if (ret < 0 || !S_ISDIR(st.st_mode)) {
free(event);
continue;
}
enable = append_file(event, "enable");
ret = stat(enable, &st);
if (ret >= 0)
events = tracecmd_add_list(events, name, len++);
free(enable);
free(event);
}
closedir(dir);
out_free_sys:
free(system_dir);
out_free:
free(events_dir);
return events;
}
static int read_file(const char *file, char **buffer)
{
char *buf;
int len = 0;
int fd;
int r;
fd = open(file, O_RDONLY);
if (fd < 0)
return -1;
buf = malloc_or_die(BUFSIZ + 1);
while ((r = read(fd, buf + len, BUFSIZ)) > 0) {
len += r;
buf = realloc(buf, len + BUFSIZ + 1);
if (!buf) {
len = -1;
goto out;
}
}
*buffer = buf;
buf[len] = 0;
out:
close(fd);
return len;
}
static int load_events(struct pevent *pevent, const char *system,
const char *sys_dir)
{
struct dirent *dent;
struct stat st;
DIR *dir;
int len = 0;
int ret = 0, failure = 0;
ret = stat(sys_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
return EINVAL;
dir = opendir(sys_dir);
if (!dir)
return errno;
while ((dent = readdir(dir))) {
const char *name = dent->d_name;
char *event;
char *format;
char *buf;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
event = append_file(sys_dir, name);
ret = stat(event, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
goto free_event;
format = append_file(event, "format");
ret = stat(format, &st);
if (ret < 0)
goto free_format;
len = read_file(format, &buf);
if (len < 0)
goto free_format;
ret = pevent_parse_event(pevent, buf, len, system);
free(buf);
free_format:
free(format);
free_event:
free(event);
if (ret)
failure = ret;
}
closedir(dir);
return failure;
}
static int read_header(struct pevent *pevent, const char *events_dir)
{
struct stat st;
char *header;
char *buf;
int len;
int ret = -1;
header = append_file(events_dir, "header_page");
ret = stat(header, &st);
if (ret < 0)
goto out;
len = read_file(header, &buf);
if (len < 0)
goto out;
pevent_parse_header_page(pevent, buf, len, sizeof(long));
free(buf);
ret = 0;
out:
free(header);
return ret;
}
/**
* tracecmd_local_events - create a pevent from the events on system
* @tracing_dir: The directory that contains the events.
*
* Returns a pevent structure that contains the pevents local to
* the system.
*/
struct pevent *tracecmd_local_events(const char *tracing_dir)
{
struct pevent *pevent = NULL;
pevent = pevent_alloc();
if (!pevent)
return NULL;
if (tracecmd_fill_local_events(tracing_dir, pevent)) {
pevent_free(pevent);
pevent = NULL;
}
return pevent;
}
/**
* tracecmd_fill_local_events - Fill a pevent with the events on system
* @tracing_dir: The directory that contains the events.
* @pevent: Allocated pevent which will be filled
*
* Returns whether the operation succeeded
*/
int tracecmd_fill_local_events(const char *tracing_dir, struct pevent *pevent)
{
struct dirent *dent;
char *events_dir;
struct stat st;
DIR *dir;
int ret, failure = 0;
if (!tracing_dir)
return -1;
events_dir = append_file(tracing_dir, "events");
if (!events_dir)
return -1;
ret = stat(events_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode)) {
ret = -1;
goto out_free;
}
dir = opendir(events_dir);
if (!dir) {
ret = -1;
goto out_free;
}
ret = read_header(pevent, events_dir);
if (ret < 0) {
ret = -1;
goto out_free;
}
while ((dent = readdir(dir))) {
const char *name = dent->d_name;
char *sys;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
sys = append_file(events_dir, name);
ret = stat(sys, &st);
if (ret < 0 || !S_ISDIR(st.st_mode)) {
free(sys);
continue;
}
ret = load_events(pevent, name, sys);
free(sys);
if (ret)
failure = 1;
}
closedir(dir);
/* always succeed because parsing failures are not critical */
ret = 0;
out_free:
free(events_dir);
pevent->parsing_failures = failure;
return ret;
}
/**
* tracecmd_local_plugins - returns an array of available tracer plugins
* @tracing_dir: The directory that contains the tracing directory
*
* Returns an allocate list of plugins. The array ends with NULL.
* Both the plugin names and array must be freed with free().
*/
char **tracecmd_local_plugins(const char *tracing_dir)
{
char *available_tracers;
struct stat st;
char **plugins = NULL;
char *buf;
char *str, *saveptr;
char *plugin;
int slen;
int len;
int ret;
if (!tracing_dir)
return NULL;
available_tracers = append_file(tracing_dir, "available_tracers");
if (!available_tracers)
return NULL;
ret = stat(available_tracers, &st);
if (ret < 0)
goto out_free;
len = read_file(available_tracers, &buf);
if (len < 0)
goto out_free;
len = 0;
for (str = buf; ; str = NULL) {
plugin = strtok_r(str, " ", &saveptr);
if (!plugin)
break;
if (!(slen = strlen(plugin)))
continue;
/* chop off any newlines */
if (plugin[slen - 1] == '\n')
plugin[slen - 1] = '\0';
/* Skip the non tracers */
if (strcmp(plugin, "nop") == 0 ||
strcmp(plugin, "none") == 0)
continue;
plugins = tracecmd_add_list(plugins, plugin, len++);
}
free(buf);
out_free:
free(available_tracers);
return plugins;
}
static void
trace_util_load_plugins_dir(struct pevent *pevent, const char *suffix,
const char *path,
void (*load_plugin)(struct pevent *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);
return;
}
struct add_plugin_data {
int ret;
int index;
char **files;
};
static void add_plugin_file(struct pevent *pevent, const char *path,
const char *name, void *data)
{
struct add_plugin_data *pdata = data;
char **ptr;
int size;
int i;
if (pdata->ret)
return;
size = pdata->index + 2;
ptr = realloc(pdata->files, sizeof(char *) * size);
if (!ptr)
goto out_free;
ptr[pdata->index] = strdup(name);
if (!ptr[pdata->index])
goto out_free;
pdata->files = ptr;
pdata->index++;
pdata->files[pdata->index] = NULL;
return;
out_free:
for (i = 0; i < pdata->index; i++)
free(pdata->files[i]);
free(pdata->files);
pdata->files = NULL;
pdata->ret = errno;
}
void trace_util_load_plugins(struct pevent *pevent, const char *suffix,
void (*load_plugin)(struct pevent *pevent,
const char *path,
const char *name,
void *data),
void *data)
{
char *home;
char *path;
char *envdir;
if (tracecmd_disable_plugins)
return;
/* If a system plugin directory was defined, check that first */
#ifdef PLUGIN_DIR
if (!tracecmd_disable_sys_plugins)
trace_util_load_plugins_dir(pevent, suffix, PLUGIN_DIR,
load_plugin, data);
#endif
/* Next let the environment-set plugin directory override the system defaults */
envdir = getenv("TRACE_CMD_PLUGIN_DIR");
if (envdir)
trace_util_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;
path = malloc_or_die(strlen(home) + strlen(LOCAL_PLUGIN_DIR) + 2);
strcpy(path, home);
strcat(path, "/");
strcat(path, LOCAL_PLUGIN_DIR);
trace_util_load_plugins_dir(pevent, suffix, path, load_plugin, data);
free(path);
}
/**
* trace_util_find_plugin_files - find list of possible plugin files
* @suffix: The suffix of the plugin files to find
*
* Searches the plugin directory for files that end in @suffix, and
* will return an allocated array of file names, or NULL if none is
* found.
*
* Must check against TRACECMD_ISERR(ret) as if an error happens
* the errno will be returned with the TRACECMD_ERR_MSK to denote
* such an error occurred.
*
* Use trace_util_free_plugin_files() to free the result.
*/
char **trace_util_find_plugin_files(const char *suffix)
{
struct add_plugin_data pdata;
memset(&pdata, 0, sizeof(pdata));
trace_util_load_plugins(NULL, suffix, add_plugin_file, &pdata);
if (pdata.ret)
return TRACECMD_ERROR(pdata.ret);
return pdata.files;
}
/**
* trace_util_free_plugin_files - free the result of trace_util_find_plugin_files()
* @files: The result from trace_util_find_plugin_files()
*
* Frees the contents that were allocated by trace_util_find_plugin_files().
*/
void trace_util_free_plugin_files(char **files)
{
int i;
if (!files || TRACECMD_ISERR(files))
return;
for (i = 0; files[i]; i++) {
free(files[i]);
}
free(files);
}
struct plugin_option_read {
struct plugin_option *options;
};
static void append_option(struct plugin_option_read *options,
struct plugin_option *option,
const char *alias, void *handle)
{
struct plugin_option *op;
while (option->name) {
op = malloc_or_die(sizeof(*op));
*op = *option;
op->next = options->options;
options->options = op;
op->file = strdup(alias);
op->handle = handle;
option++;
}
}
static void read_options(struct pevent *pevent, const char *path,
const char *file, void *data)
{
struct plugin_option_read *options = data;
struct plugin_option *option;
const char *alias;
int unload = 0;
char *plugin;
void *handle;
plugin = malloc_or_die(strlen(path) + strlen(file) + 2);
strcpy(plugin, path);
strcat(plugin, "/");
strcat(plugin, file);
handle = dlopen(plugin, RTLD_NOW | RTLD_GLOBAL);
if (!handle) {
warning("cound not load plugin '%s'\n%s\n",
plugin, dlerror());
goto out_free;
}
alias = dlsym(handle, PEVENT_PLUGIN_ALIAS_NAME);
if (!alias)
alias = file;
option = dlsym(handle, PEVENT_PLUGIN_OPTIONS_NAME);
if (!option) {
unload = 1;
goto out_unload;
}
append_option(options, option, alias, handle);
out_unload:
if (unload)
dlclose(handle);
out_free:
free(plugin);
}
struct plugin_option *trace_util_read_plugin_options(void)
{
struct plugin_option_read option = {
.options = NULL,
};
append_option(&option, trace_ftrace_options, "ftrace", NULL);
trace_util_load_plugins(NULL, ".so", read_options, &option);
return option.options;
}
void trace_util_free_options(struct plugin_option *options)
{
struct plugin_option *op;
void *last_handle = NULL;
while (options) {
op = options;
options = op->next;
if (op->handle && op->handle != last_handle) {
last_handle = op->handle;
dlclose(op->handle);
}
free(op->file);
free(op);
}
}
struct plugin_list *tracecmd_load_plugins(struct pevent *pevent)
{
struct plugin_list *list = NULL;
trace_util_load_plugins(pevent, ".so", load_plugin, &list);
return list;
}
void
tracecmd_unload_plugins(struct plugin_list *plugin_list, struct pevent *pevent)
{
pevent_plugin_unload_func func;
struct plugin_list *list;
while (plugin_list) {
list = plugin_list;
plugin_list = list->next;
func = dlsym(list->handle, PEVENT_PLUGIN_UNLOADER_NAME);
if (func)
func(pevent);
dlclose(list->handle);
free(list->name);
free(list);
}
}
char *tracecmd_get_tracing_file(const char *name)
{
static const char *tracing;
char *file;
if (!tracing) {
tracing = tracecmd_find_tracing_dir();
if (!tracing)
die("Can't find tracing dir");
}
file = malloc_or_die(strlen(tracing) + strlen(name) + 2);
if (!file)
return NULL;
sprintf(file, "%s/%s", tracing, name);
return file;
}
void tracecmd_put_tracing_file(char *name)
{
free(name);
}