mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-05 02:19:51 +00:00
![Tzvetomir Stoyanov (VMware)](/assets/img/avatar_default.png)
There are internal library functions, which are not declared as a static. They are used inside the library from different files. Hide them from the library users, as they are not part of the API. These functions are made hidden and are renamed without the prefix "tep_": tep_free_plugin_paths tep_peek_char tep_buffer_init tep_get_input_buf_ptr tep_get_input_buf tep_read_token tep_free_token tep_free_event tep_free_format_field __tep_parse_format Link: https://lore.kernel.org/linux-trace-devel/e4afdd82deb5e023d53231bb13e08dca78085fb0.camel@decadent.org.uk/ Reported-by: Ben Hutchings <ben@decadent.org.uk> Signed-off-by: Tzvetomir Stoyanov (VMware) <tz.stoyanov@gmail.com> Reviewed-by: Steven Rostedt (VMware) <rostedt@goodmis.org> Cc: linux-trace-devel@vger.kernel.org Link: http://lore.kernel.org/lkml/20200930110733.280534-1-tz.stoyanov@gmail.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2278 lines
48 KiB
C
2278 lines
48 KiB
C
// SPDX-License-Identifier: LGPL-2.1
|
|
/*
|
|
* Copyright (C) 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
|
|
*
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "event-parse.h"
|
|
#include "event-parse-local.h"
|
|
#include "event-utils.h"
|
|
|
|
#define COMM "COMM"
|
|
#define CPU "CPU"
|
|
|
|
static struct tep_format_field comm = {
|
|
.name = "COMM",
|
|
};
|
|
|
|
static struct tep_format_field cpu = {
|
|
.name = "CPU",
|
|
};
|
|
|
|
struct event_list {
|
|
struct event_list *next;
|
|
struct tep_event *event;
|
|
};
|
|
|
|
static void show_error(char *error_buf, const char *fmt, ...)
|
|
{
|
|
unsigned long long index;
|
|
const char *input;
|
|
va_list ap;
|
|
int len;
|
|
int i;
|
|
|
|
input = get_input_buf();
|
|
index = get_input_buf_ptr();
|
|
len = input ? strlen(input) : 0;
|
|
|
|
if (len) {
|
|
strcpy(error_buf, input);
|
|
error_buf[len] = '\n';
|
|
for (i = 1; i < len && i < index; i++)
|
|
error_buf[len+i] = ' ';
|
|
error_buf[len + i] = '^';
|
|
error_buf[len + i + 1] = '\n';
|
|
len += i+2;
|
|
}
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(error_buf + len, TEP_FILTER_ERROR_BUFSZ - len, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static enum tep_event_type filter_read_token(char **tok)
|
|
{
|
|
enum tep_event_type type;
|
|
char *token = NULL;
|
|
|
|
do {
|
|
free_token(token);
|
|
type = read_token(&token);
|
|
} while (type == TEP_EVENT_NEWLINE || type == TEP_EVENT_SPACE);
|
|
|
|
/* If token is = or ! check to see if the next char is ~ */
|
|
if (token &&
|
|
(strcmp(token, "=") == 0 || strcmp(token, "!") == 0) &&
|
|
peek_char() == '~') {
|
|
/* append it */
|
|
*tok = malloc(3);
|
|
if (*tok == NULL) {
|
|
free_token(token);
|
|
return TEP_EVENT_ERROR;
|
|
}
|
|
sprintf(*tok, "%c%c", *token, '~');
|
|
free_token(token);
|
|
/* Now remove the '~' from the buffer */
|
|
read_token(&token);
|
|
free_token(token);
|
|
} else
|
|
*tok = token;
|
|
|
|
return type;
|
|
}
|
|
|
|
static int filter_cmp(const void *a, const void *b)
|
|
{
|
|
const struct tep_filter_type *ea = a;
|
|
const struct tep_filter_type *eb = b;
|
|
|
|
if (ea->event_id < eb->event_id)
|
|
return -1;
|
|
|
|
if (ea->event_id > eb->event_id)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct tep_filter_type *
|
|
find_filter_type(struct tep_event_filter *filter, int id)
|
|
{
|
|
struct tep_filter_type *filter_type;
|
|
struct tep_filter_type key;
|
|
|
|
key.event_id = id;
|
|
|
|
filter_type = bsearch(&key, filter->event_filters,
|
|
filter->filters,
|
|
sizeof(*filter->event_filters),
|
|
filter_cmp);
|
|
|
|
return filter_type;
|
|
}
|
|
|
|
static struct tep_filter_type *
|
|
add_filter_type(struct tep_event_filter *filter, int id)
|
|
{
|
|
struct tep_filter_type *filter_type;
|
|
int i;
|
|
|
|
filter_type = find_filter_type(filter, id);
|
|
if (filter_type)
|
|
return filter_type;
|
|
|
|
filter_type = realloc(filter->event_filters,
|
|
sizeof(*filter->event_filters) *
|
|
(filter->filters + 1));
|
|
if (!filter_type)
|
|
return NULL;
|
|
|
|
filter->event_filters = filter_type;
|
|
|
|
for (i = 0; i < filter->filters; i++) {
|
|
if (filter->event_filters[i].event_id > id)
|
|
break;
|
|
}
|
|
|
|
if (i < filter->filters)
|
|
memmove(&filter->event_filters[i+1],
|
|
&filter->event_filters[i],
|
|
sizeof(*filter->event_filters) *
|
|
(filter->filters - i));
|
|
|
|
filter_type = &filter->event_filters[i];
|
|
filter_type->event_id = id;
|
|
filter_type->event = tep_find_event(filter->tep, id);
|
|
filter_type->filter = NULL;
|
|
|
|
filter->filters++;
|
|
|
|
return filter_type;
|
|
}
|
|
|
|
/**
|
|
* tep_filter_alloc - create a new event filter
|
|
* @tep: The tep that this filter is associated with
|
|
*/
|
|
struct tep_event_filter *tep_filter_alloc(struct tep_handle *tep)
|
|
{
|
|
struct tep_event_filter *filter;
|
|
|
|
filter = malloc(sizeof(*filter));
|
|
if (filter == NULL)
|
|
return NULL;
|
|
|
|
memset(filter, 0, sizeof(*filter));
|
|
filter->tep = tep;
|
|
tep_ref(tep);
|
|
|
|
return filter;
|
|
}
|
|
|
|
static struct tep_filter_arg *allocate_arg(void)
|
|
{
|
|
return calloc(1, sizeof(struct tep_filter_arg));
|
|
}
|
|
|
|
static void free_arg(struct tep_filter_arg *arg)
|
|
{
|
|
if (!arg)
|
|
return;
|
|
|
|
switch (arg->type) {
|
|
case TEP_FILTER_ARG_NONE:
|
|
case TEP_FILTER_ARG_BOOLEAN:
|
|
break;
|
|
|
|
case TEP_FILTER_ARG_NUM:
|
|
free_arg(arg->num.left);
|
|
free_arg(arg->num.right);
|
|
break;
|
|
|
|
case TEP_FILTER_ARG_EXP:
|
|
free_arg(arg->exp.left);
|
|
free_arg(arg->exp.right);
|
|
break;
|
|
|
|
case TEP_FILTER_ARG_STR:
|
|
free(arg->str.val);
|
|
regfree(&arg->str.reg);
|
|
free(arg->str.buffer);
|
|
break;
|
|
|
|
case TEP_FILTER_ARG_VALUE:
|
|
if (arg->value.type == TEP_FILTER_STRING ||
|
|
arg->value.type == TEP_FILTER_CHAR)
|
|
free(arg->value.str);
|
|
break;
|
|
|
|
case TEP_FILTER_ARG_OP:
|
|
free_arg(arg->op.left);
|
|
free_arg(arg->op.right);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
free(arg);
|
|
}
|
|
|
|
static int add_event(struct event_list **events,
|
|
struct tep_event *event)
|
|
{
|
|
struct event_list *list;
|
|
|
|
list = malloc(sizeof(*list));
|
|
if (list == NULL)
|
|
return -1;
|
|
|
|
list->next = *events;
|
|
*events = list;
|
|
list->event = event;
|
|
return 0;
|
|
}
|
|
|
|
static int event_match(struct tep_event *event,
|
|
regex_t *sreg, regex_t *ereg)
|
|
{
|
|
if (sreg) {
|
|
return !regexec(sreg, event->system, 0, NULL, 0) &&
|
|
!regexec(ereg, event->name, 0, NULL, 0);
|
|
}
|
|
|
|
return !regexec(ereg, event->system, 0, NULL, 0) ||
|
|
!regexec(ereg, event->name, 0, NULL, 0);
|
|
}
|
|
|
|
static enum tep_errno
|
|
find_event(struct tep_handle *tep, struct event_list **events,
|
|
char *sys_name, char *event_name)
|
|
{
|
|
struct tep_event *event;
|
|
regex_t ereg;
|
|
regex_t sreg;
|
|
int match = 0;
|
|
int fail = 0;
|
|
char *reg;
|
|
int ret;
|
|
int i;
|
|
|
|
if (!event_name) {
|
|
/* if no name is given, then swap sys and name */
|
|
event_name = sys_name;
|
|
sys_name = NULL;
|
|
}
|
|
|
|
ret = asprintf(®, "^%s$", event_name);
|
|
if (ret < 0)
|
|
return TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
|
|
ret = regcomp(&ereg, reg, REG_ICASE|REG_NOSUB);
|
|
free(reg);
|
|
|
|
if (ret)
|
|
return TEP_ERRNO__INVALID_EVENT_NAME;
|
|
|
|
if (sys_name) {
|
|
ret = asprintf(®, "^%s$", sys_name);
|
|
if (ret < 0) {
|
|
regfree(&ereg);
|
|
return TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
}
|
|
|
|
ret = regcomp(&sreg, reg, REG_ICASE|REG_NOSUB);
|
|
free(reg);
|
|
if (ret) {
|
|
regfree(&ereg);
|
|
return TEP_ERRNO__INVALID_EVENT_NAME;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < tep->nr_events; i++) {
|
|
event = tep->events[i];
|
|
if (event_match(event, sys_name ? &sreg : NULL, &ereg)) {
|
|
match = 1;
|
|
if (add_event(events, event) < 0) {
|
|
fail = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
regfree(&ereg);
|
|
if (sys_name)
|
|
regfree(&sreg);
|
|
|
|
if (!match)
|
|
return TEP_ERRNO__EVENT_NOT_FOUND;
|
|
if (fail)
|
|
return TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void free_events(struct event_list *events)
|
|
{
|
|
struct event_list *event;
|
|
|
|
while (events) {
|
|
event = events;
|
|
events = events->next;
|
|
free(event);
|
|
}
|
|
}
|
|
|
|
static enum tep_errno
|
|
create_arg_item(struct tep_event *event, const char *token,
|
|
enum tep_event_type type, struct tep_filter_arg **parg, char *error_str)
|
|
{
|
|
struct tep_format_field *field;
|
|
struct tep_filter_arg *arg;
|
|
|
|
arg = allocate_arg();
|
|
if (arg == NULL) {
|
|
show_error(error_str, "failed to allocate filter arg");
|
|
return TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
}
|
|
|
|
switch (type) {
|
|
|
|
case TEP_EVENT_SQUOTE:
|
|
case TEP_EVENT_DQUOTE:
|
|
arg->type = TEP_FILTER_ARG_VALUE;
|
|
arg->value.type =
|
|
type == TEP_EVENT_DQUOTE ? TEP_FILTER_STRING : TEP_FILTER_CHAR;
|
|
arg->value.str = strdup(token);
|
|
if (!arg->value.str) {
|
|
free_arg(arg);
|
|
show_error(error_str, "failed to allocate string filter arg");
|
|
return TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
}
|
|
break;
|
|
case TEP_EVENT_ITEM:
|
|
/* if it is a number, then convert it */
|
|
if (isdigit(token[0])) {
|
|
arg->type = TEP_FILTER_ARG_VALUE;
|
|
arg->value.type = TEP_FILTER_NUMBER;
|
|
arg->value.val = strtoull(token, NULL, 0);
|
|
break;
|
|
}
|
|
/* Consider this a field */
|
|
field = tep_find_any_field(event, token);
|
|
if (!field) {
|
|
/* If token is 'COMM' or 'CPU' then it is special */
|
|
if (strcmp(token, COMM) == 0) {
|
|
field = &comm;
|
|
} else if (strcmp(token, CPU) == 0) {
|
|
field = &cpu;
|
|
} else {
|
|
/* not a field, Make it false */
|
|
arg->type = TEP_FILTER_ARG_BOOLEAN;
|
|
arg->boolean.value = TEP_FILTER_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
arg->type = TEP_FILTER_ARG_FIELD;
|
|
arg->field.field = field;
|
|
break;
|
|
default:
|
|
free_arg(arg);
|
|
show_error(error_str, "expected a value but found %s", token);
|
|
return TEP_ERRNO__UNEXPECTED_TYPE;
|
|
}
|
|
*parg = arg;
|
|
return 0;
|
|
}
|
|
|
|
static struct tep_filter_arg *
|
|
create_arg_op(enum tep_filter_op_type btype)
|
|
{
|
|
struct tep_filter_arg *arg;
|
|
|
|
arg = allocate_arg();
|
|
if (!arg)
|
|
return NULL;
|
|
|
|
arg->type = TEP_FILTER_ARG_OP;
|
|
arg->op.type = btype;
|
|
|
|
return arg;
|
|
}
|
|
|
|
static struct tep_filter_arg *
|
|
create_arg_exp(enum tep_filter_exp_type etype)
|
|
{
|
|
struct tep_filter_arg *arg;
|
|
|
|
arg = allocate_arg();
|
|
if (!arg)
|
|
return NULL;
|
|
|
|
arg->type = TEP_FILTER_ARG_EXP;
|
|
arg->exp.type = etype;
|
|
|
|
return arg;
|
|
}
|
|
|
|
static struct tep_filter_arg *
|
|
create_arg_cmp(enum tep_filter_cmp_type ctype)
|
|
{
|
|
struct tep_filter_arg *arg;
|
|
|
|
arg = allocate_arg();
|
|
if (!arg)
|
|
return NULL;
|
|
|
|
/* Use NUM and change if necessary */
|
|
arg->type = TEP_FILTER_ARG_NUM;
|
|
arg->num.type = ctype;
|
|
|
|
return arg;
|
|
}
|
|
|
|
static enum tep_errno
|
|
add_right(struct tep_filter_arg *op, struct tep_filter_arg *arg, char *error_str)
|
|
{
|
|
struct tep_filter_arg *left;
|
|
char *str;
|
|
int op_type;
|
|
int ret;
|
|
|
|
switch (op->type) {
|
|
case TEP_FILTER_ARG_EXP:
|
|
if (op->exp.right)
|
|
goto out_fail;
|
|
op->exp.right = arg;
|
|
break;
|
|
|
|
case TEP_FILTER_ARG_OP:
|
|
if (op->op.right)
|
|
goto out_fail;
|
|
op->op.right = arg;
|
|
break;
|
|
|
|
case TEP_FILTER_ARG_NUM:
|
|
if (op->op.right)
|
|
goto out_fail;
|
|
/*
|
|
* The arg must be num, str, or field
|
|
*/
|
|
switch (arg->type) {
|
|
case TEP_FILTER_ARG_VALUE:
|
|
case TEP_FILTER_ARG_FIELD:
|
|
break;
|
|
default:
|
|
show_error(error_str, "Illegal rvalue");
|
|
return TEP_ERRNO__ILLEGAL_RVALUE;
|
|
}
|
|
|
|
/*
|
|
* Depending on the type, we may need to
|
|
* convert this to a string or regex.
|
|
*/
|
|
switch (arg->value.type) {
|
|
case TEP_FILTER_CHAR:
|
|
/*
|
|
* A char should be converted to number if
|
|
* the string is 1 byte, and the compare
|
|
* is not a REGEX.
|
|
*/
|
|
if (strlen(arg->value.str) == 1 &&
|
|
op->num.type != TEP_FILTER_CMP_REGEX &&
|
|
op->num.type != TEP_FILTER_CMP_NOT_REGEX) {
|
|
arg->value.type = TEP_FILTER_NUMBER;
|
|
goto do_int;
|
|
}
|
|
/* fall through */
|
|
case TEP_FILTER_STRING:
|
|
|
|
/* convert op to a string arg */
|
|
op_type = op->num.type;
|
|
left = op->num.left;
|
|
str = arg->value.str;
|
|
|
|
/* reset the op for the new field */
|
|
memset(op, 0, sizeof(*op));
|
|
|
|
/*
|
|
* If left arg was a field not found then
|
|
* NULL the entire op.
|
|
*/
|
|
if (left->type == TEP_FILTER_ARG_BOOLEAN) {
|
|
free_arg(left);
|
|
free_arg(arg);
|
|
op->type = TEP_FILTER_ARG_BOOLEAN;
|
|
op->boolean.value = TEP_FILTER_FALSE;
|
|
break;
|
|
}
|
|
|
|
/* Left arg must be a field */
|
|
if (left->type != TEP_FILTER_ARG_FIELD) {
|
|
show_error(error_str,
|
|
"Illegal lvalue for string comparison");
|
|
return TEP_ERRNO__ILLEGAL_LVALUE;
|
|
}
|
|
|
|
/* Make sure this is a valid string compare */
|
|
switch (op_type) {
|
|
case TEP_FILTER_CMP_EQ:
|
|
op_type = TEP_FILTER_CMP_MATCH;
|
|
break;
|
|
case TEP_FILTER_CMP_NE:
|
|
op_type = TEP_FILTER_CMP_NOT_MATCH;
|
|
break;
|
|
|
|
case TEP_FILTER_CMP_REGEX:
|
|
case TEP_FILTER_CMP_NOT_REGEX:
|
|
ret = regcomp(&op->str.reg, str, REG_ICASE|REG_NOSUB);
|
|
if (ret) {
|
|
show_error(error_str,
|
|
"RegEx '%s' did not compute",
|
|
str);
|
|
return TEP_ERRNO__INVALID_REGEX;
|
|
}
|
|
break;
|
|
default:
|
|
show_error(error_str,
|
|
"Illegal comparison for string");
|
|
return TEP_ERRNO__ILLEGAL_STRING_CMP;
|
|
}
|
|
|
|
op->type = TEP_FILTER_ARG_STR;
|
|
op->str.type = op_type;
|
|
op->str.field = left->field.field;
|
|
op->str.val = strdup(str);
|
|
if (!op->str.val) {
|
|
show_error(error_str, "Failed to allocate string filter");
|
|
return TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
}
|
|
/*
|
|
* Need a buffer to copy data for tests
|
|
*/
|
|
op->str.buffer = malloc(op->str.field->size + 1);
|
|
if (!op->str.buffer) {
|
|
show_error(error_str, "Failed to allocate string filter");
|
|
return TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
}
|
|
/* Null terminate this buffer */
|
|
op->str.buffer[op->str.field->size] = 0;
|
|
|
|
/* We no longer have left or right args */
|
|
free_arg(arg);
|
|
free_arg(left);
|
|
|
|
break;
|
|
|
|
case TEP_FILTER_NUMBER:
|
|
|
|
do_int:
|
|
switch (op->num.type) {
|
|
case TEP_FILTER_CMP_REGEX:
|
|
case TEP_FILTER_CMP_NOT_REGEX:
|
|
show_error(error_str,
|
|
"Op not allowed with integers");
|
|
return TEP_ERRNO__ILLEGAL_INTEGER_CMP;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* numeric compare */
|
|
op->num.right = arg;
|
|
break;
|
|
default:
|
|
goto out_fail;
|
|
}
|
|
break;
|
|
default:
|
|
goto out_fail;
|
|
}
|
|
|
|
return 0;
|
|
|
|
out_fail:
|
|
show_error(error_str, "Syntax error");
|
|
return TEP_ERRNO__SYNTAX_ERROR;
|
|
}
|
|
|
|
static struct tep_filter_arg *
|
|
rotate_op_right(struct tep_filter_arg *a, struct tep_filter_arg *b)
|
|
{
|
|
struct tep_filter_arg *arg;
|
|
|
|
arg = a->op.right;
|
|
a->op.right = b;
|
|
return arg;
|
|
}
|
|
|
|
static enum tep_errno add_left(struct tep_filter_arg *op, struct tep_filter_arg *arg)
|
|
{
|
|
switch (op->type) {
|
|
case TEP_FILTER_ARG_EXP:
|
|
if (arg->type == TEP_FILTER_ARG_OP)
|
|
arg = rotate_op_right(arg, op);
|
|
op->exp.left = arg;
|
|
break;
|
|
|
|
case TEP_FILTER_ARG_OP:
|
|
op->op.left = arg;
|
|
break;
|
|
case TEP_FILTER_ARG_NUM:
|
|
if (arg->type == TEP_FILTER_ARG_OP)
|
|
arg = rotate_op_right(arg, op);
|
|
|
|
/* left arg of compares must be a field */
|
|
if (arg->type != TEP_FILTER_ARG_FIELD &&
|
|
arg->type != TEP_FILTER_ARG_BOOLEAN)
|
|
return TEP_ERRNO__INVALID_ARG_TYPE;
|
|
op->num.left = arg;
|
|
break;
|
|
default:
|
|
return TEP_ERRNO__INVALID_ARG_TYPE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
enum op_type {
|
|
OP_NONE,
|
|
OP_BOOL,
|
|
OP_NOT,
|
|
OP_EXP,
|
|
OP_CMP,
|
|
};
|
|
|
|
static enum op_type process_op(const char *token,
|
|
enum tep_filter_op_type *btype,
|
|
enum tep_filter_cmp_type *ctype,
|
|
enum tep_filter_exp_type *etype)
|
|
{
|
|
*btype = TEP_FILTER_OP_NOT;
|
|
*etype = TEP_FILTER_EXP_NONE;
|
|
*ctype = TEP_FILTER_CMP_NONE;
|
|
|
|
if (strcmp(token, "&&") == 0)
|
|
*btype = TEP_FILTER_OP_AND;
|
|
else if (strcmp(token, "||") == 0)
|
|
*btype = TEP_FILTER_OP_OR;
|
|
else if (strcmp(token, "!") == 0)
|
|
return OP_NOT;
|
|
|
|
if (*btype != TEP_FILTER_OP_NOT)
|
|
return OP_BOOL;
|
|
|
|
/* Check for value expressions */
|
|
if (strcmp(token, "+") == 0) {
|
|
*etype = TEP_FILTER_EXP_ADD;
|
|
} else if (strcmp(token, "-") == 0) {
|
|
*etype = TEP_FILTER_EXP_SUB;
|
|
} else if (strcmp(token, "*") == 0) {
|
|
*etype = TEP_FILTER_EXP_MUL;
|
|
} else if (strcmp(token, "/") == 0) {
|
|
*etype = TEP_FILTER_EXP_DIV;
|
|
} else if (strcmp(token, "%") == 0) {
|
|
*etype = TEP_FILTER_EXP_MOD;
|
|
} else if (strcmp(token, ">>") == 0) {
|
|
*etype = TEP_FILTER_EXP_RSHIFT;
|
|
} else if (strcmp(token, "<<") == 0) {
|
|
*etype = TEP_FILTER_EXP_LSHIFT;
|
|
} else if (strcmp(token, "&") == 0) {
|
|
*etype = TEP_FILTER_EXP_AND;
|
|
} else if (strcmp(token, "|") == 0) {
|
|
*etype = TEP_FILTER_EXP_OR;
|
|
} else if (strcmp(token, "^") == 0) {
|
|
*etype = TEP_FILTER_EXP_XOR;
|
|
} else if (strcmp(token, "~") == 0)
|
|
*etype = TEP_FILTER_EXP_NOT;
|
|
|
|
if (*etype != TEP_FILTER_EXP_NONE)
|
|
return OP_EXP;
|
|
|
|
/* Check for compares */
|
|
if (strcmp(token, "==") == 0)
|
|
*ctype = TEP_FILTER_CMP_EQ;
|
|
else if (strcmp(token, "!=") == 0)
|
|
*ctype = TEP_FILTER_CMP_NE;
|
|
else if (strcmp(token, "<") == 0)
|
|
*ctype = TEP_FILTER_CMP_LT;
|
|
else if (strcmp(token, ">") == 0)
|
|
*ctype = TEP_FILTER_CMP_GT;
|
|
else if (strcmp(token, "<=") == 0)
|
|
*ctype = TEP_FILTER_CMP_LE;
|
|
else if (strcmp(token, ">=") == 0)
|
|
*ctype = TEP_FILTER_CMP_GE;
|
|
else if (strcmp(token, "=~") == 0)
|
|
*ctype = TEP_FILTER_CMP_REGEX;
|
|
else if (strcmp(token, "!~") == 0)
|
|
*ctype = TEP_FILTER_CMP_NOT_REGEX;
|
|
else
|
|
return OP_NONE;
|
|
|
|
return OP_CMP;
|
|
}
|
|
|
|
static int check_op_done(struct tep_filter_arg *arg)
|
|
{
|
|
switch (arg->type) {
|
|
case TEP_FILTER_ARG_EXP:
|
|
return arg->exp.right != NULL;
|
|
|
|
case TEP_FILTER_ARG_OP:
|
|
return arg->op.right != NULL;
|
|
|
|
case TEP_FILTER_ARG_NUM:
|
|
return arg->num.right != NULL;
|
|
|
|
case TEP_FILTER_ARG_STR:
|
|
/* A string conversion is always done */
|
|
return 1;
|
|
|
|
case TEP_FILTER_ARG_BOOLEAN:
|
|
/* field not found, is ok */
|
|
return 1;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
enum filter_vals {
|
|
FILTER_VAL_NORM,
|
|
FILTER_VAL_FALSE,
|
|
FILTER_VAL_TRUE,
|
|
};
|
|
|
|
static enum tep_errno
|
|
reparent_op_arg(struct tep_filter_arg *parent, struct tep_filter_arg *old_child,
|
|
struct tep_filter_arg *arg, char *error_str)
|
|
{
|
|
struct tep_filter_arg *other_child;
|
|
struct tep_filter_arg **ptr;
|
|
|
|
if (parent->type != TEP_FILTER_ARG_OP &&
|
|
arg->type != TEP_FILTER_ARG_OP) {
|
|
show_error(error_str, "can not reparent other than OP");
|
|
return TEP_ERRNO__REPARENT_NOT_OP;
|
|
}
|
|
|
|
/* Get the sibling */
|
|
if (old_child->op.right == arg) {
|
|
ptr = &old_child->op.right;
|
|
other_child = old_child->op.left;
|
|
} else if (old_child->op.left == arg) {
|
|
ptr = &old_child->op.left;
|
|
other_child = old_child->op.right;
|
|
} else {
|
|
show_error(error_str, "Error in reparent op, find other child");
|
|
return TEP_ERRNO__REPARENT_FAILED;
|
|
}
|
|
|
|
/* Detach arg from old_child */
|
|
*ptr = NULL;
|
|
|
|
/* Check for root */
|
|
if (parent == old_child) {
|
|
free_arg(other_child);
|
|
*parent = *arg;
|
|
/* Free arg without recussion */
|
|
free(arg);
|
|
return 0;
|
|
}
|
|
|
|
if (parent->op.right == old_child)
|
|
ptr = &parent->op.right;
|
|
else if (parent->op.left == old_child)
|
|
ptr = &parent->op.left;
|
|
else {
|
|
show_error(error_str, "Error in reparent op");
|
|
return TEP_ERRNO__REPARENT_FAILED;
|
|
}
|
|
|
|
*ptr = arg;
|
|
|
|
free_arg(old_child);
|
|
return 0;
|
|
}
|
|
|
|
/* Returns either filter_vals (success) or tep_errno (failfure) */
|
|
static int test_arg(struct tep_filter_arg *parent, struct tep_filter_arg *arg,
|
|
char *error_str)
|
|
{
|
|
int lval, rval;
|
|
|
|
switch (arg->type) {
|
|
|
|
/* bad case */
|
|
case TEP_FILTER_ARG_BOOLEAN:
|
|
return FILTER_VAL_FALSE + arg->boolean.value;
|
|
|
|
/* good cases: */
|
|
case TEP_FILTER_ARG_STR:
|
|
case TEP_FILTER_ARG_VALUE:
|
|
case TEP_FILTER_ARG_FIELD:
|
|
return FILTER_VAL_NORM;
|
|
|
|
case TEP_FILTER_ARG_EXP:
|
|
lval = test_arg(arg, arg->exp.left, error_str);
|
|
if (lval != FILTER_VAL_NORM)
|
|
return lval;
|
|
rval = test_arg(arg, arg->exp.right, error_str);
|
|
if (rval != FILTER_VAL_NORM)
|
|
return rval;
|
|
return FILTER_VAL_NORM;
|
|
|
|
case TEP_FILTER_ARG_NUM:
|
|
lval = test_arg(arg, arg->num.left, error_str);
|
|
if (lval != FILTER_VAL_NORM)
|
|
return lval;
|
|
rval = test_arg(arg, arg->num.right, error_str);
|
|
if (rval != FILTER_VAL_NORM)
|
|
return rval;
|
|
return FILTER_VAL_NORM;
|
|
|
|
case TEP_FILTER_ARG_OP:
|
|
if (arg->op.type != TEP_FILTER_OP_NOT) {
|
|
lval = test_arg(arg, arg->op.left, error_str);
|
|
switch (lval) {
|
|
case FILTER_VAL_NORM:
|
|
break;
|
|
case FILTER_VAL_TRUE:
|
|
if (arg->op.type == TEP_FILTER_OP_OR)
|
|
return FILTER_VAL_TRUE;
|
|
rval = test_arg(arg, arg->op.right, error_str);
|
|
if (rval != FILTER_VAL_NORM)
|
|
return rval;
|
|
|
|
return reparent_op_arg(parent, arg, arg->op.right,
|
|
error_str);
|
|
|
|
case FILTER_VAL_FALSE:
|
|
if (arg->op.type == TEP_FILTER_OP_AND)
|
|
return FILTER_VAL_FALSE;
|
|
rval = test_arg(arg, arg->op.right, error_str);
|
|
if (rval != FILTER_VAL_NORM)
|
|
return rval;
|
|
|
|
return reparent_op_arg(parent, arg, arg->op.right,
|
|
error_str);
|
|
|
|
default:
|
|
return lval;
|
|
}
|
|
}
|
|
|
|
rval = test_arg(arg, arg->op.right, error_str);
|
|
switch (rval) {
|
|
case FILTER_VAL_NORM:
|
|
default:
|
|
break;
|
|
|
|
case FILTER_VAL_TRUE:
|
|
if (arg->op.type == TEP_FILTER_OP_OR)
|
|
return FILTER_VAL_TRUE;
|
|
if (arg->op.type == TEP_FILTER_OP_NOT)
|
|
return FILTER_VAL_FALSE;
|
|
|
|
return reparent_op_arg(parent, arg, arg->op.left,
|
|
error_str);
|
|
|
|
case FILTER_VAL_FALSE:
|
|
if (arg->op.type == TEP_FILTER_OP_AND)
|
|
return FILTER_VAL_FALSE;
|
|
if (arg->op.type == TEP_FILTER_OP_NOT)
|
|
return FILTER_VAL_TRUE;
|
|
|
|
return reparent_op_arg(parent, arg, arg->op.left,
|
|
error_str);
|
|
}
|
|
|
|
return rval;
|
|
default:
|
|
show_error(error_str, "bad arg in filter tree");
|
|
return TEP_ERRNO__BAD_FILTER_ARG;
|
|
}
|
|
return FILTER_VAL_NORM;
|
|
}
|
|
|
|
/* Remove any unknown event fields */
|
|
static int collapse_tree(struct tep_filter_arg *arg,
|
|
struct tep_filter_arg **arg_collapsed, char *error_str)
|
|
{
|
|
int ret;
|
|
|
|
ret = test_arg(arg, arg, error_str);
|
|
switch (ret) {
|
|
case FILTER_VAL_NORM:
|
|
break;
|
|
|
|
case FILTER_VAL_TRUE:
|
|
case FILTER_VAL_FALSE:
|
|
free_arg(arg);
|
|
arg = allocate_arg();
|
|
if (arg) {
|
|
arg->type = TEP_FILTER_ARG_BOOLEAN;
|
|
arg->boolean.value = ret == FILTER_VAL_TRUE;
|
|
} else {
|
|
show_error(error_str, "Failed to allocate filter arg");
|
|
ret = TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* test_arg() already set the error_str */
|
|
free_arg(arg);
|
|
arg = NULL;
|
|
break;
|
|
}
|
|
|
|
*arg_collapsed = arg;
|
|
return ret;
|
|
}
|
|
|
|
static enum tep_errno
|
|
process_filter(struct tep_event *event, struct tep_filter_arg **parg,
|
|
char *error_str, int not)
|
|
{
|
|
enum tep_event_type type;
|
|
char *token = NULL;
|
|
struct tep_filter_arg *current_op = NULL;
|
|
struct tep_filter_arg *current_exp = NULL;
|
|
struct tep_filter_arg *left_item = NULL;
|
|
struct tep_filter_arg *arg = NULL;
|
|
enum op_type op_type;
|
|
enum tep_filter_op_type btype;
|
|
enum tep_filter_exp_type etype;
|
|
enum tep_filter_cmp_type ctype;
|
|
enum tep_errno ret;
|
|
|
|
*parg = NULL;
|
|
|
|
do {
|
|
free(token);
|
|
type = filter_read_token(&token);
|
|
switch (type) {
|
|
case TEP_EVENT_SQUOTE:
|
|
case TEP_EVENT_DQUOTE:
|
|
case TEP_EVENT_ITEM:
|
|
ret = create_arg_item(event, token, type, &arg, error_str);
|
|
if (ret < 0)
|
|
goto fail;
|
|
if (!left_item)
|
|
left_item = arg;
|
|
else if (current_exp) {
|
|
ret = add_right(current_exp, arg, error_str);
|
|
if (ret < 0)
|
|
goto fail;
|
|
left_item = NULL;
|
|
/* Not's only one one expression */
|
|
if (not) {
|
|
arg = NULL;
|
|
if (current_op)
|
|
goto fail_syntax;
|
|
free(token);
|
|
*parg = current_exp;
|
|
return 0;
|
|
}
|
|
} else
|
|
goto fail_syntax;
|
|
arg = NULL;
|
|
break;
|
|
|
|
case TEP_EVENT_DELIM:
|
|
if (*token == ',') {
|
|
show_error(error_str, "Illegal token ','");
|
|
ret = TEP_ERRNO__ILLEGAL_TOKEN;
|
|
goto fail;
|
|
}
|
|
|
|
if (*token == '(') {
|
|
if (left_item) {
|
|
show_error(error_str,
|
|
"Open paren can not come after item");
|
|
ret = TEP_ERRNO__INVALID_PAREN;
|
|
goto fail;
|
|
}
|
|
if (current_exp) {
|
|
show_error(error_str,
|
|
"Open paren can not come after expression");
|
|
ret = TEP_ERRNO__INVALID_PAREN;
|
|
goto fail;
|
|
}
|
|
|
|
ret = process_filter(event, &arg, error_str, 0);
|
|
if (ret != TEP_ERRNO__UNBALANCED_PAREN) {
|
|
if (ret == 0) {
|
|
show_error(error_str,
|
|
"Unbalanced number of '('");
|
|
ret = TEP_ERRNO__UNBALANCED_PAREN;
|
|
}
|
|
goto fail;
|
|
}
|
|
ret = 0;
|
|
|
|
/* A not wants just one expression */
|
|
if (not) {
|
|
if (current_op)
|
|
goto fail_syntax;
|
|
*parg = arg;
|
|
return 0;
|
|
}
|
|
|
|
if (current_op)
|
|
ret = add_right(current_op, arg, error_str);
|
|
else
|
|
current_exp = arg;
|
|
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
} else { /* ')' */
|
|
if (!current_op && !current_exp)
|
|
goto fail_syntax;
|
|
|
|
/* Make sure everything is finished at this level */
|
|
if (current_exp && !check_op_done(current_exp))
|
|
goto fail_syntax;
|
|
if (current_op && !check_op_done(current_op))
|
|
goto fail_syntax;
|
|
|
|
if (current_op)
|
|
*parg = current_op;
|
|
else
|
|
*parg = current_exp;
|
|
free(token);
|
|
return TEP_ERRNO__UNBALANCED_PAREN;
|
|
}
|
|
break;
|
|
|
|
case TEP_EVENT_OP:
|
|
op_type = process_op(token, &btype, &ctype, &etype);
|
|
|
|
/* All expect a left arg except for NOT */
|
|
switch (op_type) {
|
|
case OP_BOOL:
|
|
/* Logic ops need a left expression */
|
|
if (!current_exp && !current_op)
|
|
goto fail_syntax;
|
|
/* fall through */
|
|
case OP_NOT:
|
|
/* logic only processes ops and exp */
|
|
if (left_item)
|
|
goto fail_syntax;
|
|
break;
|
|
case OP_EXP:
|
|
case OP_CMP:
|
|
if (!left_item)
|
|
goto fail_syntax;
|
|
break;
|
|
case OP_NONE:
|
|
show_error(error_str,
|
|
"Unknown op token %s", token);
|
|
ret = TEP_ERRNO__UNKNOWN_TOKEN;
|
|
goto fail;
|
|
}
|
|
|
|
ret = 0;
|
|
switch (op_type) {
|
|
case OP_BOOL:
|
|
arg = create_arg_op(btype);
|
|
if (arg == NULL)
|
|
goto fail_alloc;
|
|
if (current_op)
|
|
ret = add_left(arg, current_op);
|
|
else
|
|
ret = add_left(arg, current_exp);
|
|
current_op = arg;
|
|
current_exp = NULL;
|
|
break;
|
|
|
|
case OP_NOT:
|
|
arg = create_arg_op(btype);
|
|
if (arg == NULL)
|
|
goto fail_alloc;
|
|
if (current_op)
|
|
ret = add_right(current_op, arg, error_str);
|
|
if (ret < 0)
|
|
goto fail;
|
|
current_exp = arg;
|
|
ret = process_filter(event, &arg, error_str, 1);
|
|
if (ret < 0)
|
|
goto fail;
|
|
ret = add_right(current_exp, arg, error_str);
|
|
if (ret < 0)
|
|
goto fail;
|
|
break;
|
|
|
|
case OP_EXP:
|
|
case OP_CMP:
|
|
if (op_type == OP_EXP)
|
|
arg = create_arg_exp(etype);
|
|
else
|
|
arg = create_arg_cmp(ctype);
|
|
if (arg == NULL)
|
|
goto fail_alloc;
|
|
|
|
if (current_op)
|
|
ret = add_right(current_op, arg, error_str);
|
|
if (ret < 0)
|
|
goto fail;
|
|
ret = add_left(arg, left_item);
|
|
if (ret < 0) {
|
|
arg = NULL;
|
|
goto fail_syntax;
|
|
}
|
|
current_exp = arg;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
arg = NULL;
|
|
if (ret < 0)
|
|
goto fail_syntax;
|
|
break;
|
|
case TEP_EVENT_NONE:
|
|
break;
|
|
case TEP_EVENT_ERROR:
|
|
goto fail_alloc;
|
|
default:
|
|
goto fail_syntax;
|
|
}
|
|
} while (type != TEP_EVENT_NONE);
|
|
|
|
if (!current_op && !current_exp)
|
|
goto fail_syntax;
|
|
|
|
if (!current_op)
|
|
current_op = current_exp;
|
|
|
|
ret = collapse_tree(current_op, parg, error_str);
|
|
/* collapse_tree() may free current_op, and updates parg accordingly */
|
|
current_op = NULL;
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
free(token);
|
|
return 0;
|
|
|
|
fail_alloc:
|
|
show_error(error_str, "failed to allocate filter arg");
|
|
ret = TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
goto fail;
|
|
fail_syntax:
|
|
show_error(error_str, "Syntax error");
|
|
ret = TEP_ERRNO__SYNTAX_ERROR;
|
|
fail:
|
|
free_arg(current_op);
|
|
free_arg(current_exp);
|
|
free_arg(arg);
|
|
free(token);
|
|
return ret;
|
|
}
|
|
|
|
static enum tep_errno
|
|
process_event(struct tep_event *event, const char *filter_str,
|
|
struct tep_filter_arg **parg, char *error_str)
|
|
{
|
|
int ret;
|
|
|
|
init_input_buf(filter_str, strlen(filter_str));
|
|
|
|
ret = process_filter(event, parg, error_str, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* If parg is NULL, then make it into FALSE */
|
|
if (!*parg) {
|
|
*parg = allocate_arg();
|
|
if (*parg == NULL)
|
|
return TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
|
|
(*parg)->type = TEP_FILTER_ARG_BOOLEAN;
|
|
(*parg)->boolean.value = TEP_FILTER_FALSE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum tep_errno
|
|
filter_event(struct tep_event_filter *filter, struct tep_event *event,
|
|
const char *filter_str, char *error_str)
|
|
{
|
|
struct tep_filter_type *filter_type;
|
|
struct tep_filter_arg *arg;
|
|
enum tep_errno ret;
|
|
|
|
if (filter_str) {
|
|
ret = process_event(event, filter_str, &arg, error_str);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
} else {
|
|
/* just add a TRUE arg */
|
|
arg = allocate_arg();
|
|
if (arg == NULL)
|
|
return TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
|
|
arg->type = TEP_FILTER_ARG_BOOLEAN;
|
|
arg->boolean.value = TEP_FILTER_TRUE;
|
|
}
|
|
|
|
filter_type = add_filter_type(filter, event->id);
|
|
if (filter_type == NULL) {
|
|
free_arg(arg);
|
|
return TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
}
|
|
|
|
if (filter_type->filter)
|
|
free_arg(filter_type->filter);
|
|
filter_type->filter = arg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void filter_init_error_buf(struct tep_event_filter *filter)
|
|
{
|
|
/* clear buffer to reset show error */
|
|
init_input_buf("", 0);
|
|
filter->error_buffer[0] = '\0';
|
|
}
|
|
|
|
/**
|
|
* tep_filter_add_filter_str - add a new filter
|
|
* @filter: the event filter to add to
|
|
* @filter_str: the filter string that contains the filter
|
|
*
|
|
* Returns 0 if the filter was successfully added or a
|
|
* negative error code. Use tep_filter_strerror() to see
|
|
* actual error message in case of error.
|
|
*/
|
|
enum tep_errno tep_filter_add_filter_str(struct tep_event_filter *filter,
|
|
const char *filter_str)
|
|
{
|
|
struct tep_handle *tep = filter->tep;
|
|
struct event_list *event;
|
|
struct event_list *events = NULL;
|
|
const char *filter_start;
|
|
const char *next_event;
|
|
char *this_event;
|
|
char *event_name = NULL;
|
|
char *sys_name = NULL;
|
|
char *sp;
|
|
enum tep_errno rtn = 0; /* TEP_ERRNO__SUCCESS */
|
|
int len;
|
|
int ret;
|
|
|
|
filter_init_error_buf(filter);
|
|
|
|
filter_start = strchr(filter_str, ':');
|
|
if (filter_start)
|
|
len = filter_start - filter_str;
|
|
else
|
|
len = strlen(filter_str);
|
|
|
|
do {
|
|
next_event = strchr(filter_str, ',');
|
|
if (next_event &&
|
|
(!filter_start || next_event < filter_start))
|
|
len = next_event - filter_str;
|
|
else if (filter_start)
|
|
len = filter_start - filter_str;
|
|
else
|
|
len = strlen(filter_str);
|
|
|
|
this_event = malloc(len + 1);
|
|
if (this_event == NULL) {
|
|
/* This can only happen when events is NULL, but still */
|
|
free_events(events);
|
|
return TEP_ERRNO__MEM_ALLOC_FAILED;
|
|
}
|
|
memcpy(this_event, filter_str, len);
|
|
this_event[len] = 0;
|
|
|
|
if (next_event)
|
|
next_event++;
|
|
|
|
filter_str = next_event;
|
|
|
|
sys_name = strtok_r(this_event, "/", &sp);
|
|
event_name = strtok_r(NULL, "/", &sp);
|
|
|
|
if (!sys_name) {
|
|
/* This can only happen when events is NULL, but still */
|
|
free_events(events);
|
|
free(this_event);
|
|
return TEP_ERRNO__FILTER_NOT_FOUND;
|
|
}
|
|
|
|
/* Find this event */
|
|
ret = find_event(tep, &events, strim(sys_name), strim(event_name));
|
|
if (ret < 0) {
|
|
free_events(events);
|
|
free(this_event);
|
|
return ret;
|
|
}
|
|
free(this_event);
|
|
} while (filter_str);
|
|
|
|
/* Skip the ':' */
|
|
if (filter_start)
|
|
filter_start++;
|
|
|
|
/* filter starts here */
|
|
for (event = events; event; event = event->next) {
|
|
ret = filter_event(filter, event->event, filter_start,
|
|
filter->error_buffer);
|
|
/* Failures are returned if a parse error happened */
|
|
if (ret < 0)
|
|
rtn = ret;
|
|
|
|
if (ret >= 0 && tep->test_filters) {
|
|
char *test;
|
|
test = tep_filter_make_string(filter, event->event->id);
|
|
if (test) {
|
|
printf(" '%s: %s'\n", event->event->name, test);
|
|
free(test);
|
|
}
|
|
}
|
|
}
|
|
|
|
free_events(events);
|
|
|
|
return rtn;
|
|
}
|
|
|
|
static void free_filter_type(struct tep_filter_type *filter_type)
|
|
{
|
|
free_arg(filter_type->filter);
|
|
}
|
|
|
|
/**
|
|
* tep_filter_strerror - fill error message in a buffer
|
|
* @filter: the event filter contains error
|
|
* @err: the error code
|
|
* @buf: the buffer to be filled in
|
|
* @buflen: the size of the buffer
|
|
*
|
|
* Returns 0 if message was filled successfully, -1 if error
|
|
*/
|
|
int tep_filter_strerror(struct tep_event_filter *filter, enum tep_errno err,
|
|
char *buf, size_t buflen)
|
|
{
|
|
if (err <= __TEP_ERRNO__START || err >= __TEP_ERRNO__END)
|
|
return -1;
|
|
|
|
if (strlen(filter->error_buffer) > 0) {
|
|
size_t len = snprintf(buf, buflen, "%s", filter->error_buffer);
|
|
|
|
if (len > buflen)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
return tep_strerror(filter->tep, err, buf, buflen);
|
|
}
|
|
|
|
/**
|
|
* tep_filter_remove_event - remove a filter for an event
|
|
* @filter: the event filter to remove from
|
|
* @event_id: the event to remove a filter for
|
|
*
|
|
* Removes the filter saved for an event defined by @event_id
|
|
* from the @filter.
|
|
*
|
|
* Returns 1: if an event was removed
|
|
* 0: if the event was not found
|
|
*/
|
|
int tep_filter_remove_event(struct tep_event_filter *filter,
|
|
int event_id)
|
|
{
|
|
struct tep_filter_type *filter_type;
|
|
unsigned long len;
|
|
|
|
if (!filter->filters)
|
|
return 0;
|
|
|
|
filter_type = find_filter_type(filter, event_id);
|
|
|
|
if (!filter_type)
|
|
return 0;
|
|
|
|
free_filter_type(filter_type);
|
|
|
|
/* The filter_type points into the event_filters array */
|
|
len = (unsigned long)(filter->event_filters + filter->filters) -
|
|
(unsigned long)(filter_type + 1);
|
|
|
|
memmove(filter_type, filter_type + 1, len);
|
|
filter->filters--;
|
|
|
|
memset(&filter->event_filters[filter->filters], 0,
|
|
sizeof(*filter_type));
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* tep_filter_reset - clear all filters in a filter
|
|
* @filter: the event filter to reset
|
|
*
|
|
* Removes all filters from a filter and resets it.
|
|
*/
|
|
void tep_filter_reset(struct tep_event_filter *filter)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < filter->filters; i++)
|
|
free_filter_type(&filter->event_filters[i]);
|
|
|
|
free(filter->event_filters);
|
|
filter->filters = 0;
|
|
filter->event_filters = NULL;
|
|
}
|
|
|
|
void tep_filter_free(struct tep_event_filter *filter)
|
|
{
|
|
tep_unref(filter->tep);
|
|
|
|
tep_filter_reset(filter);
|
|
|
|
free(filter);
|
|
}
|
|
|
|
static char *arg_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg);
|
|
|
|
static int copy_filter_type(struct tep_event_filter *filter,
|
|
struct tep_event_filter *source,
|
|
struct tep_filter_type *filter_type)
|
|
{
|
|
struct tep_filter_arg *arg;
|
|
struct tep_event *event;
|
|
const char *sys;
|
|
const char *name;
|
|
char *str;
|
|
|
|
/* Can't assume that the tep's are the same */
|
|
sys = filter_type->event->system;
|
|
name = filter_type->event->name;
|
|
event = tep_find_event_by_name(filter->tep, sys, name);
|
|
if (!event)
|
|
return -1;
|
|
|
|
str = arg_to_str(source, filter_type->filter);
|
|
if (!str)
|
|
return -1;
|
|
|
|
if (strcmp(str, "TRUE") == 0 || strcmp(str, "FALSE") == 0) {
|
|
/* Add trivial event */
|
|
arg = allocate_arg();
|
|
if (arg == NULL) {
|
|
free(str);
|
|
return -1;
|
|
}
|
|
|
|
arg->type = TEP_FILTER_ARG_BOOLEAN;
|
|
if (strcmp(str, "TRUE") == 0)
|
|
arg->boolean.value = 1;
|
|
else
|
|
arg->boolean.value = 0;
|
|
|
|
filter_type = add_filter_type(filter, event->id);
|
|
if (filter_type == NULL) {
|
|
free(str);
|
|
free_arg(arg);
|
|
return -1;
|
|
}
|
|
|
|
filter_type->filter = arg;
|
|
|
|
free(str);
|
|
return 0;
|
|
}
|
|
|
|
filter_event(filter, event, str, NULL);
|
|
free(str);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tep_filter_copy - copy a filter using another filter
|
|
* @dest - the filter to copy to
|
|
* @source - the filter to copy from
|
|
*
|
|
* Returns 0 on success and -1 if not all filters were copied
|
|
*/
|
|
int tep_filter_copy(struct tep_event_filter *dest, struct tep_event_filter *source)
|
|
{
|
|
int ret = 0;
|
|
int i;
|
|
|
|
tep_filter_reset(dest);
|
|
|
|
for (i = 0; i < source->filters; i++) {
|
|
if (copy_filter_type(dest, source, &source->event_filters[i]))
|
|
ret = -1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int test_filter(struct tep_event *event, struct tep_filter_arg *arg,
|
|
struct tep_record *record, enum tep_errno *err);
|
|
|
|
static const char *
|
|
get_comm(struct tep_event *event, struct tep_record *record)
|
|
{
|
|
const char *comm;
|
|
int pid;
|
|
|
|
pid = tep_data_pid(event->tep, record);
|
|
comm = tep_data_comm_from_pid(event->tep, pid);
|
|
return comm;
|
|
}
|
|
|
|
static unsigned long long
|
|
get_value(struct tep_event *event,
|
|
struct tep_format_field *field, struct tep_record *record)
|
|
{
|
|
unsigned long long val;
|
|
|
|
/* Handle our dummy "comm" field */
|
|
if (field == &comm) {
|
|
const char *name;
|
|
|
|
name = get_comm(event, record);
|
|
return (unsigned long)name;
|
|
}
|
|
|
|
/* Handle our dummy "cpu" field */
|
|
if (field == &cpu)
|
|
return record->cpu;
|
|
|
|
tep_read_number_field(field, record->data, &val);
|
|
|
|
if (!(field->flags & TEP_FIELD_IS_SIGNED))
|
|
return val;
|
|
|
|
switch (field->size) {
|
|
case 1:
|
|
return (char)val;
|
|
case 2:
|
|
return (short)val;
|
|
case 4:
|
|
return (int)val;
|
|
case 8:
|
|
return (long long)val;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static unsigned long long
|
|
get_arg_value(struct tep_event *event, struct tep_filter_arg *arg,
|
|
struct tep_record *record, enum tep_errno *err);
|
|
|
|
static unsigned long long
|
|
get_exp_value(struct tep_event *event, struct tep_filter_arg *arg,
|
|
struct tep_record *record, enum tep_errno *err)
|
|
{
|
|
unsigned long long lval, rval;
|
|
|
|
lval = get_arg_value(event, arg->exp.left, record, err);
|
|
rval = get_arg_value(event, arg->exp.right, record, err);
|
|
|
|
if (*err) {
|
|
/*
|
|
* There was an error, no need to process anymore.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
switch (arg->exp.type) {
|
|
case TEP_FILTER_EXP_ADD:
|
|
return lval + rval;
|
|
|
|
case TEP_FILTER_EXP_SUB:
|
|
return lval - rval;
|
|
|
|
case TEP_FILTER_EXP_MUL:
|
|
return lval * rval;
|
|
|
|
case TEP_FILTER_EXP_DIV:
|
|
return lval / rval;
|
|
|
|
case TEP_FILTER_EXP_MOD:
|
|
return lval % rval;
|
|
|
|
case TEP_FILTER_EXP_RSHIFT:
|
|
return lval >> rval;
|
|
|
|
case TEP_FILTER_EXP_LSHIFT:
|
|
return lval << rval;
|
|
|
|
case TEP_FILTER_EXP_AND:
|
|
return lval & rval;
|
|
|
|
case TEP_FILTER_EXP_OR:
|
|
return lval | rval;
|
|
|
|
case TEP_FILTER_EXP_XOR:
|
|
return lval ^ rval;
|
|
|
|
case TEP_FILTER_EXP_NOT:
|
|
default:
|
|
if (!*err)
|
|
*err = TEP_ERRNO__INVALID_EXP_TYPE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long long
|
|
get_arg_value(struct tep_event *event, struct tep_filter_arg *arg,
|
|
struct tep_record *record, enum tep_errno *err)
|
|
{
|
|
switch (arg->type) {
|
|
case TEP_FILTER_ARG_FIELD:
|
|
return get_value(event, arg->field.field, record);
|
|
|
|
case TEP_FILTER_ARG_VALUE:
|
|
if (arg->value.type != TEP_FILTER_NUMBER) {
|
|
if (!*err)
|
|
*err = TEP_ERRNO__NOT_A_NUMBER;
|
|
}
|
|
return arg->value.val;
|
|
|
|
case TEP_FILTER_ARG_EXP:
|
|
return get_exp_value(event, arg, record, err);
|
|
|
|
default:
|
|
if (!*err)
|
|
*err = TEP_ERRNO__INVALID_ARG_TYPE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int test_num(struct tep_event *event, struct tep_filter_arg *arg,
|
|
struct tep_record *record, enum tep_errno *err)
|
|
{
|
|
unsigned long long lval, rval;
|
|
|
|
lval = get_arg_value(event, arg->num.left, record, err);
|
|
rval = get_arg_value(event, arg->num.right, record, err);
|
|
|
|
if (*err) {
|
|
/*
|
|
* There was an error, no need to process anymore.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
switch (arg->num.type) {
|
|
case TEP_FILTER_CMP_EQ:
|
|
return lval == rval;
|
|
|
|
case TEP_FILTER_CMP_NE:
|
|
return lval != rval;
|
|
|
|
case TEP_FILTER_CMP_GT:
|
|
return lval > rval;
|
|
|
|
case TEP_FILTER_CMP_LT:
|
|
return lval < rval;
|
|
|
|
case TEP_FILTER_CMP_GE:
|
|
return lval >= rval;
|
|
|
|
case TEP_FILTER_CMP_LE:
|
|
return lval <= rval;
|
|
|
|
default:
|
|
if (!*err)
|
|
*err = TEP_ERRNO__ILLEGAL_INTEGER_CMP;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static const char *get_field_str(struct tep_filter_arg *arg, struct tep_record *record)
|
|
{
|
|
struct tep_event *event;
|
|
struct tep_handle *tep;
|
|
unsigned long long addr;
|
|
const char *val = NULL;
|
|
unsigned int size;
|
|
char hex[64];
|
|
|
|
/* If the field is not a string convert it */
|
|
if (arg->str.field->flags & TEP_FIELD_IS_STRING) {
|
|
val = record->data + arg->str.field->offset;
|
|
size = arg->str.field->size;
|
|
|
|
if (arg->str.field->flags & TEP_FIELD_IS_DYNAMIC) {
|
|
addr = *(unsigned int *)val;
|
|
val = record->data + (addr & 0xffff);
|
|
size = addr >> 16;
|
|
}
|
|
|
|
/*
|
|
* We need to copy the data since we can't be sure the field
|
|
* is null terminated.
|
|
*/
|
|
if (*(val + size - 1)) {
|
|
/* copy it */
|
|
memcpy(arg->str.buffer, val, arg->str.field->size);
|
|
/* the buffer is already NULL terminated */
|
|
val = arg->str.buffer;
|
|
}
|
|
|
|
} else {
|
|
event = arg->str.field->event;
|
|
tep = event->tep;
|
|
addr = get_value(event, arg->str.field, record);
|
|
|
|
if (arg->str.field->flags & (TEP_FIELD_IS_POINTER | TEP_FIELD_IS_LONG))
|
|
/* convert to a kernel symbol */
|
|
val = tep_find_function(tep, addr);
|
|
|
|
if (val == NULL) {
|
|
/* just use the hex of the string name */
|
|
snprintf(hex, 64, "0x%llx", addr);
|
|
val = hex;
|
|
}
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static int test_str(struct tep_event *event, struct tep_filter_arg *arg,
|
|
struct tep_record *record, enum tep_errno *err)
|
|
{
|
|
const char *val;
|
|
|
|
if (arg->str.field == &comm)
|
|
val = get_comm(event, record);
|
|
else
|
|
val = get_field_str(arg, record);
|
|
|
|
switch (arg->str.type) {
|
|
case TEP_FILTER_CMP_MATCH:
|
|
return strcmp(val, arg->str.val) == 0;
|
|
|
|
case TEP_FILTER_CMP_NOT_MATCH:
|
|
return strcmp(val, arg->str.val) != 0;
|
|
|
|
case TEP_FILTER_CMP_REGEX:
|
|
/* Returns zero on match */
|
|
return !regexec(&arg->str.reg, val, 0, NULL, 0);
|
|
|
|
case TEP_FILTER_CMP_NOT_REGEX:
|
|
return regexec(&arg->str.reg, val, 0, NULL, 0);
|
|
|
|
default:
|
|
if (!*err)
|
|
*err = TEP_ERRNO__ILLEGAL_STRING_CMP;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int test_op(struct tep_event *event, struct tep_filter_arg *arg,
|
|
struct tep_record *record, enum tep_errno *err)
|
|
{
|
|
switch (arg->op.type) {
|
|
case TEP_FILTER_OP_AND:
|
|
return test_filter(event, arg->op.left, record, err) &&
|
|
test_filter(event, arg->op.right, record, err);
|
|
|
|
case TEP_FILTER_OP_OR:
|
|
return test_filter(event, arg->op.left, record, err) ||
|
|
test_filter(event, arg->op.right, record, err);
|
|
|
|
case TEP_FILTER_OP_NOT:
|
|
return !test_filter(event, arg->op.right, record, err);
|
|
|
|
default:
|
|
if (!*err)
|
|
*err = TEP_ERRNO__INVALID_OP_TYPE;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int test_filter(struct tep_event *event, struct tep_filter_arg *arg,
|
|
struct tep_record *record, enum tep_errno *err)
|
|
{
|
|
if (*err) {
|
|
/*
|
|
* There was an error, no need to process anymore.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
switch (arg->type) {
|
|
case TEP_FILTER_ARG_BOOLEAN:
|
|
/* easy case */
|
|
return arg->boolean.value;
|
|
|
|
case TEP_FILTER_ARG_OP:
|
|
return test_op(event, arg, record, err);
|
|
|
|
case TEP_FILTER_ARG_NUM:
|
|
return test_num(event, arg, record, err);
|
|
|
|
case TEP_FILTER_ARG_STR:
|
|
return test_str(event, arg, record, err);
|
|
|
|
case TEP_FILTER_ARG_EXP:
|
|
case TEP_FILTER_ARG_VALUE:
|
|
case TEP_FILTER_ARG_FIELD:
|
|
/*
|
|
* Expressions, fields and values evaluate
|
|
* to true if they return non zero
|
|
*/
|
|
return !!get_arg_value(event, arg, record, err);
|
|
|
|
default:
|
|
if (!*err)
|
|
*err = TEP_ERRNO__INVALID_ARG_TYPE;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* tep_event_filtered - return true if event has filter
|
|
* @filter: filter struct with filter information
|
|
* @event_id: event id to test if filter exists
|
|
*
|
|
* Returns 1 if filter found for @event_id
|
|
* otherwise 0;
|
|
*/
|
|
int tep_event_filtered(struct tep_event_filter *filter, int event_id)
|
|
{
|
|
struct tep_filter_type *filter_type;
|
|
|
|
if (!filter->filters)
|
|
return 0;
|
|
|
|
filter_type = find_filter_type(filter, event_id);
|
|
|
|
return filter_type ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* tep_filter_match - test if a record matches a filter
|
|
* @filter: filter struct with filter information
|
|
* @record: the record to test against the filter
|
|
*
|
|
* Returns: match result or error code (prefixed with TEP_ERRNO__)
|
|
* FILTER_MATCH - filter found for event and @record matches
|
|
* FILTER_MISS - filter found for event and @record does not match
|
|
* FILTER_NOT_FOUND - no filter found for @record's event
|
|
* NO_FILTER - if no filters exist
|
|
* otherwise - error occurred during test
|
|
*/
|
|
enum tep_errno tep_filter_match(struct tep_event_filter *filter,
|
|
struct tep_record *record)
|
|
{
|
|
struct tep_handle *tep = filter->tep;
|
|
struct tep_filter_type *filter_type;
|
|
int event_id;
|
|
int ret;
|
|
enum tep_errno err = 0;
|
|
|
|
filter_init_error_buf(filter);
|
|
|
|
if (!filter->filters)
|
|
return TEP_ERRNO__NO_FILTER;
|
|
|
|
event_id = tep_data_type(tep, record);
|
|
|
|
filter_type = find_filter_type(filter, event_id);
|
|
if (!filter_type)
|
|
return TEP_ERRNO__FILTER_NOT_FOUND;
|
|
|
|
ret = test_filter(filter_type->event, filter_type->filter, record, &err);
|
|
if (err)
|
|
return err;
|
|
|
|
return ret ? TEP_ERRNO__FILTER_MATCH : TEP_ERRNO__FILTER_MISS;
|
|
}
|
|
|
|
static char *op_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg)
|
|
{
|
|
char *str = NULL;
|
|
char *left = NULL;
|
|
char *right = NULL;
|
|
char *op = NULL;
|
|
int left_val = -1;
|
|
int right_val = -1;
|
|
int val;
|
|
|
|
switch (arg->op.type) {
|
|
case TEP_FILTER_OP_AND:
|
|
op = "&&";
|
|
/* fall through */
|
|
case TEP_FILTER_OP_OR:
|
|
if (!op)
|
|
op = "||";
|
|
|
|
left = arg_to_str(filter, arg->op.left);
|
|
right = arg_to_str(filter, arg->op.right);
|
|
if (!left || !right)
|
|
break;
|
|
|
|
/* Try to consolidate boolean values */
|
|
if (strcmp(left, "TRUE") == 0)
|
|
left_val = 1;
|
|
else if (strcmp(left, "FALSE") == 0)
|
|
left_val = 0;
|
|
|
|
if (strcmp(right, "TRUE") == 0)
|
|
right_val = 1;
|
|
else if (strcmp(right, "FALSE") == 0)
|
|
right_val = 0;
|
|
|
|
if (left_val >= 0) {
|
|
if ((arg->op.type == TEP_FILTER_OP_AND && !left_val) ||
|
|
(arg->op.type == TEP_FILTER_OP_OR && left_val)) {
|
|
/* Just return left value */
|
|
str = left;
|
|
left = NULL;
|
|
break;
|
|
}
|
|
if (right_val >= 0) {
|
|
/* just evaluate this. */
|
|
val = 0;
|
|
switch (arg->op.type) {
|
|
case TEP_FILTER_OP_AND:
|
|
val = left_val && right_val;
|
|
break;
|
|
case TEP_FILTER_OP_OR:
|
|
val = left_val || right_val;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (asprintf(&str, val ? "TRUE" : "FALSE") < 0)
|
|
str = NULL;
|
|
break;
|
|
}
|
|
}
|
|
if (right_val >= 0) {
|
|
if ((arg->op.type == TEP_FILTER_OP_AND && !right_val) ||
|
|
(arg->op.type == TEP_FILTER_OP_OR && right_val)) {
|
|
/* Just return right value */
|
|
str = right;
|
|
right = NULL;
|
|
break;
|
|
}
|
|
/* The right value is meaningless */
|
|
str = left;
|
|
left = NULL;
|
|
break;
|
|
}
|
|
|
|
if (asprintf(&str, "(%s) %s (%s)", left, op, right) < 0)
|
|
str = NULL;
|
|
break;
|
|
|
|
case TEP_FILTER_OP_NOT:
|
|
op = "!";
|
|
right = arg_to_str(filter, arg->op.right);
|
|
if (!right)
|
|
break;
|
|
|
|
/* See if we can consolidate */
|
|
if (strcmp(right, "TRUE") == 0)
|
|
right_val = 1;
|
|
else if (strcmp(right, "FALSE") == 0)
|
|
right_val = 0;
|
|
if (right_val >= 0) {
|
|
/* just return the opposite */
|
|
if (asprintf(&str, right_val ? "FALSE" : "TRUE") < 0)
|
|
str = NULL;
|
|
break;
|
|
}
|
|
if (asprintf(&str, "%s(%s)", op, right) < 0)
|
|
str = NULL;
|
|
break;
|
|
|
|
default:
|
|
/* ?? */
|
|
break;
|
|
}
|
|
free(left);
|
|
free(right);
|
|
return str;
|
|
}
|
|
|
|
static char *val_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg)
|
|
{
|
|
char *str = NULL;
|
|
|
|
if (asprintf(&str, "%lld", arg->value.val) < 0)
|
|
str = NULL;
|
|
|
|
return str;
|
|
}
|
|
|
|
static char *field_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg)
|
|
{
|
|
return strdup(arg->field.field->name);
|
|
}
|
|
|
|
static char *exp_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg)
|
|
{
|
|
char *lstr;
|
|
char *rstr;
|
|
char *op;
|
|
char *str = NULL;
|
|
|
|
lstr = arg_to_str(filter, arg->exp.left);
|
|
rstr = arg_to_str(filter, arg->exp.right);
|
|
if (!lstr || !rstr)
|
|
goto out;
|
|
|
|
switch (arg->exp.type) {
|
|
case TEP_FILTER_EXP_ADD:
|
|
op = "+";
|
|
break;
|
|
case TEP_FILTER_EXP_SUB:
|
|
op = "-";
|
|
break;
|
|
case TEP_FILTER_EXP_MUL:
|
|
op = "*";
|
|
break;
|
|
case TEP_FILTER_EXP_DIV:
|
|
op = "/";
|
|
break;
|
|
case TEP_FILTER_EXP_MOD:
|
|
op = "%";
|
|
break;
|
|
case TEP_FILTER_EXP_RSHIFT:
|
|
op = ">>";
|
|
break;
|
|
case TEP_FILTER_EXP_LSHIFT:
|
|
op = "<<";
|
|
break;
|
|
case TEP_FILTER_EXP_AND:
|
|
op = "&";
|
|
break;
|
|
case TEP_FILTER_EXP_OR:
|
|
op = "|";
|
|
break;
|
|
case TEP_FILTER_EXP_XOR:
|
|
op = "^";
|
|
break;
|
|
default:
|
|
op = "[ERROR IN EXPRESSION TYPE]";
|
|
break;
|
|
}
|
|
|
|
if (asprintf(&str, "%s %s %s", lstr, op, rstr) < 0)
|
|
str = NULL;
|
|
out:
|
|
free(lstr);
|
|
free(rstr);
|
|
|
|
return str;
|
|
}
|
|
|
|
static char *num_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg)
|
|
{
|
|
char *lstr;
|
|
char *rstr;
|
|
char *str = NULL;
|
|
char *op = NULL;
|
|
|
|
lstr = arg_to_str(filter, arg->num.left);
|
|
rstr = arg_to_str(filter, arg->num.right);
|
|
if (!lstr || !rstr)
|
|
goto out;
|
|
|
|
switch (arg->num.type) {
|
|
case TEP_FILTER_CMP_EQ:
|
|
op = "==";
|
|
/* fall through */
|
|
case TEP_FILTER_CMP_NE:
|
|
if (!op)
|
|
op = "!=";
|
|
/* fall through */
|
|
case TEP_FILTER_CMP_GT:
|
|
if (!op)
|
|
op = ">";
|
|
/* fall through */
|
|
case TEP_FILTER_CMP_LT:
|
|
if (!op)
|
|
op = "<";
|
|
/* fall through */
|
|
case TEP_FILTER_CMP_GE:
|
|
if (!op)
|
|
op = ">=";
|
|
/* fall through */
|
|
case TEP_FILTER_CMP_LE:
|
|
if (!op)
|
|
op = "<=";
|
|
|
|
if (asprintf(&str, "%s %s %s", lstr, op, rstr) < 0)
|
|
str = NULL;
|
|
break;
|
|
|
|
default:
|
|
/* ?? */
|
|
break;
|
|
}
|
|
|
|
out:
|
|
free(lstr);
|
|
free(rstr);
|
|
return str;
|
|
}
|
|
|
|
static char *str_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg)
|
|
{
|
|
char *str = NULL;
|
|
char *op = NULL;
|
|
|
|
switch (arg->str.type) {
|
|
case TEP_FILTER_CMP_MATCH:
|
|
op = "==";
|
|
/* fall through */
|
|
case TEP_FILTER_CMP_NOT_MATCH:
|
|
if (!op)
|
|
op = "!=";
|
|
/* fall through */
|
|
case TEP_FILTER_CMP_REGEX:
|
|
if (!op)
|
|
op = "=~";
|
|
/* fall through */
|
|
case TEP_FILTER_CMP_NOT_REGEX:
|
|
if (!op)
|
|
op = "!~";
|
|
|
|
if (asprintf(&str, "%s %s \"%s\"",
|
|
arg->str.field->name, op, arg->str.val) < 0)
|
|
str = NULL;
|
|
break;
|
|
|
|
default:
|
|
/* ?? */
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
static char *arg_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg)
|
|
{
|
|
char *str = NULL;
|
|
|
|
switch (arg->type) {
|
|
case TEP_FILTER_ARG_BOOLEAN:
|
|
if (asprintf(&str, arg->boolean.value ? "TRUE" : "FALSE") < 0)
|
|
str = NULL;
|
|
return str;
|
|
|
|
case TEP_FILTER_ARG_OP:
|
|
return op_to_str(filter, arg);
|
|
|
|
case TEP_FILTER_ARG_NUM:
|
|
return num_to_str(filter, arg);
|
|
|
|
case TEP_FILTER_ARG_STR:
|
|
return str_to_str(filter, arg);
|
|
|
|
case TEP_FILTER_ARG_VALUE:
|
|
return val_to_str(filter, arg);
|
|
|
|
case TEP_FILTER_ARG_FIELD:
|
|
return field_to_str(filter, arg);
|
|
|
|
case TEP_FILTER_ARG_EXP:
|
|
return exp_to_str(filter, arg);
|
|
|
|
default:
|
|
/* ?? */
|
|
return NULL;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* tep_filter_make_string - return a string showing the filter
|
|
* @filter: filter struct with filter information
|
|
* @event_id: the event id to return the filter string with
|
|
*
|
|
* Returns a string that displays the filter contents.
|
|
* This string must be freed with free(str).
|
|
* NULL is returned if no filter is found or allocation failed.
|
|
*/
|
|
char *
|
|
tep_filter_make_string(struct tep_event_filter *filter, int event_id)
|
|
{
|
|
struct tep_filter_type *filter_type;
|
|
|
|
if (!filter->filters)
|
|
return NULL;
|
|
|
|
filter_type = find_filter_type(filter, event_id);
|
|
|
|
if (!filter_type)
|
|
return NULL;
|
|
|
|
return arg_to_str(filter, filter_type->filter);
|
|
}
|
|
|
|
/**
|
|
* tep_filter_compare - compare two filters and return if they are the same
|
|
* @filter1: Filter to compare with @filter2
|
|
* @filter2: Filter to compare with @filter1
|
|
*
|
|
* Returns:
|
|
* 1 if the two filters hold the same content.
|
|
* 0 if they do not.
|
|
*/
|
|
int tep_filter_compare(struct tep_event_filter *filter1, struct tep_event_filter *filter2)
|
|
{
|
|
struct tep_filter_type *filter_type1;
|
|
struct tep_filter_type *filter_type2;
|
|
char *str1, *str2;
|
|
int result;
|
|
int i;
|
|
|
|
/* Do the easy checks first */
|
|
if (filter1->filters != filter2->filters)
|
|
return 0;
|
|
if (!filter1->filters && !filter2->filters)
|
|
return 1;
|
|
|
|
/*
|
|
* Now take a look at each of the events to see if they have the same
|
|
* filters to them.
|
|
*/
|
|
for (i = 0; i < filter1->filters; i++) {
|
|
filter_type1 = &filter1->event_filters[i];
|
|
filter_type2 = find_filter_type(filter2, filter_type1->event_id);
|
|
if (!filter_type2)
|
|
break;
|
|
if (filter_type1->filter->type != filter_type2->filter->type)
|
|
break;
|
|
/* The best way to compare complex filters is with strings */
|
|
str1 = arg_to_str(filter1, filter_type1->filter);
|
|
str2 = arg_to_str(filter2, filter_type2->filter);
|
|
if (str1 && str2)
|
|
result = strcmp(str1, str2) != 0;
|
|
else
|
|
/* bail out if allocation fails */
|
|
result = 1;
|
|
|
|
free(str1);
|
|
free(str2);
|
|
if (result)
|
|
break;
|
|
}
|
|
|
|
if (i < filter1->filters)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|