summaryrefslogtreecommitdiffstats
path: root/src/tracefs-filter.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tracefs-filter.c')
-rw-r--r--src/tracefs-filter.c807
1 files changed, 807 insertions, 0 deletions
diff --git a/src/tracefs-filter.c b/src/tracefs-filter.c
new file mode 100644
index 0000000..a3dd77b
--- /dev/null
+++ b/src/tracefs-filter.c
@@ -0,0 +1,807 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org>
+ *
+ * Updates:
+ * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <trace-seq.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "tracefs.h"
+#include "tracefs-local.h"
+
+enum {
+ S_START,
+ S_COMPARE,
+ S_NOT,
+ S_CONJUNCTION,
+ S_OPEN_PAREN,
+ S_CLOSE_PAREN,
+};
+
+static const struct tep_format_field common_timestamp = {
+ .type = "u64",
+ .name = "common_timestamp",
+ .size = 8,
+};
+
+static const struct tep_format_field common_timestamp_usecs = {
+ .type = "u64",
+ .name = "common_timestamp.usecs",
+ .size = 8,
+};
+
+static const struct tep_format_field common_comm = {
+ .type = "char *",
+ .name = "common_comm",
+ .size = 16,
+};
+
+/*
+ * This also must be able to accept fields that are OK via the histograms,
+ * such as common_timestamp.
+ */
+static const struct tep_format_field *get_event_field(struct tep_event *event,
+ const char *field_name)
+{
+ const struct tep_format_field *field;
+
+ if (!strcmp(field_name, TRACEFS_TIMESTAMP))
+ return &common_timestamp;
+
+ if (!strcmp(field_name, TRACEFS_TIMESTAMP_USECS))
+ return &common_timestamp_usecs;
+
+ field = tep_find_any_field(event, field_name);
+ if (!field && (!strcmp(field_name, "COMM") || !strcmp(field_name, "comm")))
+ return &common_comm;
+
+ return field;
+}
+
+__hidden bool
+trace_verify_event_field(struct tep_event *event,
+ const char *field_name,
+ const struct tep_format_field **ptr_field)
+{
+ const struct tep_format_field *field;
+
+ field = get_event_field(event, field_name);
+ if (!field) {
+ errno = ENODEV;
+ return false;
+ }
+
+ if (ptr_field)
+ *ptr_field = field;
+
+ return true;
+}
+
+__hidden int trace_test_state(int state)
+{
+ switch (state) {
+ case S_START:
+ case S_CLOSE_PAREN:
+ case S_COMPARE:
+ return 0;
+ }
+
+ errno = EBADE;
+ return -1;
+}
+
+static int append_filter(char **filter, unsigned int *state,
+ unsigned int *open_parens,
+ struct tep_event *event,
+ enum tracefs_filter type,
+ const char *field_name,
+ enum tracefs_compare compare,
+ const char *val)
+{
+ const struct tep_format_field *field;
+ bool is_string;
+ char *conj = "||";
+ char *tmp;
+
+ switch (type) {
+ case TRACEFS_FILTER_COMPARE:
+ switch (*state) {
+ case S_START:
+ case S_OPEN_PAREN:
+ case S_CONJUNCTION:
+ case S_NOT:
+ break;
+ default:
+ goto inval;
+ }
+ break;
+
+ case TRACEFS_FILTER_AND:
+ conj = "&&";
+ /* Fall through */
+ case TRACEFS_FILTER_OR:
+ switch (*state) {
+ case S_COMPARE:
+ case S_CLOSE_PAREN:
+ break;
+ default:
+ goto inval;
+ }
+ /* Don't lose old filter on failure */
+ tmp = strdup(*filter);
+ if (!tmp)
+ return -1;
+ tmp = append_string(tmp, NULL, conj);
+ if (!tmp)
+ return -1;
+ free(*filter);
+ *filter = tmp;
+ *state = S_CONJUNCTION;
+ return 0;
+
+ case TRACEFS_FILTER_NOT:
+ switch (*state) {
+ case S_START:
+ case S_OPEN_PAREN:
+ case S_CONJUNCTION:
+ case S_NOT:
+ break;
+ default:
+ goto inval;
+ }
+ if (*filter) {
+ tmp = strdup(*filter);
+ tmp = append_string(tmp, NULL, "!");
+ } else {
+ tmp = strdup("!");
+ }
+ if (!tmp)
+ return -1;
+ free(*filter);
+ *filter = tmp;
+ *state = S_NOT;
+ return 0;
+
+ case TRACEFS_FILTER_OPEN_PAREN:
+ switch (*state) {
+ case S_START:
+ case S_OPEN_PAREN:
+ case S_NOT:
+ case S_CONJUNCTION:
+ break;
+ default:
+ goto inval;
+ }
+ if (*filter) {
+ tmp = strdup(*filter);
+ tmp = append_string(tmp, NULL, "(");
+ } else {
+ tmp = strdup("(");
+ }
+ if (!tmp)
+ return -1;
+ free(*filter);
+ *filter = tmp;
+ *state = S_OPEN_PAREN;
+ (*open_parens)++;
+ return 0;
+
+ case TRACEFS_FILTER_CLOSE_PAREN:
+ switch (*state) {
+ case S_CLOSE_PAREN:
+ case S_COMPARE:
+ break;
+ default:
+ goto inval;
+ }
+ if (!*open_parens)
+ goto inval;
+
+ tmp = strdup(*filter);
+ if (!tmp)
+ return -1;
+ tmp = append_string(tmp, NULL, ")");
+ if (!tmp)
+ return -1;
+ free(*filter);
+ *filter = tmp;
+ *state = S_CLOSE_PAREN;
+ (*open_parens)--;
+ return 0;
+ }
+
+ if (!field_name || !val)
+ goto inval;
+
+ if (!trace_verify_event_field(event, field_name, &field))
+ return -1;
+
+ is_string = field->flags & TEP_FIELD_IS_STRING;
+
+ if (!is_string && (field->flags & TEP_FIELD_IS_ARRAY))
+ goto inval;
+
+ if (*filter) {
+ tmp = strdup(*filter);
+ if (!tmp)
+ return -1;
+ tmp = append_string(tmp, NULL, field_name);
+ } else {
+ tmp = strdup(field_name);
+ }
+
+ switch (compare) {
+ case TRACEFS_COMPARE_EQ: tmp = append_string(tmp, NULL, " == "); break;
+ case TRACEFS_COMPARE_NE: tmp = append_string(tmp, NULL, " != "); break;
+ case TRACEFS_COMPARE_RE:
+ if (!is_string)
+ goto inval;
+ tmp = append_string(tmp, NULL, "~");
+ break;
+ default:
+ if (is_string)
+ goto inval;
+ }
+
+ switch (compare) {
+ case TRACEFS_COMPARE_GT: tmp = append_string(tmp, NULL, " > "); break;
+ case TRACEFS_COMPARE_GE: tmp = append_string(tmp, NULL, " >= "); break;
+ case TRACEFS_COMPARE_LT: tmp = append_string(tmp, NULL, " < "); break;
+ case TRACEFS_COMPARE_LE: tmp = append_string(tmp, NULL, " <= "); break;
+ case TRACEFS_COMPARE_AND: tmp = append_string(tmp, NULL, " & "); break;
+ default: break;
+ }
+
+ tmp = append_string(tmp, NULL, val);
+
+ if (!tmp)
+ return -1;
+
+ free(*filter);
+ *filter = tmp;
+ *state = S_COMPARE;
+
+ return 0;
+inval:
+ errno = EINVAL;
+ return -1;
+}
+
+static int count_parens(char *filter, unsigned int *state)
+{
+ bool backslash = false;
+ int quote = 0;
+ int open = 0;
+ int i;
+
+ if (!filter)
+ return 0;
+
+ for (i = 0; filter[i]; i++) {
+ if (quote) {
+ if (backslash)
+ backslash = false;
+ else if (filter[i] == '\\')
+ backslash = true;
+ else if (quote == filter[i])
+ quote = 0;
+ continue;
+ }
+
+ switch (filter[i]) {
+ case '(':
+ *state = S_OPEN_PAREN;
+ open++;
+ break;
+ case ')':
+ *state = S_CLOSE_PAREN;
+ open--;
+ break;
+ case '\'':
+ case '"':
+ *state = S_COMPARE;
+ quote = filter[i];
+ break;
+ case '!':
+ switch (filter[i+1]) {
+ case '=':
+ case '~':
+ *state = S_COMPARE;
+ i++;
+ break;
+ default:
+ *state = S_NOT;
+ }
+ break;
+ case '&':
+ case '|':
+ if (filter[i] == filter[i+1]) {
+ *state = S_CONJUNCTION;
+ i++;
+ break;
+ }
+ /* Fall through */
+ case '0' ... '9':
+ case 'a' ... 'z':
+ case 'A' ... 'Z':
+ case '_': case '+': case '-': case '*': case '/':
+ *state = S_COMPARE;
+ break;
+ }
+ }
+ return open;
+}
+
+__hidden int trace_append_filter(char **filter, unsigned int *state,
+ unsigned int *open_parens,
+ struct tep_event *event,
+ enum tracefs_filter type,
+ const char *field_name,
+ enum tracefs_compare compare,
+ const char *val)
+{
+ return append_filter(filter, state, open_parens, event, type,
+ field_name, compare, val);
+}
+
+/**
+ * tracefs_filter_string_append - create or append a filter for an event
+ * @event: tep_event to create / append a filter for
+ * @filter: Pointer to string to append to (pointer to NULL to create)
+ * @type: The type of element to add to the filter
+ * @field: For @type == TRACEFS_FILTER_COMPARE, the field to compare
+ * @compare: For @type == TRACEFS_FILTER_COMPARE, how to compare @field to @val
+ * @val: For @type == TRACEFS_FILTER_COMPARE, what value @field is to be
+ *
+ * This will put together a filter string for the starting event
+ * of @synth. It check to make sure that what is added is correct compared
+ * to the filter that is already built.
+ *
+ * @type can be:
+ * TRACEFS_FILTER_COMPARE: See below
+ * TRACEFS_FILTER_AND: Append "&&" to the filter
+ * TRACEFS_FILTER_OR: Append "||" to the filter
+ * TRACEFS_FILTER_NOT: Append "!" to the filter
+ * TRACEFS_FILTER_OPEN_PAREN: Append "(" to the filter
+ * TRACEFS_FILTER_CLOSE_PAREN: Append ")" to the filter
+ *
+ * For all types except TRACEFS_FILTER_COMPARE, the @field, @compare,
+ * and @val are ignored.
+ *
+ * For @type == TRACEFS_FILTER_COMPARE.
+ *
+ * @field is the name of the field for the start event to compare.
+ * If it is not a field for the start event, this return an
+ * error.
+ *
+ * @compare can be one of:
+ * TRACEFS_COMPARE_EQ: Test @field == @val
+ * TRACEFS_COMPARE_NE: Test @field != @val
+ * TRACEFS_COMPARE_GT: Test @field > @val
+ * TRACEFS_COMPARE_GE: Test @field >= @val
+ * TRACEFS_COMPARE_LT: Test @field < @val
+ * TRACEFS_COMPARE_LE: Test @field <= @val
+ * TRACEFS_COMPARE_RE: Test @field ~ @val
+ * TRACEFS_COMPARE_AND: Test @field & @val
+ *
+ * If the @field is of type string, and @compare is not
+ * TRACEFS_COMPARE_EQ, TRACEFS_COMPARE_NE or TRACEFS_COMPARE_RE,
+ * then this will return an error.
+ *
+ * Various other checks are made, for instance, if more CLOSE_PARENs
+ * are added than existing OPEN_PARENs. Or if AND is added after an
+ * OPEN_PAREN or another AND or an OR or a NOT.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+int tracefs_filter_string_append(struct tep_event *event, char **filter,
+ enum tracefs_filter type,
+ const char *field, enum tracefs_compare compare,
+ const char *val)
+{
+ unsigned int open_parens;
+ unsigned int state = 0;
+ char *str = NULL;
+ int open;
+ int ret;
+
+ if (!filter) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ open = count_parens(*filter, &state);
+ if (open < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (*filter) {
+ /* append_filter() will free filter on error */
+ str = strdup(*filter);
+ if (!str)
+ return -1;
+ }
+ open_parens = open;
+
+ ret = append_filter(&str, &state, &open_parens,
+ event, type, field, compare, val);
+ if (!ret) {
+ free(*filter);
+ *filter = str;
+ }
+
+ return ret;
+}
+
+static int error_msg(char **err, char *str,
+ const char *filter, int i, const char *msg)
+{
+ char ws[i+2];
+ char *errmsg;
+
+ free(str);
+
+ /* msg is NULL for parsing append_filter failing */
+ if (!msg) {
+ switch(errno) {
+ case ENODEV:
+ msg = "field not valid";
+ break;
+ default:
+ msg = "Invalid filter";
+
+ }
+ } else
+ errno = EINVAL;
+
+ if (!err)
+ return -1;
+
+ if (!filter) {
+ *err = strdup(msg);
+ return -1;
+ }
+
+ memset(ws, ' ', i);
+ ws[i] = '^';
+ ws[i+1] = '\0';
+
+ errmsg = strdup(filter);
+ errmsg = append_string(errmsg, "\n", ws);
+ errmsg = append_string(errmsg, "\n", msg);
+ errmsg = append_string(errmsg, NULL, "\n");
+
+ *err = errmsg;
+ return -1;
+}
+
+static int get_field_end(const char *filter, int i, int *end)
+{
+ int start_i = i;
+
+ for (; filter[i]; i++) {
+ switch(filter[i]) {
+ case '0' ... '9':
+ if (i == start_i)
+ return 0;
+ /* Fall through */
+ case 'a' ... 'z':
+ case 'A' ... 'Z':
+ case '_':
+ continue;
+ default:
+ *end = i;
+ return i - start_i;
+ }
+ }
+ *end = i;
+ return i - start_i;
+}
+
+static int get_compare(const char *filter, int i, enum tracefs_compare *cmp)
+{
+ int start_i = i;
+
+ for (; filter[i]; i++) {
+ if (!isspace(filter[i]))
+ break;
+ }
+
+ switch(filter[i]) {
+ case '=':
+ if (filter[i+1] != '=')
+ goto err;
+ *cmp = TRACEFS_COMPARE_EQ;
+ i++;
+ break;
+ case '!':
+ if (filter[i+1] == '=') {
+ *cmp = TRACEFS_COMPARE_NE;
+ i++;
+ break;
+ }
+ if (filter[i+1] == '~') {
+ /* todo! */
+ }
+ goto err;
+ case '>':
+ if (filter[i+1] == '=') {
+ *cmp = TRACEFS_COMPARE_GE;
+ i++;
+ break;
+ }
+ *cmp = TRACEFS_COMPARE_GT;
+ break;
+ case '<':
+ if (filter[i+1] == '=') {
+ *cmp = TRACEFS_COMPARE_LE;
+ i++;
+ break;
+ }
+ *cmp = TRACEFS_COMPARE_LT;
+ break;
+ case '~':
+ *cmp = TRACEFS_COMPARE_RE;
+ break;
+ case '&':
+ *cmp = TRACEFS_COMPARE_AND;
+ break;
+ default:
+ goto err;
+ }
+ i++;
+
+ for (; filter[i]; i++) {
+ if (!isspace(filter[i]))
+ break;
+ }
+ return i - start_i;
+ err:
+ return start_i - i; /* negative or zero */
+}
+
+static int get_val_end(const char *filter, int i, int *end)
+{
+ bool backslash = false;
+ int start_i = i;
+ int quote;
+
+ switch (filter[i]) {
+ case '0':
+ i++;
+ if (tolower(filter[i+1]) != 'x' &&
+ !isdigit(filter[i+1]))
+ break;
+ /* fall through */
+ case '1' ... '9':
+ switch (tolower(filter[i])) {
+ case 'x':
+ for (i++; filter[i]; i++) {
+ if (!isxdigit(filter[i]))
+ break;
+ }
+ break;
+ case '0':
+ for (i++; filter[i]; i++) {
+ if (filter[i] < '0' ||
+ filter[i] > '7')
+ break;
+ }
+ break;
+ default:
+ for (i++; filter[i]; i++) {
+ if (!isdigit(filter[i]))
+ break;
+ }
+ break;
+ }
+ break;
+ case '"':
+ case '\'':
+ quote = filter[i];
+ for (i++; filter[i]; i++) {
+ if (backslash) {
+ backslash = false;
+ continue;
+ }
+ switch (filter[i]) {
+ case '\\':
+ backslash = true;
+ continue;
+ case '"':
+ case '\'':
+ if (filter[i] == quote)
+ break;
+ continue;
+ default:
+ continue;
+ }
+ break;
+ }
+ if (filter[i])
+ i++;
+ break;
+ default:
+ break;
+ }
+
+ *end = i;
+ return i - start_i;
+}
+
+/**
+ * tracefs_filter_string_verify - verify a given filter works for an event
+ * @event: The event to test the given filter for
+ * @filter: The filter to test
+ * @err: Error message for syntax errors (NULL to ignore)
+ *
+ * Parse the @filter to verify that it is valid for the given @event.
+ *
+ * Returns 0 on succes and -1 on error, and except for memory allocation
+ * errors, @err will be allocated with an error message. It must
+ * be freed with free().
+ */
+int tracefs_filter_string_verify(struct tep_event *event, const char *filter,
+ char **err)
+{
+ enum tracefs_filter filter_type;
+ enum tracefs_compare compare;
+ char *str = NULL;
+ char buf[(filter ? strlen(filter) : 0) + 1];
+ char *field;
+ char *val;
+ unsigned int state = 0;
+ unsigned int open = 0;
+ int len;
+ int end;
+ int n;
+ int i;
+
+ if (!filter)
+ return error_msg(err, str, NULL, 0, "No filter given");
+
+ len = strlen(filter);
+
+ for (i = 0; i < len; i++) {
+ field = NULL;
+ val = NULL;
+ compare = 0;
+
+ switch (filter[i]) {
+ case '(':
+ filter_type = TRACEFS_FILTER_OPEN_PAREN;
+ break;
+ case ')':
+ filter_type = TRACEFS_FILTER_CLOSE_PAREN;
+ break;
+ case '!':
+ filter_type = TRACEFS_FILTER_NOT;
+ break;
+ case '&':
+ case '|':
+
+ if (filter[i] == filter[i+1]) {
+ i++;
+ if (filter[i] == '&')
+ filter_type = TRACEFS_FILTER_AND;
+ else
+ filter_type = TRACEFS_FILTER_OR;
+ break;
+ }
+ if (filter[i] == '|')
+ return error_msg(err, str, filter, i,
+ "Invalid op");
+
+ return error_msg(err, str, filter, i,
+ "Invalid location for '&'");
+ default:
+ if (isspace(filter[i]))
+ continue;
+
+ field = buf;
+
+ n = get_field_end(filter, i, &end);
+ if (!n)
+ return error_msg(err, str, filter, i,
+ "Invalid field name");
+
+ strncpy(field, filter+i, n);
+
+ i += n;
+ field[n++] = '\0';
+
+ val = field + n;
+
+ n = get_compare(filter, i, &compare);
+ if (n <= 0)
+ return error_msg(err, str, filter, i - n,
+ "Invalid compare");
+
+ i += n;
+ get_val_end(filter, i, &end);
+ n = end - i;
+ if (!n)
+ return error_msg(err, str, filter, i,
+ "Invalid value");
+ strncpy(val, filter + i, n);
+ val[n] = '\0';
+ i += n - 1;
+
+ filter_type = TRACEFS_FILTER_COMPARE;
+ break;
+ }
+ n = append_filter(&str, &state, &open,
+ event, filter_type, field, compare, val);
+
+ if (n < 0)
+ return error_msg(err, str, filter, i, NULL);
+ }
+
+ if (open)
+ return error_msg(err, str, filter, i,
+ "Not enough closed parenthesis");
+ switch (state) {
+ case S_COMPARE:
+ case S_CLOSE_PAREN:
+ break;
+ default:
+ return error_msg(err, str, filter, i,
+ "Unfinished filter");
+ }
+
+ free(str);
+ return 0;
+}
+
+/**
+ * tracefs_event_filter_apply - apply given filter on event in given instance
+ * @instance: The instance in which the filter will be applied (NULL for toplevel).
+ * @event: The event to apply the filter on.
+ * @filter: The filter to apply.
+ *
+ * Apply the @filter to given @event in givem @instance. The @filter string
+ * should be created with tracefs_filter_string_append().
+ *
+ * Returns 0 on succes and -1 on error.
+ */
+int tracefs_event_filter_apply(struct tracefs_instance *instance,
+ struct tep_event *event, const char *filter)
+{
+ return tracefs_event_file_write(instance, event->system, event->name,
+ "filter", filter);
+}
+
+/**
+ * tracefs_event_filter_clear - clear the filter on event in given instance
+ * @instance: The instance in which the filter will be applied (NULL for toplevel).
+ * @event: The event to apply the filter on.
+ *
+ * Returns 0 on succes and -1 on error.
+ */
+int tracefs_event_filter_clear(struct tracefs_instance *instance,
+ struct tep_event *event)
+{
+ return tracefs_event_file_write(instance, event->system, event->name,
+ "filter", "0");
+}
+
+/** Deprecated **/
+int tracefs_event_append_filter(struct tep_event *event, char **filter,
+ enum tracefs_filter type,
+ const char *field, enum tracefs_compare compare,
+ const char *val)
+{
+ return tracefs_filter_string_append(event, filter, type, field,
+ compare, val);
+}
+int tracefs_event_verify_filter(struct tep_event *event, const char *filter,
+ char **err)
+{
+ return tracefs_filter_string_verify(event, filter, err);
+}