diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /tools/lib/traceevent/parse-filter.c | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/lib/traceevent/parse-filter.c')
-rw-r--r-- | tools/lib/traceevent/parse-filter.c | 2281 |
1 files changed, 2281 insertions, 0 deletions
diff --git a/tools/lib/traceevent/parse-filter.c b/tools/lib/traceevent/parse-filter.c new file mode 100644 index 000000000..5df177070 --- /dev/null +++ b/tools/lib/traceevent/parse-filter.c @@ -0,0 +1,2281 @@ +// 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; + size = addr >> 16; + addr &= 0xffff; + if (arg->str.field->flags & TEP_FIELD_IS_RELATIVE) + addr += arg->str.field->offset + arg->str.field->size; + val = record->data + addr; + } + + /* + * 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; +} + |