mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2024-12-28 16:52:18 +00:00
b156040869
When eventfs was introduced, special care had to be done to coordinate the
freeing of the file meta data with the files that are exposed to user
space. The file meta data would have a ref count that is set when the file
is created and would be decremented and freed after the last user that
opened the file closed it. When the file meta data was to be freed, it
would set a flag (EVENT_FILE_FL_FREED) to denote that the file is freed,
and any new references made (like new opens or reads) would fail as it is
marked freed. This allowed other meta data to be freed after this flag was
set (under the event_mutex).
All the files that were dynamically created in the events directory had a
pointer to the file meta data and would call event_release() when the last
reference to the user space file was closed. This would be the time that it
is safe to free the file meta data.
A shortcut was made for the "format" file. It's i_private would point to
the "call" entry directly and not point to the file's meta data. This is
because all format files are the same for the same "call", so it was
thought there was no reason to differentiate them. The other files
maintain state (like the "enable", "trigger", etc). But this meant if the
file were to disappear, the "format" file would be unaware of it.
This caused a race that could be trigger via the user_events test (that
would create dynamic events and free them), and running a loop that would
read the user_events format files:
In one console run:
# cd tools/testing/selftests/user_events
# while true; do ./ftrace_test; done
And in another console run:
# cd /sys/kernel/tracing/
# while true; do cat events/user_events/__test_event/format; done 2>/dev/null
With KASAN memory checking, it would trigger a use-after-free bug report
(which was a real bug). This was because the format file was not checking
the file's meta data flag "EVENT_FILE_FL_FREED", so it would access the
event that the file meta data pointed to after the event was freed.
After inspection, there are other locations that were found to not check
the EVENT_FILE_FL_FREED flag when accessing the trace_event_file. Add a
new helper function: event_file_file() that will make sure that the
event_mutex is held, and will return NULL if the trace_event_file has the
EVENT_FILE_FL_FREED flag set. Have the first reference of the struct file
pointer use event_file_file() and check for NULL. Later uses can still use
the event_file_data() helper function if the event_mutex is still held and
was not released since the event_file_file() call.
Link: https://lore.kernel.org/all/20240719204701.1605950-1-minipli@grsecurity.net/
Cc: stable@vger.kernel.org
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Ajay Kaher <ajay.kaher@broadcom.com>
Cc: Ilkka Naulapää <digirigawa@gmail.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Dan Carpenter <dan.carpenter@linaro.org>
Cc: Beau Belgrave <beaub@linux.microsoft.com>
Cc: Florian Fainelli <florian.fainelli@broadcom.com>
Cc: Alexey Makhalov <alexey.makhalov@broadcom.com>
Cc: Vasavi Sirnapalli <vasavi.sirnapalli@broadcom.com>
Link: https://lore.kernel.org/20240730110657.3b69d3c1@gandalf.local.home
Fixes: b63db58e2f
("eventfs/tracing: Add callback for release of an eventfs_inode")
Reported-by: Mathias Krause <minipli@grsecurity.net>
Tested-by: Mathias Krause <minipli@grsecurity.net>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
2027 lines
52 KiB
C
2027 lines
52 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* trace_events_trigger - trace event triggers
|
|
*
|
|
* Copyright (C) 2013 Tom Zanussi <tom.zanussi@linux.intel.com>
|
|
*/
|
|
|
|
#include <linux/security.h>
|
|
#include <linux/module.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/rculist.h>
|
|
|
|
#include "trace.h"
|
|
|
|
static LIST_HEAD(trigger_commands);
|
|
static DEFINE_MUTEX(trigger_cmd_mutex);
|
|
|
|
void trigger_data_free(struct event_trigger_data *data)
|
|
{
|
|
if (data->cmd_ops->set_filter)
|
|
data->cmd_ops->set_filter(NULL, data, NULL);
|
|
|
|
/* make sure current triggers exit before free */
|
|
tracepoint_synchronize_unregister();
|
|
|
|
kfree(data);
|
|
}
|
|
|
|
/**
|
|
* event_triggers_call - Call triggers associated with a trace event
|
|
* @file: The trace_event_file associated with the event
|
|
* @buffer: The ring buffer that the event is being written to
|
|
* @rec: The trace entry for the event, NULL for unconditional invocation
|
|
* @event: The event meta data in the ring buffer
|
|
*
|
|
* For each trigger associated with an event, invoke the trigger
|
|
* function registered with the associated trigger command. If rec is
|
|
* non-NULL, it means that the trigger requires further processing and
|
|
* shouldn't be unconditionally invoked. If rec is non-NULL and the
|
|
* trigger has a filter associated with it, rec will checked against
|
|
* the filter and if the record matches the trigger will be invoked.
|
|
* If the trigger is a 'post_trigger', meaning it shouldn't be invoked
|
|
* in any case until the current event is written, the trigger
|
|
* function isn't invoked but the bit associated with the deferred
|
|
* trigger is set in the return value.
|
|
*
|
|
* Returns an enum event_trigger_type value containing a set bit for
|
|
* any trigger that should be deferred, ETT_NONE if nothing to defer.
|
|
*
|
|
* Called from tracepoint handlers (with rcu_read_lock_sched() held).
|
|
*
|
|
* Return: an enum event_trigger_type value containing a set bit for
|
|
* any trigger that should be deferred, ETT_NONE if nothing to defer.
|
|
*/
|
|
enum event_trigger_type
|
|
event_triggers_call(struct trace_event_file *file,
|
|
struct trace_buffer *buffer, void *rec,
|
|
struct ring_buffer_event *event)
|
|
{
|
|
struct event_trigger_data *data;
|
|
enum event_trigger_type tt = ETT_NONE;
|
|
struct event_filter *filter;
|
|
|
|
if (list_empty(&file->triggers))
|
|
return tt;
|
|
|
|
list_for_each_entry_rcu(data, &file->triggers, list) {
|
|
if (data->paused)
|
|
continue;
|
|
if (!rec) {
|
|
data->ops->trigger(data, buffer, rec, event);
|
|
continue;
|
|
}
|
|
filter = rcu_dereference_sched(data->filter);
|
|
if (filter && !filter_match_preds(filter, rec))
|
|
continue;
|
|
if (event_command_post_trigger(data->cmd_ops)) {
|
|
tt |= data->cmd_ops->trigger_type;
|
|
continue;
|
|
}
|
|
data->ops->trigger(data, buffer, rec, event);
|
|
}
|
|
return tt;
|
|
}
|
|
EXPORT_SYMBOL_GPL(event_triggers_call);
|
|
|
|
bool __trace_trigger_soft_disabled(struct trace_event_file *file)
|
|
{
|
|
unsigned long eflags = file->flags;
|
|
|
|
if (eflags & EVENT_FILE_FL_TRIGGER_MODE)
|
|
event_triggers_call(file, NULL, NULL, NULL);
|
|
if (eflags & EVENT_FILE_FL_SOFT_DISABLED)
|
|
return true;
|
|
if (eflags & EVENT_FILE_FL_PID_FILTER)
|
|
return trace_event_ignore_this_pid(file);
|
|
return false;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__trace_trigger_soft_disabled);
|
|
|
|
/**
|
|
* event_triggers_post_call - Call 'post_triggers' for a trace event
|
|
* @file: The trace_event_file associated with the event
|
|
* @tt: enum event_trigger_type containing a set bit for each trigger to invoke
|
|
*
|
|
* For each trigger associated with an event, invoke the trigger
|
|
* function registered with the associated trigger command, if the
|
|
* corresponding bit is set in the tt enum passed into this function.
|
|
* See @event_triggers_call for details on how those bits are set.
|
|
*
|
|
* Called from tracepoint handlers (with rcu_read_lock_sched() held).
|
|
*/
|
|
void
|
|
event_triggers_post_call(struct trace_event_file *file,
|
|
enum event_trigger_type tt)
|
|
{
|
|
struct event_trigger_data *data;
|
|
|
|
list_for_each_entry_rcu(data, &file->triggers, list) {
|
|
if (data->paused)
|
|
continue;
|
|
if (data->cmd_ops->trigger_type & tt)
|
|
data->ops->trigger(data, NULL, NULL, NULL);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(event_triggers_post_call);
|
|
|
|
#define SHOW_AVAILABLE_TRIGGERS (void *)(1UL)
|
|
|
|
static void *trigger_next(struct seq_file *m, void *t, loff_t *pos)
|
|
{
|
|
struct trace_event_file *event_file = event_file_data(m->private);
|
|
|
|
if (t == SHOW_AVAILABLE_TRIGGERS) {
|
|
(*pos)++;
|
|
return NULL;
|
|
}
|
|
return seq_list_next(t, &event_file->triggers, pos);
|
|
}
|
|
|
|
static bool check_user_trigger(struct trace_event_file *file)
|
|
{
|
|
struct event_trigger_data *data;
|
|
|
|
list_for_each_entry_rcu(data, &file->triggers, list,
|
|
lockdep_is_held(&event_mutex)) {
|
|
if (data->flags & EVENT_TRIGGER_FL_PROBE)
|
|
continue;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void *trigger_start(struct seq_file *m, loff_t *pos)
|
|
{
|
|
struct trace_event_file *event_file;
|
|
|
|
/* ->stop() is called even if ->start() fails */
|
|
mutex_lock(&event_mutex);
|
|
event_file = event_file_file(m->private);
|
|
if (unlikely(!event_file))
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
if (list_empty(&event_file->triggers) || !check_user_trigger(event_file))
|
|
return *pos == 0 ? SHOW_AVAILABLE_TRIGGERS : NULL;
|
|
|
|
return seq_list_start(&event_file->triggers, *pos);
|
|
}
|
|
|
|
static void trigger_stop(struct seq_file *m, void *t)
|
|
{
|
|
mutex_unlock(&event_mutex);
|
|
}
|
|
|
|
static int trigger_show(struct seq_file *m, void *v)
|
|
{
|
|
struct event_trigger_data *data;
|
|
struct event_command *p;
|
|
|
|
if (v == SHOW_AVAILABLE_TRIGGERS) {
|
|
seq_puts(m, "# Available triggers:\n");
|
|
seq_putc(m, '#');
|
|
mutex_lock(&trigger_cmd_mutex);
|
|
list_for_each_entry_reverse(p, &trigger_commands, list)
|
|
seq_printf(m, " %s", p->name);
|
|
seq_putc(m, '\n');
|
|
mutex_unlock(&trigger_cmd_mutex);
|
|
return 0;
|
|
}
|
|
|
|
data = list_entry(v, struct event_trigger_data, list);
|
|
data->ops->print(m, data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations event_triggers_seq_ops = {
|
|
.start = trigger_start,
|
|
.next = trigger_next,
|
|
.stop = trigger_stop,
|
|
.show = trigger_show,
|
|
};
|
|
|
|
static int event_trigger_regex_open(struct inode *inode, struct file *file)
|
|
{
|
|
int ret;
|
|
|
|
ret = security_locked_down(LOCKDOWN_TRACEFS);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mutex_lock(&event_mutex);
|
|
|
|
if (unlikely(!event_file_file(file))) {
|
|
mutex_unlock(&event_mutex);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if ((file->f_mode & FMODE_WRITE) &&
|
|
(file->f_flags & O_TRUNC)) {
|
|
struct trace_event_file *event_file;
|
|
struct event_command *p;
|
|
|
|
event_file = event_file_data(file);
|
|
|
|
list_for_each_entry(p, &trigger_commands, list) {
|
|
if (p->unreg_all)
|
|
p->unreg_all(event_file);
|
|
}
|
|
}
|
|
|
|
if (file->f_mode & FMODE_READ) {
|
|
ret = seq_open(file, &event_triggers_seq_ops);
|
|
if (!ret) {
|
|
struct seq_file *m = file->private_data;
|
|
m->private = file;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&event_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int trigger_process_regex(struct trace_event_file *file, char *buff)
|
|
{
|
|
char *command, *next;
|
|
struct event_command *p;
|
|
int ret = -EINVAL;
|
|
|
|
next = buff = skip_spaces(buff);
|
|
command = strsep(&next, ": \t");
|
|
if (next) {
|
|
next = skip_spaces(next);
|
|
if (!*next)
|
|
next = NULL;
|
|
}
|
|
command = (command[0] != '!') ? command : command + 1;
|
|
|
|
mutex_lock(&trigger_cmd_mutex);
|
|
list_for_each_entry(p, &trigger_commands, list) {
|
|
if (strcmp(p->name, command) == 0) {
|
|
ret = p->parse(p, file, buff, command, next);
|
|
goto out_unlock;
|
|
}
|
|
}
|
|
out_unlock:
|
|
mutex_unlock(&trigger_cmd_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t event_trigger_regex_write(struct file *file,
|
|
const char __user *ubuf,
|
|
size_t cnt, loff_t *ppos)
|
|
{
|
|
struct trace_event_file *event_file;
|
|
ssize_t ret;
|
|
char *buf;
|
|
|
|
if (!cnt)
|
|
return 0;
|
|
|
|
if (cnt >= PAGE_SIZE)
|
|
return -EINVAL;
|
|
|
|
buf = memdup_user_nul(ubuf, cnt);
|
|
if (IS_ERR(buf))
|
|
return PTR_ERR(buf);
|
|
|
|
strim(buf);
|
|
|
|
mutex_lock(&event_mutex);
|
|
event_file = event_file_file(file);
|
|
if (unlikely(!event_file)) {
|
|
mutex_unlock(&event_mutex);
|
|
kfree(buf);
|
|
return -ENODEV;
|
|
}
|
|
ret = trigger_process_regex(event_file, buf);
|
|
mutex_unlock(&event_mutex);
|
|
|
|
kfree(buf);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
*ppos += cnt;
|
|
ret = cnt;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int event_trigger_regex_release(struct inode *inode, struct file *file)
|
|
{
|
|
mutex_lock(&event_mutex);
|
|
|
|
if (file->f_mode & FMODE_READ)
|
|
seq_release(inode, file);
|
|
|
|
mutex_unlock(&event_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
event_trigger_write(struct file *filp, const char __user *ubuf,
|
|
size_t cnt, loff_t *ppos)
|
|
{
|
|
return event_trigger_regex_write(filp, ubuf, cnt, ppos);
|
|
}
|
|
|
|
static int
|
|
event_trigger_open(struct inode *inode, struct file *filp)
|
|
{
|
|
/* Checks for tracefs lockdown */
|
|
return event_trigger_regex_open(inode, filp);
|
|
}
|
|
|
|
static int
|
|
event_trigger_release(struct inode *inode, struct file *file)
|
|
{
|
|
return event_trigger_regex_release(inode, file);
|
|
}
|
|
|
|
const struct file_operations event_trigger_fops = {
|
|
.open = event_trigger_open,
|
|
.read = seq_read,
|
|
.write = event_trigger_write,
|
|
.llseek = tracing_lseek,
|
|
.release = event_trigger_release,
|
|
};
|
|
|
|
/*
|
|
* Currently we only register event commands from __init, so mark this
|
|
* __init too.
|
|
*/
|
|
__init int register_event_command(struct event_command *cmd)
|
|
{
|
|
struct event_command *p;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&trigger_cmd_mutex);
|
|
list_for_each_entry(p, &trigger_commands, list) {
|
|
if (strcmp(cmd->name, p->name) == 0) {
|
|
ret = -EBUSY;
|
|
goto out_unlock;
|
|
}
|
|
}
|
|
list_add(&cmd->list, &trigger_commands);
|
|
out_unlock:
|
|
mutex_unlock(&trigger_cmd_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Currently we only unregister event commands from __init, so mark
|
|
* this __init too.
|
|
*/
|
|
__init int unregister_event_command(struct event_command *cmd)
|
|
{
|
|
struct event_command *p, *n;
|
|
int ret = -ENODEV;
|
|
|
|
mutex_lock(&trigger_cmd_mutex);
|
|
list_for_each_entry_safe(p, n, &trigger_commands, list) {
|
|
if (strcmp(cmd->name, p->name) == 0) {
|
|
ret = 0;
|
|
list_del_init(&p->list);
|
|
goto out_unlock;
|
|
}
|
|
}
|
|
out_unlock:
|
|
mutex_unlock(&trigger_cmd_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* event_trigger_print - Generic event_trigger_ops @print implementation
|
|
* @name: The name of the event trigger
|
|
* @m: The seq_file being printed to
|
|
* @data: Trigger-specific data
|
|
* @filter_str: filter_str to print, if present
|
|
*
|
|
* Common implementation for event triggers to print themselves.
|
|
*
|
|
* Usually wrapped by a function that simply sets the @name of the
|
|
* trigger command and then invokes this.
|
|
*
|
|
* Return: 0 on success, errno otherwise
|
|
*/
|
|
static int
|
|
event_trigger_print(const char *name, struct seq_file *m,
|
|
void *data, char *filter_str)
|
|
{
|
|
long count = (long)data;
|
|
|
|
seq_puts(m, name);
|
|
|
|
if (count == -1)
|
|
seq_puts(m, ":unlimited");
|
|
else
|
|
seq_printf(m, ":count=%ld", count);
|
|
|
|
if (filter_str)
|
|
seq_printf(m, " if %s\n", filter_str);
|
|
else
|
|
seq_putc(m, '\n');
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* event_trigger_init - Generic event_trigger_ops @init implementation
|
|
* @data: Trigger-specific data
|
|
*
|
|
* Common implementation of event trigger initialization.
|
|
*
|
|
* Usually used directly as the @init method in event trigger
|
|
* implementations.
|
|
*
|
|
* Return: 0 on success, errno otherwise
|
|
*/
|
|
int event_trigger_init(struct event_trigger_data *data)
|
|
{
|
|
data->ref++;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* event_trigger_free - Generic event_trigger_ops @free implementation
|
|
* @data: Trigger-specific data
|
|
*
|
|
* Common implementation of event trigger de-initialization.
|
|
*
|
|
* Usually used directly as the @free method in event trigger
|
|
* implementations.
|
|
*/
|
|
static void
|
|
event_trigger_free(struct event_trigger_data *data)
|
|
{
|
|
if (WARN_ON_ONCE(data->ref <= 0))
|
|
return;
|
|
|
|
data->ref--;
|
|
if (!data->ref)
|
|
trigger_data_free(data);
|
|
}
|
|
|
|
int trace_event_trigger_enable_disable(struct trace_event_file *file,
|
|
int trigger_enable)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (trigger_enable) {
|
|
if (atomic_inc_return(&file->tm_ref) > 1)
|
|
return ret;
|
|
set_bit(EVENT_FILE_FL_TRIGGER_MODE_BIT, &file->flags);
|
|
ret = trace_event_enable_disable(file, 1, 1);
|
|
} else {
|
|
if (atomic_dec_return(&file->tm_ref) > 0)
|
|
return ret;
|
|
clear_bit(EVENT_FILE_FL_TRIGGER_MODE_BIT, &file->flags);
|
|
ret = trace_event_enable_disable(file, 0, 1);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* clear_event_triggers - Clear all triggers associated with a trace array
|
|
* @tr: The trace array to clear
|
|
*
|
|
* For each trigger, the triggering event has its tm_ref decremented
|
|
* via trace_event_trigger_enable_disable(), and any associated event
|
|
* (in the case of enable/disable_event triggers) will have its sm_ref
|
|
* decremented via free()->trace_event_enable_disable(). That
|
|
* combination effectively reverses the soft-mode/trigger state added
|
|
* by trigger registration.
|
|
*
|
|
* Must be called with event_mutex held.
|
|
*/
|
|
void
|
|
clear_event_triggers(struct trace_array *tr)
|
|
{
|
|
struct trace_event_file *file;
|
|
|
|
list_for_each_entry(file, &tr->events, list) {
|
|
struct event_trigger_data *data, *n;
|
|
list_for_each_entry_safe(data, n, &file->triggers, list) {
|
|
trace_event_trigger_enable_disable(file, 0);
|
|
list_del_rcu(&data->list);
|
|
if (data->ops->free)
|
|
data->ops->free(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* update_cond_flag - Set or reset the TRIGGER_COND bit
|
|
* @file: The trace_event_file associated with the event
|
|
*
|
|
* If an event has triggers and any of those triggers has a filter or
|
|
* a post_trigger, trigger invocation needs to be deferred until after
|
|
* the current event has logged its data, and the event should have
|
|
* its TRIGGER_COND bit set, otherwise the TRIGGER_COND bit should be
|
|
* cleared.
|
|
*/
|
|
void update_cond_flag(struct trace_event_file *file)
|
|
{
|
|
struct event_trigger_data *data;
|
|
bool set_cond = false;
|
|
|
|
lockdep_assert_held(&event_mutex);
|
|
|
|
list_for_each_entry(data, &file->triggers, list) {
|
|
if (data->filter || event_command_post_trigger(data->cmd_ops) ||
|
|
event_command_needs_rec(data->cmd_ops)) {
|
|
set_cond = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (set_cond)
|
|
set_bit(EVENT_FILE_FL_TRIGGER_COND_BIT, &file->flags);
|
|
else
|
|
clear_bit(EVENT_FILE_FL_TRIGGER_COND_BIT, &file->flags);
|
|
}
|
|
|
|
/**
|
|
* register_trigger - Generic event_command @reg implementation
|
|
* @glob: The raw string used to register the trigger
|
|
* @data: Trigger-specific data to associate with the trigger
|
|
* @file: The trace_event_file associated with the event
|
|
*
|
|
* Common implementation for event trigger registration.
|
|
*
|
|
* Usually used directly as the @reg method in event command
|
|
* implementations.
|
|
*
|
|
* Return: 0 on success, errno otherwise
|
|
*/
|
|
static int register_trigger(char *glob,
|
|
struct event_trigger_data *data,
|
|
struct trace_event_file *file)
|
|
{
|
|
struct event_trigger_data *test;
|
|
int ret = 0;
|
|
|
|
lockdep_assert_held(&event_mutex);
|
|
|
|
list_for_each_entry(test, &file->triggers, list) {
|
|
if (test->cmd_ops->trigger_type == data->cmd_ops->trigger_type) {
|
|
ret = -EEXIST;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (data->ops->init) {
|
|
ret = data->ops->init(data);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
|
|
list_add_rcu(&data->list, &file->triggers);
|
|
|
|
update_cond_flag(file);
|
|
ret = trace_event_trigger_enable_disable(file, 1);
|
|
if (ret < 0) {
|
|
list_del_rcu(&data->list);
|
|
update_cond_flag(file);
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* True if the trigger was found and unregistered, else false.
|
|
*/
|
|
static bool try_unregister_trigger(char *glob,
|
|
struct event_trigger_data *test,
|
|
struct trace_event_file *file)
|
|
{
|
|
struct event_trigger_data *data = NULL, *iter;
|
|
|
|
lockdep_assert_held(&event_mutex);
|
|
|
|
list_for_each_entry(iter, &file->triggers, list) {
|
|
if (iter->cmd_ops->trigger_type == test->cmd_ops->trigger_type) {
|
|
data = iter;
|
|
list_del_rcu(&data->list);
|
|
trace_event_trigger_enable_disable(file, 0);
|
|
update_cond_flag(file);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (data) {
|
|
if (data->ops->free)
|
|
data->ops->free(data);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* unregister_trigger - Generic event_command @unreg implementation
|
|
* @glob: The raw string used to register the trigger
|
|
* @test: Trigger-specific data used to find the trigger to remove
|
|
* @file: The trace_event_file associated with the event
|
|
*
|
|
* Common implementation for event trigger unregistration.
|
|
*
|
|
* Usually used directly as the @unreg method in event command
|
|
* implementations.
|
|
*/
|
|
static void unregister_trigger(char *glob,
|
|
struct event_trigger_data *test,
|
|
struct trace_event_file *file)
|
|
{
|
|
try_unregister_trigger(glob, test, file);
|
|
}
|
|
|
|
/*
|
|
* Event trigger parsing helper functions.
|
|
*
|
|
* These functions help make it easier to write an event trigger
|
|
* parsing function i.e. the struct event_command.parse() callback
|
|
* function responsible for parsing and registering a trigger command
|
|
* written to the 'trigger' file.
|
|
*
|
|
* A trigger command (or just 'trigger' for short) takes the form:
|
|
* [trigger] [if filter]
|
|
*
|
|
* The struct event_command.parse() callback (and other struct
|
|
* event_command functions) refer to several components of a trigger
|
|
* command. Those same components are referenced by the event trigger
|
|
* parsing helper functions defined below. These components are:
|
|
*
|
|
* cmd - the trigger command name
|
|
* glob - the trigger command name optionally prefaced with '!'
|
|
* param_and_filter - text following cmd and ':'
|
|
* param - text following cmd and ':' and stripped of filter
|
|
* filter - the optional filter text following (and including) 'if'
|
|
*
|
|
* To illustrate the use of these componenents, here are some concrete
|
|
* examples. For the following triggers:
|
|
*
|
|
* echo 'traceon:5 if pid == 0' > trigger
|
|
* - 'traceon' is both cmd and glob
|
|
* - '5 if pid == 0' is the param_and_filter
|
|
* - '5' is the param
|
|
* - 'if pid == 0' is the filter
|
|
*
|
|
* echo 'enable_event:sys:event:n' > trigger
|
|
* - 'enable_event' is both cmd and glob
|
|
* - 'sys:event:n' is the param_and_filter
|
|
* - 'sys:event:n' is the param
|
|
* - there is no filter
|
|
*
|
|
* echo 'hist:keys=pid if prio > 50' > trigger
|
|
* - 'hist' is both cmd and glob
|
|
* - 'keys=pid if prio > 50' is the param_and_filter
|
|
* - 'keys=pid' is the param
|
|
* - 'if prio > 50' is the filter
|
|
*
|
|
* echo '!enable_event:sys:event:n' > trigger
|
|
* - 'enable_event' the cmd
|
|
* - '!enable_event' is the glob
|
|
* - 'sys:event:n' is the param_and_filter
|
|
* - 'sys:event:n' is the param
|
|
* - there is no filter
|
|
*
|
|
* echo 'traceoff' > trigger
|
|
* - 'traceoff' is both cmd and glob
|
|
* - there is no param_and_filter
|
|
* - there is no param
|
|
* - there is no filter
|
|
*
|
|
* There are a few different categories of event trigger covered by
|
|
* these helpers:
|
|
*
|
|
* - triggers that don't require a parameter e.g. traceon
|
|
* - triggers that do require a parameter e.g. enable_event and hist
|
|
* - triggers that though they may not require a param may support an
|
|
* optional 'n' param (n = number of times the trigger should fire)
|
|
* e.g.: traceon:5 or enable_event:sys:event:n
|
|
* - triggers that do not support an 'n' param e.g. hist
|
|
*
|
|
* These functions can be used or ignored as necessary - it all
|
|
* depends on the complexity of the trigger, and the granularity of
|
|
* the functions supported reflects the fact that some implementations
|
|
* may need to customize certain aspects of their implementations and
|
|
* won't need certain functions. For instance, the hist trigger
|
|
* implementation doesn't use event_trigger_separate_filter() because
|
|
* it has special requirements for handling the filter.
|
|
*/
|
|
|
|
/**
|
|
* event_trigger_check_remove - check whether an event trigger specifies remove
|
|
* @glob: The trigger command string, with optional remove(!) operator
|
|
*
|
|
* The event trigger callback implementations pass in 'glob' as a
|
|
* parameter. This is the command name either with or without a
|
|
* remove(!) operator. This function simply parses the glob and
|
|
* determines whether the command corresponds to a trigger removal or
|
|
* a trigger addition.
|
|
*
|
|
* Return: true if this is a remove command, false otherwise
|
|
*/
|
|
bool event_trigger_check_remove(const char *glob)
|
|
{
|
|
return (glob && glob[0] == '!') ? true : false;
|
|
}
|
|
|
|
/**
|
|
* event_trigger_empty_param - check whether the param is empty
|
|
* @param: The trigger param string
|
|
*
|
|
* The event trigger callback implementations pass in 'param' as a
|
|
* parameter. This corresponds to the string following the command
|
|
* name minus the command name. This function can be called by a
|
|
* callback implementation for any command that requires a param; a
|
|
* callback that doesn't require a param can ignore it.
|
|
*
|
|
* Return: true if this is an empty param, false otherwise
|
|
*/
|
|
bool event_trigger_empty_param(const char *param)
|
|
{
|
|
return !param;
|
|
}
|
|
|
|
/**
|
|
* event_trigger_separate_filter - separate an event trigger from a filter
|
|
* @param_and_filter: String containing trigger and possibly filter
|
|
* @param: outparam, will be filled with a pointer to the trigger
|
|
* @filter: outparam, will be filled with a pointer to the filter
|
|
* @param_required: Specifies whether or not the param string is required
|
|
*
|
|
* Given a param string of the form '[trigger] [if filter]', this
|
|
* function separates the filter from the trigger and returns the
|
|
* trigger in @param and the filter in @filter. Either the @param
|
|
* or the @filter may be set to NULL by this function - if not set to
|
|
* NULL, they will contain strings corresponding to the trigger and
|
|
* filter.
|
|
*
|
|
* There are two cases that need to be handled with respect to the
|
|
* passed-in param: either the param is required, or it is not
|
|
* required. If @param_required is set, and there's no param, it will
|
|
* return -EINVAL. If @param_required is not set and there's a param
|
|
* that starts with a number, that corresponds to the case of a
|
|
* trigger with :n (n = number of times the trigger should fire) and
|
|
* the parsing continues normally; otherwise the function just returns
|
|
* and assumes param just contains a filter and there's nothing else
|
|
* to do.
|
|
*
|
|
* Return: 0 on success, errno otherwise
|
|
*/
|
|
int event_trigger_separate_filter(char *param_and_filter, char **param,
|
|
char **filter, bool param_required)
|
|
{
|
|
int ret = 0;
|
|
|
|
*param = *filter = NULL;
|
|
|
|
if (!param_and_filter) {
|
|
if (param_required)
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Here we check for an optional param. The only legal
|
|
* optional param is :n, and if that's the case, continue
|
|
* below. Otherwise we assume what's left is a filter and
|
|
* return it as the filter string for the caller to deal with.
|
|
*/
|
|
if (!param_required && param_and_filter && !isdigit(param_and_filter[0])) {
|
|
*filter = param_and_filter;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Separate the param from the filter (param [if filter]).
|
|
* Here we have either an optional :n param or a required
|
|
* param and an optional filter.
|
|
*/
|
|
*param = strsep(¶m_and_filter, " \t");
|
|
|
|
/*
|
|
* Here we have a filter, though it may be empty.
|
|
*/
|
|
if (param_and_filter) {
|
|
*filter = skip_spaces(param_and_filter);
|
|
if (!**filter)
|
|
*filter = NULL;
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* event_trigger_alloc - allocate and init event_trigger_data for a trigger
|
|
* @cmd_ops: The event_command operations for the trigger
|
|
* @cmd: The cmd string
|
|
* @param: The param string
|
|
* @private_data: User data to associate with the event trigger
|
|
*
|
|
* Allocate an event_trigger_data instance and initialize it. The
|
|
* @cmd_ops are used along with the @cmd and @param to get the
|
|
* trigger_ops to assign to the event_trigger_data. @private_data can
|
|
* also be passed in and associated with the event_trigger_data.
|
|
*
|
|
* Use event_trigger_free() to free an event_trigger_data object.
|
|
*
|
|
* Return: The trigger_data object success, NULL otherwise
|
|
*/
|
|
struct event_trigger_data *event_trigger_alloc(struct event_command *cmd_ops,
|
|
char *cmd,
|
|
char *param,
|
|
void *private_data)
|
|
{
|
|
struct event_trigger_data *trigger_data;
|
|
struct event_trigger_ops *trigger_ops;
|
|
|
|
trigger_ops = cmd_ops->get_trigger_ops(cmd, param);
|
|
|
|
trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL);
|
|
if (!trigger_data)
|
|
return NULL;
|
|
|
|
trigger_data->count = -1;
|
|
trigger_data->ops = trigger_ops;
|
|
trigger_data->cmd_ops = cmd_ops;
|
|
trigger_data->private_data = private_data;
|
|
|
|
INIT_LIST_HEAD(&trigger_data->list);
|
|
INIT_LIST_HEAD(&trigger_data->named_list);
|
|
RCU_INIT_POINTER(trigger_data->filter, NULL);
|
|
|
|
return trigger_data;
|
|
}
|
|
|
|
/**
|
|
* event_trigger_parse_num - parse and return the number param for a trigger
|
|
* @param: The param string
|
|
* @trigger_data: The trigger_data for the trigger
|
|
*
|
|
* Parse the :n (n = number of times the trigger should fire) param
|
|
* and set the count variable in the trigger_data to the parsed count.
|
|
*
|
|
* Return: 0 on success, errno otherwise
|
|
*/
|
|
int event_trigger_parse_num(char *param,
|
|
struct event_trigger_data *trigger_data)
|
|
{
|
|
char *number;
|
|
int ret = 0;
|
|
|
|
if (param) {
|
|
number = strsep(¶m, ":");
|
|
|
|
if (!strlen(number))
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* We use the callback data field (which is a pointer)
|
|
* as our counter.
|
|
*/
|
|
ret = kstrtoul(number, 0, &trigger_data->count);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* event_trigger_set_filter - set an event trigger's filter
|
|
* @cmd_ops: The event_command operations for the trigger
|
|
* @file: The event file for the trigger's event
|
|
* @param: The string containing the filter
|
|
* @trigger_data: The trigger_data for the trigger
|
|
*
|
|
* Set the filter for the trigger. If the filter is NULL, just return
|
|
* without error.
|
|
*
|
|
* Return: 0 on success, errno otherwise
|
|
*/
|
|
int event_trigger_set_filter(struct event_command *cmd_ops,
|
|
struct trace_event_file *file,
|
|
char *param,
|
|
struct event_trigger_data *trigger_data)
|
|
{
|
|
if (param && cmd_ops->set_filter)
|
|
return cmd_ops->set_filter(param, trigger_data, file);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* event_trigger_reset_filter - reset an event trigger's filter
|
|
* @cmd_ops: The event_command operations for the trigger
|
|
* @trigger_data: The trigger_data for the trigger
|
|
*
|
|
* Reset the filter for the trigger to no filter.
|
|
*/
|
|
void event_trigger_reset_filter(struct event_command *cmd_ops,
|
|
struct event_trigger_data *trigger_data)
|
|
{
|
|
if (cmd_ops->set_filter)
|
|
cmd_ops->set_filter(NULL, trigger_data, NULL);
|
|
}
|
|
|
|
/**
|
|
* event_trigger_register - register an event trigger
|
|
* @cmd_ops: The event_command operations for the trigger
|
|
* @file: The event file for the trigger's event
|
|
* @glob: The trigger command string, with optional remove(!) operator
|
|
* @trigger_data: The trigger_data for the trigger
|
|
*
|
|
* Register an event trigger. The @cmd_ops are used to call the
|
|
* cmd_ops->reg() function which actually does the registration.
|
|
*
|
|
* Return: 0 on success, errno otherwise
|
|
*/
|
|
int event_trigger_register(struct event_command *cmd_ops,
|
|
struct trace_event_file *file,
|
|
char *glob,
|
|
struct event_trigger_data *trigger_data)
|
|
{
|
|
return cmd_ops->reg(glob, trigger_data, file);
|
|
}
|
|
|
|
/**
|
|
* event_trigger_unregister - unregister an event trigger
|
|
* @cmd_ops: The event_command operations for the trigger
|
|
* @file: The event file for the trigger's event
|
|
* @glob: The trigger command string, with optional remove(!) operator
|
|
* @trigger_data: The trigger_data for the trigger
|
|
*
|
|
* Unregister an event trigger. The @cmd_ops are used to call the
|
|
* cmd_ops->unreg() function which actually does the unregistration.
|
|
*/
|
|
void event_trigger_unregister(struct event_command *cmd_ops,
|
|
struct trace_event_file *file,
|
|
char *glob,
|
|
struct event_trigger_data *trigger_data)
|
|
{
|
|
cmd_ops->unreg(glob, trigger_data, file);
|
|
}
|
|
|
|
/*
|
|
* End event trigger parsing helper functions.
|
|
*/
|
|
|
|
/**
|
|
* event_trigger_parse - Generic event_command @parse implementation
|
|
* @cmd_ops: The command ops, used for trigger registration
|
|
* @file: The trace_event_file associated with the event
|
|
* @glob: The raw string used to register the trigger
|
|
* @cmd: The cmd portion of the string used to register the trigger
|
|
* @param_and_filter: The param and filter portion of the string used to register the trigger
|
|
*
|
|
* Common implementation for event command parsing and trigger
|
|
* instantiation.
|
|
*
|
|
* Usually used directly as the @parse method in event command
|
|
* implementations.
|
|
*
|
|
* Return: 0 on success, errno otherwise
|
|
*/
|
|
static int
|
|
event_trigger_parse(struct event_command *cmd_ops,
|
|
struct trace_event_file *file,
|
|
char *glob, char *cmd, char *param_and_filter)
|
|
{
|
|
struct event_trigger_data *trigger_data;
|
|
char *param, *filter;
|
|
bool remove;
|
|
int ret;
|
|
|
|
remove = event_trigger_check_remove(glob);
|
|
|
|
ret = event_trigger_separate_filter(param_and_filter, ¶m, &filter, false);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = -ENOMEM;
|
|
trigger_data = event_trigger_alloc(cmd_ops, cmd, param, file);
|
|
if (!trigger_data)
|
|
goto out;
|
|
|
|
if (remove) {
|
|
event_trigger_unregister(cmd_ops, file, glob+1, trigger_data);
|
|
kfree(trigger_data);
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
ret = event_trigger_parse_num(param, trigger_data);
|
|
if (ret)
|
|
goto out_free;
|
|
|
|
ret = event_trigger_set_filter(cmd_ops, file, filter, trigger_data);
|
|
if (ret < 0)
|
|
goto out_free;
|
|
|
|
/* Up the trigger_data count to make sure reg doesn't free it on failure */
|
|
event_trigger_init(trigger_data);
|
|
|
|
ret = event_trigger_register(cmd_ops, file, glob, trigger_data);
|
|
if (ret)
|
|
goto out_free;
|
|
|
|
/* Down the counter of trigger_data or free it if not used anymore */
|
|
event_trigger_free(trigger_data);
|
|
out:
|
|
return ret;
|
|
|
|
out_free:
|
|
event_trigger_reset_filter(cmd_ops, trigger_data);
|
|
kfree(trigger_data);
|
|
goto out;
|
|
}
|
|
|
|
/**
|
|
* set_trigger_filter - Generic event_command @set_filter implementation
|
|
* @filter_str: The filter string for the trigger, NULL to remove filter
|
|
* @trigger_data: Trigger-specific data
|
|
* @file: The trace_event_file associated with the event
|
|
*
|
|
* Common implementation for event command filter parsing and filter
|
|
* instantiation.
|
|
*
|
|
* Usually used directly as the @set_filter method in event command
|
|
* implementations.
|
|
*
|
|
* Also used to remove a filter (if filter_str = NULL).
|
|
*
|
|
* Return: 0 on success, errno otherwise
|
|
*/
|
|
int set_trigger_filter(char *filter_str,
|
|
struct event_trigger_data *trigger_data,
|
|
struct trace_event_file *file)
|
|
{
|
|
struct event_trigger_data *data = trigger_data;
|
|
struct event_filter *filter = NULL, *tmp;
|
|
int ret = -EINVAL;
|
|
char *s;
|
|
|
|
if (!filter_str) /* clear the current filter */
|
|
goto assign;
|
|
|
|
s = strsep(&filter_str, " \t");
|
|
|
|
if (!strlen(s) || strcmp(s, "if") != 0)
|
|
goto out;
|
|
|
|
if (!filter_str)
|
|
goto out;
|
|
|
|
/* The filter is for the 'trigger' event, not the triggered event */
|
|
ret = create_event_filter(file->tr, file->event_call,
|
|
filter_str, true, &filter);
|
|
|
|
/* Only enabled set_str for error handling */
|
|
if (filter) {
|
|
kfree(filter->filter_string);
|
|
filter->filter_string = NULL;
|
|
}
|
|
|
|
/*
|
|
* If create_event_filter() fails, filter still needs to be freed.
|
|
* Which the calling code will do with data->filter.
|
|
*/
|
|
assign:
|
|
tmp = rcu_access_pointer(data->filter);
|
|
|
|
rcu_assign_pointer(data->filter, filter);
|
|
|
|
if (tmp) {
|
|
/*
|
|
* Make sure the call is done with the filter.
|
|
* It is possible that a filter could fail at boot up,
|
|
* and then this path will be called. Avoid the synchronization
|
|
* in that case.
|
|
*/
|
|
if (system_state != SYSTEM_BOOTING)
|
|
tracepoint_synchronize_unregister();
|
|
free_event_filter(tmp);
|
|
}
|
|
|
|
kfree(data->filter_str);
|
|
data->filter_str = NULL;
|
|
|
|
if (filter_str) {
|
|
data->filter_str = kstrdup(filter_str, GFP_KERNEL);
|
|
if (!data->filter_str) {
|
|
free_event_filter(rcu_access_pointer(data->filter));
|
|
data->filter = NULL;
|
|
ret = -ENOMEM;
|
|
}
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static LIST_HEAD(named_triggers);
|
|
|
|
/**
|
|
* find_named_trigger - Find the common named trigger associated with @name
|
|
* @name: The name of the set of named triggers to find the common data for
|
|
*
|
|
* Named triggers are sets of triggers that share a common set of
|
|
* trigger data. The first named trigger registered with a given name
|
|
* owns the common trigger data that the others subsequently
|
|
* registered with the same name will reference. This function
|
|
* returns the common trigger data associated with that first
|
|
* registered instance.
|
|
*
|
|
* Return: the common trigger data for the given named trigger on
|
|
* success, NULL otherwise.
|
|
*/
|
|
struct event_trigger_data *find_named_trigger(const char *name)
|
|
{
|
|
struct event_trigger_data *data;
|
|
|
|
if (!name)
|
|
return NULL;
|
|
|
|
list_for_each_entry(data, &named_triggers, named_list) {
|
|
if (data->named_data)
|
|
continue;
|
|
if (strcmp(data->name, name) == 0)
|
|
return data;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* is_named_trigger - determine if a given trigger is a named trigger
|
|
* @test: The trigger data to test
|
|
*
|
|
* Return: true if 'test' is a named trigger, false otherwise.
|
|
*/
|
|
bool is_named_trigger(struct event_trigger_data *test)
|
|
{
|
|
struct event_trigger_data *data;
|
|
|
|
list_for_each_entry(data, &named_triggers, named_list) {
|
|
if (test == data)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* save_named_trigger - save the trigger in the named trigger list
|
|
* @name: The name of the named trigger set
|
|
* @data: The trigger data to save
|
|
*
|
|
* Return: 0 if successful, negative error otherwise.
|
|
*/
|
|
int save_named_trigger(const char *name, struct event_trigger_data *data)
|
|
{
|
|
data->name = kstrdup(name, GFP_KERNEL);
|
|
if (!data->name)
|
|
return -ENOMEM;
|
|
|
|
list_add(&data->named_list, &named_triggers);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* del_named_trigger - delete a trigger from the named trigger list
|
|
* @data: The trigger data to delete
|
|
*/
|
|
void del_named_trigger(struct event_trigger_data *data)
|
|
{
|
|
kfree(data->name);
|
|
data->name = NULL;
|
|
|
|
list_del(&data->named_list);
|
|
}
|
|
|
|
static void __pause_named_trigger(struct event_trigger_data *data, bool pause)
|
|
{
|
|
struct event_trigger_data *test;
|
|
|
|
list_for_each_entry(test, &named_triggers, named_list) {
|
|
if (strcmp(test->name, data->name) == 0) {
|
|
if (pause) {
|
|
test->paused_tmp = test->paused;
|
|
test->paused = true;
|
|
} else {
|
|
test->paused = test->paused_tmp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pause_named_trigger - Pause all named triggers with the same name
|
|
* @data: The trigger data of a named trigger to pause
|
|
*
|
|
* Pauses a named trigger along with all other triggers having the
|
|
* same name. Because named triggers share a common set of data,
|
|
* pausing only one is meaningless, so pausing one named trigger needs
|
|
* to pause all triggers with the same name.
|
|
*/
|
|
void pause_named_trigger(struct event_trigger_data *data)
|
|
{
|
|
__pause_named_trigger(data, true);
|
|
}
|
|
|
|
/**
|
|
* unpause_named_trigger - Un-pause all named triggers with the same name
|
|
* @data: The trigger data of a named trigger to unpause
|
|
*
|
|
* Un-pauses a named trigger along with all other triggers having the
|
|
* same name. Because named triggers share a common set of data,
|
|
* unpausing only one is meaningless, so unpausing one named trigger
|
|
* needs to unpause all triggers with the same name.
|
|
*/
|
|
void unpause_named_trigger(struct event_trigger_data *data)
|
|
{
|
|
__pause_named_trigger(data, false);
|
|
}
|
|
|
|
/**
|
|
* set_named_trigger_data - Associate common named trigger data
|
|
* @data: The trigger data to associate
|
|
* @named_data: The common named trigger to be associated
|
|
*
|
|
* Named triggers are sets of triggers that share a common set of
|
|
* trigger data. The first named trigger registered with a given name
|
|
* owns the common trigger data that the others subsequently
|
|
* registered with the same name will reference. This function
|
|
* associates the common trigger data from the first trigger with the
|
|
* given trigger.
|
|
*/
|
|
void set_named_trigger_data(struct event_trigger_data *data,
|
|
struct event_trigger_data *named_data)
|
|
{
|
|
data->named_data = named_data;
|
|
}
|
|
|
|
struct event_trigger_data *
|
|
get_named_trigger_data(struct event_trigger_data *data)
|
|
{
|
|
return data->named_data;
|
|
}
|
|
|
|
static void
|
|
traceon_trigger(struct event_trigger_data *data,
|
|
struct trace_buffer *buffer, void *rec,
|
|
struct ring_buffer_event *event)
|
|
{
|
|
struct trace_event_file *file = data->private_data;
|
|
|
|
if (file) {
|
|
if (tracer_tracing_is_on(file->tr))
|
|
return;
|
|
|
|
tracer_tracing_on(file->tr);
|
|
return;
|
|
}
|
|
|
|
if (tracing_is_on())
|
|
return;
|
|
|
|
tracing_on();
|
|
}
|
|
|
|
static void
|
|
traceon_count_trigger(struct event_trigger_data *data,
|
|
struct trace_buffer *buffer, void *rec,
|
|
struct ring_buffer_event *event)
|
|
{
|
|
struct trace_event_file *file = data->private_data;
|
|
|
|
if (file) {
|
|
if (tracer_tracing_is_on(file->tr))
|
|
return;
|
|
} else {
|
|
if (tracing_is_on())
|
|
return;
|
|
}
|
|
|
|
if (!data->count)
|
|
return;
|
|
|
|
if (data->count != -1)
|
|
(data->count)--;
|
|
|
|
if (file)
|
|
tracer_tracing_on(file->tr);
|
|
else
|
|
tracing_on();
|
|
}
|
|
|
|
static void
|
|
traceoff_trigger(struct event_trigger_data *data,
|
|
struct trace_buffer *buffer, void *rec,
|
|
struct ring_buffer_event *event)
|
|
{
|
|
struct trace_event_file *file = data->private_data;
|
|
|
|
if (file) {
|
|
if (!tracer_tracing_is_on(file->tr))
|
|
return;
|
|
|
|
tracer_tracing_off(file->tr);
|
|
return;
|
|
}
|
|
|
|
if (!tracing_is_on())
|
|
return;
|
|
|
|
tracing_off();
|
|
}
|
|
|
|
static void
|
|
traceoff_count_trigger(struct event_trigger_data *data,
|
|
struct trace_buffer *buffer, void *rec,
|
|
struct ring_buffer_event *event)
|
|
{
|
|
struct trace_event_file *file = data->private_data;
|
|
|
|
if (file) {
|
|
if (!tracer_tracing_is_on(file->tr))
|
|
return;
|
|
} else {
|
|
if (!tracing_is_on())
|
|
return;
|
|
}
|
|
|
|
if (!data->count)
|
|
return;
|
|
|
|
if (data->count != -1)
|
|
(data->count)--;
|
|
|
|
if (file)
|
|
tracer_tracing_off(file->tr);
|
|
else
|
|
tracing_off();
|
|
}
|
|
|
|
static int
|
|
traceon_trigger_print(struct seq_file *m, struct event_trigger_data *data)
|
|
{
|
|
return event_trigger_print("traceon", m, (void *)data->count,
|
|
data->filter_str);
|
|
}
|
|
|
|
static int
|
|
traceoff_trigger_print(struct seq_file *m, struct event_trigger_data *data)
|
|
{
|
|
return event_trigger_print("traceoff", m, (void *)data->count,
|
|
data->filter_str);
|
|
}
|
|
|
|
static struct event_trigger_ops traceon_trigger_ops = {
|
|
.trigger = traceon_trigger,
|
|
.print = traceon_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_trigger_free,
|
|
};
|
|
|
|
static struct event_trigger_ops traceon_count_trigger_ops = {
|
|
.trigger = traceon_count_trigger,
|
|
.print = traceon_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_trigger_free,
|
|
};
|
|
|
|
static struct event_trigger_ops traceoff_trigger_ops = {
|
|
.trigger = traceoff_trigger,
|
|
.print = traceoff_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_trigger_free,
|
|
};
|
|
|
|
static struct event_trigger_ops traceoff_count_trigger_ops = {
|
|
.trigger = traceoff_count_trigger,
|
|
.print = traceoff_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_trigger_free,
|
|
};
|
|
|
|
static struct event_trigger_ops *
|
|
onoff_get_trigger_ops(char *cmd, char *param)
|
|
{
|
|
struct event_trigger_ops *ops;
|
|
|
|
/* we register both traceon and traceoff to this callback */
|
|
if (strcmp(cmd, "traceon") == 0)
|
|
ops = param ? &traceon_count_trigger_ops :
|
|
&traceon_trigger_ops;
|
|
else
|
|
ops = param ? &traceoff_count_trigger_ops :
|
|
&traceoff_trigger_ops;
|
|
|
|
return ops;
|
|
}
|
|
|
|
static struct event_command trigger_traceon_cmd = {
|
|
.name = "traceon",
|
|
.trigger_type = ETT_TRACE_ONOFF,
|
|
.parse = event_trigger_parse,
|
|
.reg = register_trigger,
|
|
.unreg = unregister_trigger,
|
|
.get_trigger_ops = onoff_get_trigger_ops,
|
|
.set_filter = set_trigger_filter,
|
|
};
|
|
|
|
static struct event_command trigger_traceoff_cmd = {
|
|
.name = "traceoff",
|
|
.trigger_type = ETT_TRACE_ONOFF,
|
|
.flags = EVENT_CMD_FL_POST_TRIGGER,
|
|
.parse = event_trigger_parse,
|
|
.reg = register_trigger,
|
|
.unreg = unregister_trigger,
|
|
.get_trigger_ops = onoff_get_trigger_ops,
|
|
.set_filter = set_trigger_filter,
|
|
};
|
|
|
|
#ifdef CONFIG_TRACER_SNAPSHOT
|
|
static void
|
|
snapshot_trigger(struct event_trigger_data *data,
|
|
struct trace_buffer *buffer, void *rec,
|
|
struct ring_buffer_event *event)
|
|
{
|
|
struct trace_event_file *file = data->private_data;
|
|
|
|
if (file)
|
|
tracing_snapshot_instance(file->tr);
|
|
else
|
|
tracing_snapshot();
|
|
}
|
|
|
|
static void
|
|
snapshot_count_trigger(struct event_trigger_data *data,
|
|
struct trace_buffer *buffer, void *rec,
|
|
struct ring_buffer_event *event)
|
|
{
|
|
if (!data->count)
|
|
return;
|
|
|
|
if (data->count != -1)
|
|
(data->count)--;
|
|
|
|
snapshot_trigger(data, buffer, rec, event);
|
|
}
|
|
|
|
static int
|
|
register_snapshot_trigger(char *glob,
|
|
struct event_trigger_data *data,
|
|
struct trace_event_file *file)
|
|
{
|
|
int ret = tracing_arm_snapshot(file->tr);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = register_trigger(glob, data, file);
|
|
if (ret < 0)
|
|
tracing_disarm_snapshot(file->tr);
|
|
return ret;
|
|
}
|
|
|
|
static void unregister_snapshot_trigger(char *glob,
|
|
struct event_trigger_data *data,
|
|
struct trace_event_file *file)
|
|
{
|
|
if (try_unregister_trigger(glob, data, file))
|
|
tracing_disarm_snapshot(file->tr);
|
|
}
|
|
|
|
static int
|
|
snapshot_trigger_print(struct seq_file *m, struct event_trigger_data *data)
|
|
{
|
|
return event_trigger_print("snapshot", m, (void *)data->count,
|
|
data->filter_str);
|
|
}
|
|
|
|
static struct event_trigger_ops snapshot_trigger_ops = {
|
|
.trigger = snapshot_trigger,
|
|
.print = snapshot_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_trigger_free,
|
|
};
|
|
|
|
static struct event_trigger_ops snapshot_count_trigger_ops = {
|
|
.trigger = snapshot_count_trigger,
|
|
.print = snapshot_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_trigger_free,
|
|
};
|
|
|
|
static struct event_trigger_ops *
|
|
snapshot_get_trigger_ops(char *cmd, char *param)
|
|
{
|
|
return param ? &snapshot_count_trigger_ops : &snapshot_trigger_ops;
|
|
}
|
|
|
|
static struct event_command trigger_snapshot_cmd = {
|
|
.name = "snapshot",
|
|
.trigger_type = ETT_SNAPSHOT,
|
|
.parse = event_trigger_parse,
|
|
.reg = register_snapshot_trigger,
|
|
.unreg = unregister_snapshot_trigger,
|
|
.get_trigger_ops = snapshot_get_trigger_ops,
|
|
.set_filter = set_trigger_filter,
|
|
};
|
|
|
|
static __init int register_trigger_snapshot_cmd(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = register_event_command(&trigger_snapshot_cmd);
|
|
WARN_ON(ret < 0);
|
|
|
|
return ret;
|
|
}
|
|
#else
|
|
static __init int register_trigger_snapshot_cmd(void) { return 0; }
|
|
#endif /* CONFIG_TRACER_SNAPSHOT */
|
|
|
|
#ifdef CONFIG_STACKTRACE
|
|
#ifdef CONFIG_UNWINDER_ORC
|
|
/* Skip 2:
|
|
* event_triggers_post_call()
|
|
* trace_event_raw_event_xxx()
|
|
*/
|
|
# define STACK_SKIP 2
|
|
#else
|
|
/*
|
|
* Skip 4:
|
|
* stacktrace_trigger()
|
|
* event_triggers_post_call()
|
|
* trace_event_buffer_commit()
|
|
* trace_event_raw_event_xxx()
|
|
*/
|
|
#define STACK_SKIP 4
|
|
#endif
|
|
|
|
static void
|
|
stacktrace_trigger(struct event_trigger_data *data,
|
|
struct trace_buffer *buffer, void *rec,
|
|
struct ring_buffer_event *event)
|
|
{
|
|
struct trace_event_file *file = data->private_data;
|
|
|
|
if (file)
|
|
__trace_stack(file->tr, tracing_gen_ctx(), STACK_SKIP);
|
|
else
|
|
trace_dump_stack(STACK_SKIP);
|
|
}
|
|
|
|
static void
|
|
stacktrace_count_trigger(struct event_trigger_data *data,
|
|
struct trace_buffer *buffer, void *rec,
|
|
struct ring_buffer_event *event)
|
|
{
|
|
if (!data->count)
|
|
return;
|
|
|
|
if (data->count != -1)
|
|
(data->count)--;
|
|
|
|
stacktrace_trigger(data, buffer, rec, event);
|
|
}
|
|
|
|
static int
|
|
stacktrace_trigger_print(struct seq_file *m, struct event_trigger_data *data)
|
|
{
|
|
return event_trigger_print("stacktrace", m, (void *)data->count,
|
|
data->filter_str);
|
|
}
|
|
|
|
static struct event_trigger_ops stacktrace_trigger_ops = {
|
|
.trigger = stacktrace_trigger,
|
|
.print = stacktrace_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_trigger_free,
|
|
};
|
|
|
|
static struct event_trigger_ops stacktrace_count_trigger_ops = {
|
|
.trigger = stacktrace_count_trigger,
|
|
.print = stacktrace_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_trigger_free,
|
|
};
|
|
|
|
static struct event_trigger_ops *
|
|
stacktrace_get_trigger_ops(char *cmd, char *param)
|
|
{
|
|
return param ? &stacktrace_count_trigger_ops : &stacktrace_trigger_ops;
|
|
}
|
|
|
|
static struct event_command trigger_stacktrace_cmd = {
|
|
.name = "stacktrace",
|
|
.trigger_type = ETT_STACKTRACE,
|
|
.flags = EVENT_CMD_FL_POST_TRIGGER,
|
|
.parse = event_trigger_parse,
|
|
.reg = register_trigger,
|
|
.unreg = unregister_trigger,
|
|
.get_trigger_ops = stacktrace_get_trigger_ops,
|
|
.set_filter = set_trigger_filter,
|
|
};
|
|
|
|
static __init int register_trigger_stacktrace_cmd(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = register_event_command(&trigger_stacktrace_cmd);
|
|
WARN_ON(ret < 0);
|
|
|
|
return ret;
|
|
}
|
|
#else
|
|
static __init int register_trigger_stacktrace_cmd(void) { return 0; }
|
|
#endif /* CONFIG_STACKTRACE */
|
|
|
|
static __init void unregister_trigger_traceon_traceoff_cmds(void)
|
|
{
|
|
unregister_event_command(&trigger_traceon_cmd);
|
|
unregister_event_command(&trigger_traceoff_cmd);
|
|
}
|
|
|
|
static void
|
|
event_enable_trigger(struct event_trigger_data *data,
|
|
struct trace_buffer *buffer, void *rec,
|
|
struct ring_buffer_event *event)
|
|
{
|
|
struct enable_trigger_data *enable_data = data->private_data;
|
|
|
|
if (enable_data->enable)
|
|
clear_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &enable_data->file->flags);
|
|
else
|
|
set_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &enable_data->file->flags);
|
|
}
|
|
|
|
static void
|
|
event_enable_count_trigger(struct event_trigger_data *data,
|
|
struct trace_buffer *buffer, void *rec,
|
|
struct ring_buffer_event *event)
|
|
{
|
|
struct enable_trigger_data *enable_data = data->private_data;
|
|
|
|
if (!data->count)
|
|
return;
|
|
|
|
/* Skip if the event is in a state we want to switch to */
|
|
if (enable_data->enable == !(enable_data->file->flags & EVENT_FILE_FL_SOFT_DISABLED))
|
|
return;
|
|
|
|
if (data->count != -1)
|
|
(data->count)--;
|
|
|
|
event_enable_trigger(data, buffer, rec, event);
|
|
}
|
|
|
|
int event_enable_trigger_print(struct seq_file *m,
|
|
struct event_trigger_data *data)
|
|
{
|
|
struct enable_trigger_data *enable_data = data->private_data;
|
|
|
|
seq_printf(m, "%s:%s:%s",
|
|
enable_data->hist ?
|
|
(enable_data->enable ? ENABLE_HIST_STR : DISABLE_HIST_STR) :
|
|
(enable_data->enable ? ENABLE_EVENT_STR : DISABLE_EVENT_STR),
|
|
enable_data->file->event_call->class->system,
|
|
trace_event_name(enable_data->file->event_call));
|
|
|
|
if (data->count == -1)
|
|
seq_puts(m, ":unlimited");
|
|
else
|
|
seq_printf(m, ":count=%ld", data->count);
|
|
|
|
if (data->filter_str)
|
|
seq_printf(m, " if %s\n", data->filter_str);
|
|
else
|
|
seq_putc(m, '\n');
|
|
|
|
return 0;
|
|
}
|
|
|
|
void event_enable_trigger_free(struct event_trigger_data *data)
|
|
{
|
|
struct enable_trigger_data *enable_data = data->private_data;
|
|
|
|
if (WARN_ON_ONCE(data->ref <= 0))
|
|
return;
|
|
|
|
data->ref--;
|
|
if (!data->ref) {
|
|
/* Remove the SOFT_MODE flag */
|
|
trace_event_enable_disable(enable_data->file, 0, 1);
|
|
trace_event_put_ref(enable_data->file->event_call);
|
|
trigger_data_free(data);
|
|
kfree(enable_data);
|
|
}
|
|
}
|
|
|
|
static struct event_trigger_ops event_enable_trigger_ops = {
|
|
.trigger = event_enable_trigger,
|
|
.print = event_enable_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_enable_trigger_free,
|
|
};
|
|
|
|
static struct event_trigger_ops event_enable_count_trigger_ops = {
|
|
.trigger = event_enable_count_trigger,
|
|
.print = event_enable_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_enable_trigger_free,
|
|
};
|
|
|
|
static struct event_trigger_ops event_disable_trigger_ops = {
|
|
.trigger = event_enable_trigger,
|
|
.print = event_enable_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_enable_trigger_free,
|
|
};
|
|
|
|
static struct event_trigger_ops event_disable_count_trigger_ops = {
|
|
.trigger = event_enable_count_trigger,
|
|
.print = event_enable_trigger_print,
|
|
.init = event_trigger_init,
|
|
.free = event_enable_trigger_free,
|
|
};
|
|
|
|
int event_enable_trigger_parse(struct event_command *cmd_ops,
|
|
struct trace_event_file *file,
|
|
char *glob, char *cmd, char *param_and_filter)
|
|
{
|
|
struct trace_event_file *event_enable_file;
|
|
struct enable_trigger_data *enable_data;
|
|
struct event_trigger_data *trigger_data;
|
|
struct trace_array *tr = file->tr;
|
|
char *param, *filter;
|
|
bool enable, remove;
|
|
const char *system;
|
|
const char *event;
|
|
bool hist = false;
|
|
int ret;
|
|
|
|
remove = event_trigger_check_remove(glob);
|
|
|
|
if (event_trigger_empty_param(param_and_filter))
|
|
return -EINVAL;
|
|
|
|
ret = event_trigger_separate_filter(param_and_filter, ¶m, &filter, true);
|
|
if (ret)
|
|
return ret;
|
|
|
|
system = strsep(¶m, ":");
|
|
if (!param)
|
|
return -EINVAL;
|
|
|
|
event = strsep(¶m, ":");
|
|
|
|
ret = -EINVAL;
|
|
event_enable_file = find_event_file(tr, system, event);
|
|
if (!event_enable_file)
|
|
goto out;
|
|
|
|
#ifdef CONFIG_HIST_TRIGGERS
|
|
hist = ((strcmp(cmd, ENABLE_HIST_STR) == 0) ||
|
|
(strcmp(cmd, DISABLE_HIST_STR) == 0));
|
|
|
|
enable = ((strcmp(cmd, ENABLE_EVENT_STR) == 0) ||
|
|
(strcmp(cmd, ENABLE_HIST_STR) == 0));
|
|
#else
|
|
enable = strcmp(cmd, ENABLE_EVENT_STR) == 0;
|
|
#endif
|
|
ret = -ENOMEM;
|
|
|
|
enable_data = kzalloc(sizeof(*enable_data), GFP_KERNEL);
|
|
if (!enable_data)
|
|
goto out;
|
|
|
|
enable_data->hist = hist;
|
|
enable_data->enable = enable;
|
|
enable_data->file = event_enable_file;
|
|
|
|
trigger_data = event_trigger_alloc(cmd_ops, cmd, param, enable_data);
|
|
if (!trigger_data) {
|
|
kfree(enable_data);
|
|
goto out;
|
|
}
|
|
|
|
if (remove) {
|
|
event_trigger_unregister(cmd_ops, file, glob+1, trigger_data);
|
|
kfree(trigger_data);
|
|
kfree(enable_data);
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
/* Up the trigger_data count to make sure nothing frees it on failure */
|
|
event_trigger_init(trigger_data);
|
|
|
|
ret = event_trigger_parse_num(param, trigger_data);
|
|
if (ret)
|
|
goto out_free;
|
|
|
|
ret = event_trigger_set_filter(cmd_ops, file, filter, trigger_data);
|
|
if (ret < 0)
|
|
goto out_free;
|
|
|
|
/* Don't let event modules unload while probe registered */
|
|
ret = trace_event_try_get_ref(event_enable_file->event_call);
|
|
if (!ret) {
|
|
ret = -EBUSY;
|
|
goto out_free;
|
|
}
|
|
|
|
ret = trace_event_enable_disable(event_enable_file, 1, 1);
|
|
if (ret < 0)
|
|
goto out_put;
|
|
|
|
ret = event_trigger_register(cmd_ops, file, glob, trigger_data);
|
|
if (ret)
|
|
goto out_disable;
|
|
|
|
event_trigger_free(trigger_data);
|
|
out:
|
|
return ret;
|
|
out_disable:
|
|
trace_event_enable_disable(event_enable_file, 0, 1);
|
|
out_put:
|
|
trace_event_put_ref(event_enable_file->event_call);
|
|
out_free:
|
|
event_trigger_reset_filter(cmd_ops, trigger_data);
|
|
event_trigger_free(trigger_data);
|
|
kfree(enable_data);
|
|
|
|
goto out;
|
|
}
|
|
|
|
int event_enable_register_trigger(char *glob,
|
|
struct event_trigger_data *data,
|
|
struct trace_event_file *file)
|
|
{
|
|
struct enable_trigger_data *enable_data = data->private_data;
|
|
struct enable_trigger_data *test_enable_data;
|
|
struct event_trigger_data *test;
|
|
int ret = 0;
|
|
|
|
lockdep_assert_held(&event_mutex);
|
|
|
|
list_for_each_entry(test, &file->triggers, list) {
|
|
test_enable_data = test->private_data;
|
|
if (test_enable_data &&
|
|
(test->cmd_ops->trigger_type ==
|
|
data->cmd_ops->trigger_type) &&
|
|
(test_enable_data->file == enable_data->file)) {
|
|
ret = -EEXIST;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (data->ops->init) {
|
|
ret = data->ops->init(data);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
|
|
list_add_rcu(&data->list, &file->triggers);
|
|
|
|
update_cond_flag(file);
|
|
ret = trace_event_trigger_enable_disable(file, 1);
|
|
if (ret < 0) {
|
|
list_del_rcu(&data->list);
|
|
update_cond_flag(file);
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
void event_enable_unregister_trigger(char *glob,
|
|
struct event_trigger_data *test,
|
|
struct trace_event_file *file)
|
|
{
|
|
struct enable_trigger_data *test_enable_data = test->private_data;
|
|
struct event_trigger_data *data = NULL, *iter;
|
|
struct enable_trigger_data *enable_data;
|
|
|
|
lockdep_assert_held(&event_mutex);
|
|
|
|
list_for_each_entry(iter, &file->triggers, list) {
|
|
enable_data = iter->private_data;
|
|
if (enable_data &&
|
|
(iter->cmd_ops->trigger_type ==
|
|
test->cmd_ops->trigger_type) &&
|
|
(enable_data->file == test_enable_data->file)) {
|
|
data = iter;
|
|
list_del_rcu(&data->list);
|
|
trace_event_trigger_enable_disable(file, 0);
|
|
update_cond_flag(file);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (data && data->ops->free)
|
|
data->ops->free(data);
|
|
}
|
|
|
|
static struct event_trigger_ops *
|
|
event_enable_get_trigger_ops(char *cmd, char *param)
|
|
{
|
|
struct event_trigger_ops *ops;
|
|
bool enable;
|
|
|
|
#ifdef CONFIG_HIST_TRIGGERS
|
|
enable = ((strcmp(cmd, ENABLE_EVENT_STR) == 0) ||
|
|
(strcmp(cmd, ENABLE_HIST_STR) == 0));
|
|
#else
|
|
enable = strcmp(cmd, ENABLE_EVENT_STR) == 0;
|
|
#endif
|
|
if (enable)
|
|
ops = param ? &event_enable_count_trigger_ops :
|
|
&event_enable_trigger_ops;
|
|
else
|
|
ops = param ? &event_disable_count_trigger_ops :
|
|
&event_disable_trigger_ops;
|
|
|
|
return ops;
|
|
}
|
|
|
|
static struct event_command trigger_enable_cmd = {
|
|
.name = ENABLE_EVENT_STR,
|
|
.trigger_type = ETT_EVENT_ENABLE,
|
|
.parse = event_enable_trigger_parse,
|
|
.reg = event_enable_register_trigger,
|
|
.unreg = event_enable_unregister_trigger,
|
|
.get_trigger_ops = event_enable_get_trigger_ops,
|
|
.set_filter = set_trigger_filter,
|
|
};
|
|
|
|
static struct event_command trigger_disable_cmd = {
|
|
.name = DISABLE_EVENT_STR,
|
|
.trigger_type = ETT_EVENT_ENABLE,
|
|
.parse = event_enable_trigger_parse,
|
|
.reg = event_enable_register_trigger,
|
|
.unreg = event_enable_unregister_trigger,
|
|
.get_trigger_ops = event_enable_get_trigger_ops,
|
|
.set_filter = set_trigger_filter,
|
|
};
|
|
|
|
static __init void unregister_trigger_enable_disable_cmds(void)
|
|
{
|
|
unregister_event_command(&trigger_enable_cmd);
|
|
unregister_event_command(&trigger_disable_cmd);
|
|
}
|
|
|
|
static __init int register_trigger_enable_disable_cmds(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = register_event_command(&trigger_enable_cmd);
|
|
if (WARN_ON(ret < 0))
|
|
return ret;
|
|
ret = register_event_command(&trigger_disable_cmd);
|
|
if (WARN_ON(ret < 0))
|
|
unregister_trigger_enable_disable_cmds();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static __init int register_trigger_traceon_traceoff_cmds(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = register_event_command(&trigger_traceon_cmd);
|
|
if (WARN_ON(ret < 0))
|
|
return ret;
|
|
ret = register_event_command(&trigger_traceoff_cmd);
|
|
if (WARN_ON(ret < 0))
|
|
unregister_trigger_traceon_traceoff_cmds();
|
|
|
|
return ret;
|
|
}
|
|
|
|
__init int register_trigger_cmds(void)
|
|
{
|
|
register_trigger_traceon_traceoff_cmds();
|
|
register_trigger_snapshot_cmd();
|
|
register_trigger_stacktrace_cmd();
|
|
register_trigger_enable_disable_cmds();
|
|
register_trigger_hist_enable_disable_cmds();
|
|
register_trigger_hist_cmd();
|
|
|
|
return 0;
|
|
}
|