summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 07:25:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 07:25:46 +0000
commit5277429a362a9cf4ce649557bf4c8fe0e20c05c0 (patch)
tree52cc97728c6fbb7393984dc3db05c5836107fe44 /src
parentInitial commit. (diff)
downloadlibtraceevent-5277429a362a9cf4ce649557bf4c8fe0e20c05c0.tar.xz
libtraceevent-5277429a362a9cf4ce649557bf4c8fe0e20c05c0.zip
Adding upstream version 1:1.7.1.upstream/1%1.7.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src')
-rw-r--r--src/Build8
-rw-r--r--src/Makefile55
-rw-r--r--src/event-parse-api.c362
-rw-r--r--src/event-parse-local.h127
-rw-r--r--src/event-parse.c8592
-rw-r--r--src/event-plugin.c711
-rw-r--r--src/kbuffer-parse.c848
-rw-r--r--src/parse-filter.c2283
-rw-r--r--src/parse-utils.c151
-rw-r--r--src/tep_strerror.c53
-rw-r--r--src/trace-seq.c249
11 files changed, 13439 insertions, 0 deletions
diff --git a/src/Build b/src/Build
new file mode 100644
index 0000000..f9a5d79
--- /dev/null
+++ b/src/Build
@@ -0,0 +1,8 @@
+libtraceevent-y += event-parse.o
+libtraceevent-y += event-plugin.o
+libtraceevent-y += trace-seq.o
+libtraceevent-y += parse-filter.o
+libtraceevent-y += parse-utils.o
+libtraceevent-y += kbuffer-parse.o
+libtraceevent-y += tep_strerror.o
+libtraceevent-y += event-parse-api.o
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..53bb570
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: LGPL-2.1
+
+include $(src)/scripts/utils.mk
+
+OBJS =
+OBJS += event-parse-api.o
+OBJS += event-parse.o
+OBJS += event-plugin.o
+OBJS += kbuffer-parse.o
+OBJS += parse-filter.o
+OBJS += parse-utils.o
+OBJS += tep_strerror.o
+OBJS += trace-seq.o
+
+OBJS := $(OBJS:%.o=$(bdir)/%.o)
+DEPS := $(OBJS:$(bdir)/%.o=$(bdir)/.%.d)
+
+$(bdir)/%.o: %.c
+ $(Q)$(call do_fpic_compile)
+
+$(LIBTRACEEVENT_STATIC): $(OBJS)
+ $(Q)$(call do_build_static_lib)
+
+$(LIBTRACEEVENT_SHARED): $(OBJS)
+ $(Q)$(call do_compile_shared_library,$(notdir $(LIBTRACEEVENT_SHARED_VERSION)))
+
+$(LIBTRACEEVENT_SHARED_VERSION): $(LIBTRACEEVENT_SHARED)
+ @ln -sf $(<F) $@
+
+$(LIBTRACEEVENT_SHARED_SO): $(LIBTRACEEVENT_SHARED_VERSION)
+ @ln -sf $(<F) $@
+
+libtraceevent.so: $(LIBTRACEEVENT_SHARED_SO)
+
+libtraceevent: $(libtraceevent-y)
+ $(Q)$(MAKE) $(build)=libtraceevent
+
+$(DEPS): $(bdir)/.%.d: %.c
+ $(Q)$(CC) -M -MT $(bdir)/$*.o $(CPPFLAGS) $(CFLAGS) $< > $@
+
+$(OBJS): $(bdir)/%.o : $(bdir)/.%.d
+
+$(OBJS): | $(bdir)
+$(DEPS): | $(bdir)
+
+clean:
+ $(Q)$(call do_clean,$(OBJS) $(DEPS))
+
+dep_includes := $(wildcard $(DEPS))
+
+ifneq ($(dep_includes),)
+ include $(dep_includes)
+endif
+
+.PHONY: $(LIBTRACEEVENT_SHARED_SO) $(LIBTRACEEVENT_STATIC)
diff --git a/src/event-parse-api.c b/src/event-parse-api.c
new file mode 100644
index 0000000..268a586
--- /dev/null
+++ b/src/event-parse-api.c
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ */
+
+#include "event-parse.h"
+#include "event-parse-local.h"
+#include "event-utils.h"
+
+/**
+ * tep_get_event - returns the event with the given index
+ * @tep: a handle to the tep_handle
+ * @index: index of the requested event, in the range 0 .. nr_events
+ *
+ * This returns pointer to the element of the events array with the given index
+ * If @tep is NULL, or @index is not in the range 0 .. nr_events, NULL is returned.
+ */
+struct tep_event *tep_get_event(struct tep_handle *tep, int index)
+{
+ if (tep && tep->events && index < tep->nr_events)
+ return tep->events[index];
+
+ return NULL;
+}
+
+/**
+ * tep_get_first_event - returns the first event in the events array
+ * @tep: a handle to the tep_handle
+ *
+ * This returns pointer to the first element of the events array
+ * If @tep is NULL, NULL is returned.
+ */
+struct tep_event *tep_get_first_event(struct tep_handle *tep)
+{
+ return tep_get_event(tep, 0);
+}
+
+/**
+ * tep_get_events_count - get the number of defined events
+ * @tep: a handle to the tep_handle
+ *
+ * This returns number of elements in event array
+ * If @tep is NULL, 0 is returned.
+ */
+int tep_get_events_count(struct tep_handle *tep)
+{
+ if (tep)
+ return tep->nr_events;
+ return 0;
+}
+
+/**
+ * tep_get_events_count - get the number of defined events
+ * @tep: a handle to the tep_handle
+ *
+ * This returns number of elements in event array
+ * If @tep is NULL, 0 is returned.
+ */
+int tep_get_function_count(struct tep_handle *tep)
+{
+ if (tep)
+ return tep->func_count;
+ return 0;
+}
+
+/**
+ * tep_set_flag - set event parser flag
+ * @tep: a handle to the tep_handle
+ * @flag: flag, or combination of flags to be set
+ * can be any combination from enum tep_flag
+ *
+ * This sets a flag or combination of flags from enum tep_flag
+ */
+void tep_set_flag(struct tep_handle *tep, int flag)
+{
+ if (tep)
+ tep->flags |= flag;
+}
+
+/**
+ * tep_clear_flag - clear event parser flag
+ * @tep: a handle to the tep_handle
+ * @flag: flag to be cleared
+ *
+ * This clears a tep flag
+ */
+void tep_clear_flag(struct tep_handle *tep, enum tep_flag flag)
+{
+ if (tep)
+ tep->flags &= ~flag;
+}
+
+/**
+ * tep_test_flag - check the state of event parser flag
+ * @tep: a handle to the tep_handle
+ * @flag: flag to be checked
+ *
+ * This returns the state of the requested tep flag.
+ * Returns: true if the flag is set, false otherwise.
+ */
+bool tep_test_flag(struct tep_handle *tep, enum tep_flag flag)
+{
+ if (tep)
+ return tep->flags & flag;
+ return false;
+}
+
+__hidden unsigned short data2host2(struct tep_handle *tep, unsigned short data)
+{
+ unsigned short swap;
+
+ if (!tep || tep->host_bigendian == tep->file_bigendian)
+ return data;
+
+ swap = ((data & 0xffULL) << 8) |
+ ((data & (0xffULL << 8)) >> 8);
+
+ return swap;
+}
+
+__hidden unsigned int data2host4(struct tep_handle *tep, unsigned int data)
+{
+ unsigned int swap;
+
+ if (!tep || tep->host_bigendian == tep->file_bigendian)
+ return data;
+
+ swap = ((data & 0xffULL) << 24) |
+ ((data & (0xffULL << 8)) << 8) |
+ ((data & (0xffULL << 16)) >> 8) |
+ ((data & (0xffULL << 24)) >> 24);
+
+ return swap;
+}
+
+__hidden unsigned long long
+data2host8(struct tep_handle *tep, unsigned long long data)
+{
+ unsigned long long swap;
+
+ if (!tep || tep->host_bigendian == tep->file_bigendian)
+ return data;
+
+ swap = ((data & 0xffULL) << 56) |
+ ((data & (0xffULL << 8)) << 40) |
+ ((data & (0xffULL << 16)) << 24) |
+ ((data & (0xffULL << 24)) << 8) |
+ ((data & (0xffULL << 32)) >> 8) |
+ ((data & (0xffULL << 40)) >> 24) |
+ ((data & (0xffULL << 48)) >> 40) |
+ ((data & (0xffULL << 56)) >> 56);
+
+ return swap;
+}
+
+/**
+ * tep_get_header_page_size - get size of the header page
+ * @tep: a handle to the tep_handle
+ *
+ * This returns size of the header page
+ * If @tep is NULL, 0 is returned.
+ */
+int tep_get_header_page_size(struct tep_handle *tep)
+{
+ if (tep)
+ return tep->header_page_size_size;
+ return 0;
+}
+
+/**
+ * tep_get_header_timestamp_size - get size of the timestamp in the header page
+ * @tep: a handle to the tep_handle
+ *
+ * This returns size of the timestamp in the header page
+ * If @tep is NULL, 0 is returned.
+ */
+int tep_get_header_timestamp_size(struct tep_handle *tep)
+{
+ if (tep)
+ return tep->header_page_ts_size;
+ return 0;
+}
+
+/**
+ * tep_get_cpus - get the number of CPUs
+ * @tep: a handle to the tep_handle
+ *
+ * This returns the number of CPUs
+ * If @tep is NULL, 0 is returned.
+ */
+int tep_get_cpus(struct tep_handle *tep)
+{
+ if (tep)
+ return tep->cpus;
+ return 0;
+}
+
+/**
+ * tep_set_cpus - set the number of CPUs
+ * @tep: a handle to the tep_handle
+ *
+ * This sets the number of CPUs
+ */
+void tep_set_cpus(struct tep_handle *tep, int cpus)
+{
+ if (tep)
+ tep->cpus = cpus;
+}
+
+/**
+ * tep_get_long_size - get the size of a long integer on the traced machine
+ * @tep: a handle to the tep_handle
+ *
+ * This returns the size of a long integer on the traced machine
+ * If @tep is NULL, 0 is returned.
+ */
+int tep_get_long_size(struct tep_handle *tep)
+{
+ if (tep)
+ return tep->long_size;
+ return 0;
+}
+
+/**
+ * tep_set_long_size - set the size of a long integer on the traced machine
+ * @tep: a handle to the tep_handle
+ * @size: size, in bytes, of a long integer
+ *
+ * This sets the size of a long integer on the traced machine
+ */
+void tep_set_long_size(struct tep_handle *tep, int long_size)
+{
+ if (tep)
+ tep->long_size = long_size;
+}
+
+/**
+ * tep_get_page_size - get the size of a memory page on the traced machine
+ * @tep: a handle to the tep_handle
+ *
+ * This returns the size of a memory page on the traced machine
+ * If @tep is NULL, 0 is returned.
+ */
+int tep_get_page_size(struct tep_handle *tep)
+{
+ if (tep)
+ return tep->page_size;
+ return 0;
+}
+
+/**
+ * tep_set_page_size - set the size of a memory page on the traced machine
+ * @tep: a handle to the tep_handle
+ * @_page_size: size of a memory page, in bytes
+ *
+ * This sets the size of a memory page on the traced machine
+ */
+void tep_set_page_size(struct tep_handle *tep, int _page_size)
+{
+ if (tep)
+ tep->page_size = _page_size;
+}
+
+/**
+ * tep_get_sub_buffer_size - get the size of a trace buffer page
+ * @tep: a handle to the tep_handle
+ *
+ * This returns the size of a trace buffer page on the traced machine.
+ * If @tep is NULL then -1 is returned.
+ */
+int tep_get_sub_buffer_size(struct tep_handle *tep)
+{
+ if (!tep)
+ return -1;
+
+ return tep->header_page_data_size + tep->header_page_data_offset;
+}
+
+/**
+ * tep_is_file_bigendian - return the endian of the file
+ * @tep: a handle to the tep_handle
+ *
+ * This returns true if the file is in big endian order
+ * If @tep is NULL, false is returned.
+ */
+bool tep_is_file_bigendian(struct tep_handle *tep)
+{
+ if (tep)
+ return (tep->file_bigendian == TEP_BIG_ENDIAN);
+ return false;
+}
+
+/**
+ * tep_set_file_bigendian - set if the file is in big endian order
+ * @tep: a handle to the tep_handle
+ * @endian: non zero, if the file is in big endian order
+ *
+ * This sets if the file is in big endian order
+ */
+void tep_set_file_bigendian(struct tep_handle *tep, enum tep_endian endian)
+{
+ if (tep)
+ tep->file_bigendian = endian;
+}
+
+/**
+ * tep_is_local_bigendian - return the endian of the saved local machine
+ * @tep: a handle to the tep_handle
+ *
+ * This returns true if the saved local machine in @tep is big endian.
+ * If @tep is NULL, false is returned.
+ */
+bool tep_is_local_bigendian(struct tep_handle *tep)
+{
+ if (tep)
+ return (tep->host_bigendian == TEP_BIG_ENDIAN);
+ return 0;
+}
+
+/**
+ * tep_set_local_bigendian - set the stored local machine endian order
+ * @tep: a handle to the tep_handle
+ * @endian: non zero, if the local host has big endian order
+ *
+ * This sets the endian order for the local machine.
+ */
+void tep_set_local_bigendian(struct tep_handle *tep, enum tep_endian endian)
+{
+ if (tep)
+ tep->host_bigendian = endian;
+}
+
+/**
+ * tep_is_old_format - get if an old kernel is used
+ * @tep: a handle to the tep_handle
+ *
+ * This returns true, if an old kernel is used to generate the tracing events or
+ * false if a new kernel is used. Old kernels did not have header page info.
+ * If @tep is NULL, false is returned.
+ */
+bool tep_is_old_format(struct tep_handle *tep)
+{
+ if (tep)
+ return tep->old_format;
+ return false;
+}
+
+/**
+ * tep_set_test_filters - set a flag to test a filter string
+ * @tep: a handle to the tep_handle
+ * @test_filters: the new value of the test_filters flag
+ *
+ * This sets a flag to test a filter string. If this flag is set, when
+ * tep_filter_add_filter_str() API as called,it will print the filter string
+ * instead of adding it.
+ */
+void tep_set_test_filters(struct tep_handle *tep, int test_filters)
+{
+ if (tep)
+ tep->test_filters = test_filters;
+}
diff --git a/src/event-parse-local.h b/src/event-parse-local.h
new file mode 100644
index 0000000..c6bfc61
--- /dev/null
+++ b/src/event-parse-local.h
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ */
+
+#ifndef _PARSE_EVENTS_INT_H
+#define _PARSE_EVENTS_INT_H
+
+struct tep_cmdline;
+struct cmdline_list;
+struct func_map;
+struct func_list;
+struct event_handler;
+struct func_resolver;
+struct tep_plugins_dir;
+
+#define __hidden __attribute__((visibility ("hidden")))
+
+struct tep_handle {
+ int ref_count;
+
+ int header_page_ts_offset;
+ int header_page_ts_size;
+ int header_page_size_offset;
+ int header_page_size_size;
+ int header_page_data_offset;
+ int header_page_data_size;
+ int header_page_overwrite;
+
+ enum tep_endian file_bigendian;
+ enum tep_endian host_bigendian;
+
+ int old_format;
+
+ int cpus;
+ int long_size;
+ int page_size;
+
+ struct tep_cmdline *cmdlines;
+ struct cmdline_list *cmdlist;
+ int cmdline_count;
+
+ struct func_map *func_map;
+ struct func_resolver *func_resolver;
+ struct func_list *funclist;
+ unsigned int func_count;
+
+ struct printk_map *printk_map;
+ struct printk_list *printklist;
+ unsigned int printk_count;
+
+ struct tep_event **events;
+ int nr_events;
+ struct tep_event **sort_events;
+ enum tep_event_sort_type last_type;
+
+ int type_offset;
+ int type_size;
+
+ int pid_offset;
+ int pid_size;
+
+ int pc_offset;
+ int pc_size;
+
+ int flags_offset;
+ int flags_size;
+
+ int ld_offset;
+ int ld_size;
+
+ int test_filters;
+
+ int flags;
+
+ struct tep_format_field *bprint_ip_field;
+ struct tep_format_field *bprint_fmt_field;
+ struct tep_format_field *bprint_buf_field;
+
+ struct event_handler *handlers;
+ struct tep_function_handler *func_handlers;
+
+ /* cache */
+ struct tep_event *last_event;
+
+ struct tep_plugins_dir *plugins_dir;
+
+ const char *input_buf;
+ unsigned long long input_buf_ptr;
+ unsigned long long input_buf_siz;
+};
+
+enum tep_print_parse_type {
+ PRINT_FMT_STRING,
+ PRINT_FMT_ARG_DIGIT,
+ PRINT_FMT_ARG_POINTER,
+ PRINT_FMT_ARG_STRING,
+};
+
+struct tep_print_parse {
+ struct tep_print_parse *next;
+
+ char *format;
+ int ls;
+ enum tep_print_parse_type type;
+ struct tep_print_arg *arg;
+ struct tep_print_arg *len_as_arg;
+};
+
+void free_tep_event(struct tep_event *event);
+void free_tep_format_field(struct tep_format_field *field);
+void free_tep_plugin_paths(struct tep_handle *tep);
+
+unsigned short data2host2(struct tep_handle *tep, unsigned short data);
+unsigned int data2host4(struct tep_handle *tep, unsigned int data);
+unsigned long long data2host8(struct tep_handle *tep, unsigned long long data);
+
+/* access to the internal parser */
+int peek_char(struct tep_handle *tep);
+void init_input_buf(struct tep_handle *tep, const char *buf, unsigned long long size);
+unsigned long long get_input_buf_ptr(struct tep_handle *tep);
+const char *get_input_buf(struct tep_handle *tep);
+enum tep_event_type read_token(struct tep_handle *tep, char **tok);
+void free_token(char *tok);
+
+#endif /* _PARSE_EVENTS_INT_H */
diff --git a/src/event-parse.c b/src/event-parse.c
new file mode 100644
index 0000000..e655087
--- /dev/null
+++ b/src/event-parse.c
@@ -0,0 +1,8592 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ *
+ * The parts for function graph printing was taken and modified from the
+ * Linux Kernel that were written by
+ * - Copyright (C) 2009 Frederic Weisbecker,
+ * Frederic Weisbecker gave his permission to relicense the code to
+ * the Lesser General Public License.
+ */
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <limits.h>
+#include <linux/time64.h>
+
+#include <netinet/in.h>
+#include "event-parse.h"
+
+#include "event-parse-local.h"
+#include "event-utils.h"
+#include "trace-seq.h"
+
+static int is_flag_field;
+static int is_symbolic_field;
+
+static int show_warning = 1;
+
+#define do_warning(fmt, ...) \
+ do { \
+ if (show_warning) \
+ tep_warning(fmt, ##__VA_ARGS__);\
+ } while (0)
+
+#define do_warning_event(event, fmt, ...) \
+ do { \
+ if (!show_warning) \
+ continue; \
+ \
+ if (event) \
+ tep_warning("[%s:%s] " fmt, event->system, \
+ event->name, ##__VA_ARGS__); \
+ else \
+ tep_warning(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+/**
+ * init_input_buf - init buffer for parsing
+ * @buf: buffer to parse
+ * @size: the size of the buffer
+ *
+ * Initializes the internal buffer that tep_read_token() will parse.
+ */
+__hidden void init_input_buf(struct tep_handle *tep, const char *buf,
+ unsigned long long size)
+{
+ tep->input_buf = buf;
+ tep->input_buf_siz = size;
+ tep->input_buf_ptr = 0;
+}
+
+__hidden const char *get_input_buf(struct tep_handle *tep)
+{
+ return tep->input_buf;
+}
+
+__hidden unsigned long long get_input_buf_ptr(struct tep_handle *tep)
+{
+ return tep->input_buf_ptr;
+}
+
+struct event_handler {
+ struct event_handler *next;
+ int id;
+ const char *sys_name;
+ const char *event_name;
+ tep_event_handler_func func;
+ void *context;
+};
+
+struct func_params {
+ struct func_params *next;
+ enum tep_func_arg_type type;
+};
+
+struct tep_function_handler {
+ struct tep_function_handler *next;
+ enum tep_func_arg_type ret_type;
+ char *name;
+ tep_func_handler func;
+ struct func_params *params;
+ int nr_args;
+};
+
+static unsigned long long
+process_defined_func(struct trace_seq *s, void *data, int size,
+ struct tep_event *event, struct tep_print_arg *arg);
+
+static void free_func_handle(struct tep_function_handler *func);
+
+void breakpoint(void)
+{
+ static int x;
+ x++;
+}
+
+static const char *get_event_type(enum tep_event_type type)
+{
+ switch (type) {
+ case TEP_EVENT_ERROR: return "ERROR";
+ case TEP_EVENT_NONE: return "NONE";
+ case TEP_EVENT_SPACE: return "SPACE";
+ case TEP_EVENT_NEWLINE: return "NEWLINE";
+ case TEP_EVENT_OP: return "OP";
+ case TEP_EVENT_DELIM: return "DELIM";
+ case TEP_EVENT_ITEM: return "ITEM";
+ case TEP_EVENT_DQUOTE: return "DQUOTE";
+ case TEP_EVENT_SQUOTE: return "SQUOTE";
+ }
+ return "(UNKNOWN)";
+}
+
+static struct tep_print_arg *alloc_arg(void)
+{
+ return calloc(1, sizeof(struct tep_print_arg));
+}
+
+struct tep_cmdline {
+ char *comm;
+ int pid;
+};
+
+static int cmdline_cmp(const void *a, const void *b)
+{
+ const struct tep_cmdline *ca = a;
+ const struct tep_cmdline *cb = b;
+
+ if (ca->pid < cb->pid)
+ return -1;
+ if (ca->pid > cb->pid)
+ return 1;
+
+ return 0;
+}
+
+/* Looking for where to place the key */
+static int cmdline_slot_cmp(const void *a, const void *b)
+{
+ const struct tep_cmdline *ca = a;
+ const struct tep_cmdline *cb = b;
+ const struct tep_cmdline *cb1 = cb + 1;
+
+ if (ca->pid < cb->pid)
+ return -1;
+
+ if (ca->pid > cb->pid) {
+ if (ca->pid <= cb1->pid)
+ return 0;
+ return 1;
+ }
+
+ return 0;
+}
+
+struct cmdline_list {
+ struct cmdline_list *next;
+ char *comm;
+ int pid;
+};
+
+static int cmdline_init(struct tep_handle *tep)
+{
+ struct cmdline_list *cmdlist = tep->cmdlist;
+ struct cmdline_list *item;
+ struct tep_cmdline *cmdlines;
+ int i;
+
+ cmdlines = malloc(sizeof(*cmdlines) * tep->cmdline_count);
+ if (!cmdlines)
+ return -1;
+
+ i = 0;
+ while (cmdlist) {
+ cmdlines[i].pid = cmdlist->pid;
+ cmdlines[i].comm = cmdlist->comm;
+ i++;
+ item = cmdlist;
+ cmdlist = cmdlist->next;
+ free(item);
+ }
+
+ qsort(cmdlines, tep->cmdline_count, sizeof(*cmdlines), cmdline_cmp);
+
+ tep->cmdlines = cmdlines;
+ tep->cmdlist = NULL;
+
+ return 0;
+}
+
+static const char *find_cmdline(struct tep_handle *tep, int pid)
+{
+ const struct tep_cmdline *comm;
+ struct tep_cmdline key;
+
+ if (!pid)
+ return "<idle>";
+
+ if (!tep->cmdlines && cmdline_init(tep))
+ return "<not enough memory for cmdlines!>";
+
+ key.pid = pid;
+
+ comm = bsearch(&key, tep->cmdlines, tep->cmdline_count,
+ sizeof(*tep->cmdlines), cmdline_cmp);
+
+ if (comm)
+ return comm->comm;
+ return "<...>";
+}
+
+/**
+ * tep_is_pid_registered - return if a pid has a cmdline registered
+ * @tep: a handle to the trace event parser context
+ * @pid: The pid to check if it has a cmdline registered with.
+ *
+ * Returns true if the pid has a cmdline mapped to it
+ * false otherwise.
+ */
+bool tep_is_pid_registered(struct tep_handle *tep, int pid)
+{
+ const struct tep_cmdline *comm;
+ struct tep_cmdline key;
+
+ if (!pid)
+ return true;
+
+ if (!tep->cmdlines && cmdline_init(tep))
+ return false;
+
+ key.pid = pid;
+
+ comm = bsearch(&key, tep->cmdlines, tep->cmdline_count,
+ sizeof(*tep->cmdlines), cmdline_cmp);
+
+ if (comm)
+ return true;
+ return false;
+}
+
+/*
+ * If the command lines have been converted to an array, then
+ * we must add this pid. This is much slower than when cmdlines
+ * are added before the array is initialized.
+ */
+static int add_new_comm(struct tep_handle *tep,
+ const char *comm, int pid, bool override)
+{
+ struct tep_cmdline *cmdlines = tep->cmdlines;
+ struct tep_cmdline *cmdline;
+ struct tep_cmdline key;
+ char *new_comm;
+ int cnt;
+
+ if (!pid)
+ return 0;
+
+ /* avoid duplicates */
+ key.pid = pid;
+
+ cmdline = bsearch(&key, tep->cmdlines, tep->cmdline_count,
+ sizeof(*tep->cmdlines), cmdline_cmp);
+ if (cmdline) {
+ if (!override) {
+ errno = EEXIST;
+ return -1;
+ }
+ new_comm = strdup(comm);
+ if (!new_comm) {
+ errno = ENOMEM;
+ return -1;
+ }
+ free(cmdline->comm);
+ cmdline->comm = new_comm;
+
+ return 0;
+ }
+
+ cmdlines = realloc(cmdlines, sizeof(*cmdlines) * (tep->cmdline_count + 1));
+ if (!cmdlines) {
+ errno = ENOMEM;
+ return -1;
+ }
+ tep->cmdlines = cmdlines;
+
+ key.comm = strdup(comm);
+ if (!key.comm) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (!tep->cmdline_count) {
+ /* no entries yet */
+ tep->cmdlines[0] = key;
+ tep->cmdline_count++;
+ return 0;
+ }
+
+ /* Now find where we want to store the new cmdline */
+ cmdline = bsearch(&key, tep->cmdlines, tep->cmdline_count - 1,
+ sizeof(*tep->cmdlines), cmdline_slot_cmp);
+
+ cnt = tep->cmdline_count;
+ if (cmdline) {
+ /* cmdline points to the one before the spot we want */
+ cmdline++;
+ cnt -= cmdline - tep->cmdlines;
+
+ } else {
+ /* The new entry is either before or after the list */
+ if (key.pid > tep->cmdlines[tep->cmdline_count - 1].pid) {
+ tep->cmdlines[tep->cmdline_count++] = key;
+ return 0;
+ }
+ cmdline = &tep->cmdlines[0];
+ }
+ memmove(cmdline + 1, cmdline, (cnt * sizeof(*cmdline)));
+ *cmdline = key;
+
+ tep->cmdline_count++;
+
+ return 0;
+}
+
+static int _tep_register_comm(struct tep_handle *tep,
+ const char *comm, int pid, bool override)
+{
+ struct cmdline_list *item;
+
+ if (tep->cmdlines)
+ return add_new_comm(tep, comm, pid, override);
+
+ item = malloc(sizeof(*item));
+ if (!item)
+ return -1;
+
+ if (comm)
+ item->comm = strdup(comm);
+ else
+ item->comm = strdup("<...>");
+ if (!item->comm) {
+ free(item);
+ return -1;
+ }
+ item->pid = pid;
+ item->next = tep->cmdlist;
+
+ tep->cmdlist = item;
+ tep->cmdline_count++;
+
+ return 0;
+}
+
+/**
+ * tep_register_comm - register a pid / comm mapping
+ * @tep: a handle to the trace event parser context
+ * @comm: the command line to register
+ * @pid: the pid to map the command line to
+ *
+ * This adds a mapping to search for command line names with
+ * a given pid. The comm is duplicated. If a command with the same pid
+ * already exist, -1 is returned and errno is set to EEXIST
+ */
+int tep_register_comm(struct tep_handle *tep, const char *comm, int pid)
+{
+ return _tep_register_comm(tep, comm, pid, false);
+}
+
+/**
+ * tep_override_comm - register a pid / comm mapping
+ * @tep: a handle to the trace event parser context
+ * @comm: the command line to register
+ * @pid: the pid to map the command line to
+ *
+ * This adds a mapping to search for command line names with
+ * a given pid. The comm is duplicated. If a command with the same pid
+ * already exist, the command string is udapted with the new one
+ */
+int tep_override_comm(struct tep_handle *tep, const char *comm, int pid)
+{
+ if (!tep->cmdlines && cmdline_init(tep)) {
+ errno = ENOMEM;
+ return -1;
+ }
+ return _tep_register_comm(tep, comm, pid, true);
+}
+
+/**
+ * tep_parse_saved_cmdlines - parse the comms from the saved_cmdlines file
+ * @tep: a handle to the trace event parser
+ * @buf: A string buffer that holds the content of saved_cmdlines and ends with '\0'
+ *
+ * This is a helper function to parse the comms in the tracefs saved_cmdlines
+ * file (stored in a string buffer) and load the comms into the @tep handler
+ * such that comm name matches an process ID (pid). This is used to show
+ * the names of the processes as the events only hold the pid.
+ *
+ * Returns 0 on success, and -1 on error.
+ */
+int tep_parse_saved_cmdlines(struct tep_handle *tep, const char *buf)
+{
+ char *copy;
+ char *comm;
+ char *line;
+ char *next = NULL;
+ int pid;
+ int ret = -1;
+ int n;
+
+ copy = strdup(buf);
+ if (!copy)
+ return -1;
+
+ line = strtok_r(copy, "\n", &next);
+ while (line) {
+ errno = 0;
+ n = sscanf(line, "%d %m[^\n]s", &pid, &comm);
+ if (errno || n != 2 || !comm)
+ goto out;
+ tep_register_comm(tep, comm, pid);
+ free(comm);
+ line = strtok_r(NULL, "\n", &next);
+ }
+ ret = 0;
+ out:
+ free(copy);
+ return ret;
+}
+
+struct func_map {
+ unsigned long long addr;
+ char *func;
+ char *mod;
+};
+
+struct func_list {
+ struct func_list *next;
+ unsigned long long addr;
+ char *func;
+ char *mod;
+};
+
+static int func_cmp(const void *a, const void *b)
+{
+ const struct func_map *fa = a;
+ const struct func_map *fb = b;
+
+ if (fa->addr < fb->addr)
+ return -1;
+ if (fa->addr > fb->addr)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * We are searching for a record in between, not an exact
+ * match.
+ */
+static int func_bcmp(const void *a, const void *b)
+{
+ const struct func_map *fa = a;
+ const struct func_map *fb = b;
+
+ if ((fa->addr == fb->addr) ||
+
+ (fa->addr > fb->addr &&
+ fa->addr < (fb+1)->addr))
+ return 0;
+
+ if (fa->addr < fb->addr)
+ return -1;
+
+ return 1;
+}
+
+static int func_map_init(struct tep_handle *tep)
+{
+ struct func_list *funclist;
+ struct func_list *item;
+ struct func_map *func_map;
+ int i;
+
+ func_map = malloc(sizeof(*func_map) * (tep->func_count + 1));
+ if (!func_map)
+ return -1;
+
+ funclist = tep->funclist;
+
+ i = 0;
+ while (funclist) {
+ func_map[i].func = funclist->func;
+ func_map[i].addr = funclist->addr;
+ func_map[i].mod = funclist->mod;
+ i++;
+ item = funclist;
+ funclist = funclist->next;
+ free(item);
+ }
+
+ qsort(func_map, tep->func_count, sizeof(*func_map), func_cmp);
+
+ /*
+ * Add a special record at the end.
+ */
+ func_map[tep->func_count].func = NULL;
+ func_map[tep->func_count].addr = 0;
+ func_map[tep->func_count].mod = NULL;
+
+ tep->func_map = func_map;
+ tep->funclist = NULL;
+
+ return 0;
+}
+
+static struct func_map *
+__find_func(struct tep_handle *tep, unsigned long long addr)
+{
+ struct func_map *func;
+ struct func_map key;
+
+ if (!tep->func_map)
+ func_map_init(tep);
+
+ key.addr = addr;
+
+ func = bsearch(&key, tep->func_map, tep->func_count,
+ sizeof(*tep->func_map), func_bcmp);
+
+ return func;
+}
+
+struct func_resolver {
+ tep_func_resolver_t *func;
+ void *priv;
+ struct func_map map;
+};
+
+/**
+ * tep_set_function_resolver - set an alternative function resolver
+ * @tep: a handle to the trace event parser context
+ * @resolver: function to be used
+ * @priv: resolver function private state.
+ *
+ * Some tools may have already a way to resolve kernel functions, allow them to
+ * keep using it instead of duplicating all the entries inside tep->funclist.
+ */
+int tep_set_function_resolver(struct tep_handle *tep,
+ tep_func_resolver_t *func, void *priv)
+{
+ struct func_resolver *resolver = malloc(sizeof(*resolver));
+
+ if (resolver == NULL)
+ return -1;
+
+ resolver->func = func;
+ resolver->priv = priv;
+
+ free(tep->func_resolver);
+ tep->func_resolver = resolver;
+
+ return 0;
+}
+
+/**
+ * tep_reset_function_resolver - reset alternative function resolver
+ * @tep: a handle to the trace event parser context
+ *
+ * Stop using whatever alternative resolver was set, use the default
+ * one instead.
+ */
+void tep_reset_function_resolver(struct tep_handle *tep)
+{
+ free(tep->func_resolver);
+ tep->func_resolver = NULL;
+}
+
+static struct func_map *
+find_func(struct tep_handle *tep, unsigned long long addr)
+{
+ struct func_map *map;
+
+ if (!tep->func_resolver)
+ return __find_func(tep, addr);
+
+ map = &tep->func_resolver->map;
+ map->mod = NULL;
+ map->addr = addr;
+ map->func = tep->func_resolver->func(tep->func_resolver->priv,
+ &map->addr, &map->mod);
+ if (map->func == NULL)
+ return NULL;
+
+ return map;
+}
+
+/**
+ * tep_find_function_info - find a function by a given address
+ * @tep: a handle to the trace event parser context
+ * @addr: the address to find the function with
+ * @name: Return the name of the function (if found)
+ * @start: Return the start of the function (if found)
+ * @size: Return the size of the function (if found)
+ *
+ * Returns 1 if found, and 0 if it is not.
+ * If found then @name will point to the name of the function.
+ * @start: will contain the starting address of the function.
+ * @size: will contain the size of the function.
+ */
+int tep_find_function_info(struct tep_handle *tep, unsigned long long addr,
+ const char **name, unsigned long long *start,
+ unsigned long *size)
+{
+ struct func_map *map;
+
+ map = find_func(tep, addr);
+ if (!map)
+ return 0;
+
+ if (name)
+ *name = map->func;
+ if (start)
+ *start = map->addr;
+ if (size) {
+ if (!tep->func_resolver)
+ *size = map[1].addr - map->addr;
+ else
+ *size = 0;
+ }
+
+ return 1;
+}
+
+/**
+ * tep_find_function - find a function by a given address
+ * @tep: a handle to the trace event parser context
+ * @addr: the address to find the function with
+ *
+ * Returns a pointer to the function stored that has the given
+ * address. Note, the address does not have to be exact, it
+ * will select the function that would contain the address.
+ */
+const char *tep_find_function(struct tep_handle *tep, unsigned long long addr)
+{
+ struct func_map *map;
+
+ map = find_func(tep, addr);
+ if (!map)
+ return NULL;
+
+ return map->func;
+}
+
+/**
+ * tep_find_function_address - find a function address by a given address
+ * @tep: a handle to the trace event parser context
+ * @addr: the address to find the function with
+ *
+ * Returns the address the function starts at. This can be used in
+ * conjunction with tep_find_function to print both the function
+ * name and the function offset.
+ */
+unsigned long long
+tep_find_function_address(struct tep_handle *tep, unsigned long long addr)
+{
+ struct func_map *map;
+
+ map = find_func(tep, addr);
+ if (!map)
+ return 0;
+
+ return map->addr;
+}
+
+/**
+ * tep_register_function - register a function with a given address
+ * @tep: a handle to the trace event parser context
+ * @function: the function name to register
+ * @addr: the address the function starts at
+ * @mod: the kernel module the function may be in (NULL for none)
+ *
+ * This registers a function name with an address and module.
+ * The @func passed in is duplicated.
+ */
+int tep_register_function(struct tep_handle *tep, char *func,
+ unsigned long long addr, char *mod)
+{
+ struct func_list *item = malloc(sizeof(*item));
+
+ if (!item)
+ return -1;
+
+ item->next = tep->funclist;
+ item->func = strdup(func);
+ if (!item->func)
+ goto out_free;
+
+ if (mod) {
+ item->mod = strdup(mod);
+ if (!item->mod)
+ goto out_free_func;
+ } else
+ item->mod = NULL;
+ item->addr = addr;
+
+ tep->funclist = item;
+ tep->func_count++;
+
+ return 0;
+
+out_free_func:
+ free(item->func);
+ item->func = NULL;
+out_free:
+ free(item);
+ errno = ENOMEM;
+ return -1;
+}
+
+/**
+ * tep_parse_kallsyms - load functions from a read of /proc/kallsyms
+ * @tep: a handle to the trace event parser
+ * @kallsyms: A string buffer that holds the content of /proc/kallsyms and ends with '\0'
+ *
+ * This is a helper function to parse the Linux kernel /proc/kallsyms
+ * format (stored in a string buffer) and load the functions into
+ * the @tep handler such that function IP addresses can be mapped to
+ * their name when parsing events with %pS in the print format field.
+ *
+ * Returns 0 on success, and -1 on error.
+ */
+int tep_parse_kallsyms(struct tep_handle *tep, const char *kallsyms)
+{
+ unsigned long long addr;
+ char *copy;
+ char *func;
+ char *line;
+ char *next = NULL;
+ char *mod;
+ char ch;
+ int ret = -1;
+
+ copy = strdup(kallsyms);
+ if (!copy)
+ return -1;
+
+ line = strtok_r(copy, "\n", &next);
+ while (line) {
+ int func_start, func_end = 0;
+ int mod_start, mod_end = 0;
+ int n;
+
+ mod = NULL;
+ errno = 0;
+ n = sscanf(line, "%16llx %c %n%*s%n%*1[\t][%n%*s%n",
+ &addr, &ch, &func_start, &func_end, &mod_start, &mod_end);
+ if (errno)
+ goto out;
+
+ if (n != 2 || !func_end) {
+ tep_warning("Failed to parse kallsyms n=%d func_end=%d",
+ n, func_end);
+ goto out;
+ }
+
+ func = line + func_start;
+ /*
+ * Hacks for
+ * - arm arch that adds a lot of bogus '$a' functions
+ * - x86-64 that reports per-cpu variable offsets as absolute
+ */
+ if (func[0] != '$' && ch != 'A' && ch != 'a') {
+ line[func_end] = 0;
+ if (mod_end) {
+ mod = line + mod_start;
+ /* truncate the extra ']' */
+ line[mod_end - 1] = 0;
+ }
+ tep_register_function(tep, func, addr, mod);
+ }
+
+ line = strtok_r(NULL, "\n", &next);
+ }
+ free(line);
+ ret = 0;
+ out:
+ free(copy);
+
+ return ret;
+}
+
+/**
+ * tep_print_funcs - print out the stored functions
+ * @tep: a handle to the trace event parser context
+ *
+ * This prints out the stored functions.
+ */
+void tep_print_funcs(struct tep_handle *tep)
+{
+ int i;
+
+ if (!tep->func_map)
+ func_map_init(tep);
+
+ for (i = 0; i < (int)tep->func_count; i++) {
+ printf("%016llx %s",
+ tep->func_map[i].addr,
+ tep->func_map[i].func);
+ if (tep->func_map[i].mod)
+ printf(" [%s]\n", tep->func_map[i].mod);
+ else
+ printf("\n");
+ }
+}
+
+struct printk_map {
+ unsigned long long addr;
+ char *printk;
+};
+
+struct printk_list {
+ struct printk_list *next;
+ unsigned long long addr;
+ char *printk;
+};
+
+static int printk_cmp(const void *a, const void *b)
+{
+ const struct printk_map *pa = a;
+ const struct printk_map *pb = b;
+
+ if (pa->addr < pb->addr)
+ return -1;
+ if (pa->addr > pb->addr)
+ return 1;
+
+ return 0;
+}
+
+static int printk_map_init(struct tep_handle *tep)
+{
+ struct printk_list *printklist;
+ struct printk_list *item;
+ struct printk_map *printk_map;
+ int i;
+
+ printk_map = malloc(sizeof(*printk_map) * (tep->printk_count + 1));
+ if (!printk_map)
+ return -1;
+
+ printklist = tep->printklist;
+
+ i = 0;
+ while (printklist) {
+ printk_map[i].printk = printklist->printk;
+ printk_map[i].addr = printklist->addr;
+ i++;
+ item = printklist;
+ printklist = printklist->next;
+ free(item);
+ }
+
+ qsort(printk_map, tep->printk_count, sizeof(*printk_map), printk_cmp);
+
+ tep->printk_map = printk_map;
+ tep->printklist = NULL;
+
+ return 0;
+}
+
+static struct printk_map *
+find_printk(struct tep_handle *tep, unsigned long long addr)
+{
+ struct printk_map *printk;
+ struct printk_map key;
+
+ if (!tep->printk_map && printk_map_init(tep))
+ return NULL;
+
+ key.addr = addr;
+
+ printk = bsearch(&key, tep->printk_map, tep->printk_count,
+ sizeof(*tep->printk_map), printk_cmp);
+
+ return printk;
+}
+
+/**
+ * tep_register_print_string - register a string by its address
+ * @tep: a handle to the trace event parser context
+ * @fmt: the string format to register
+ * @addr: the address the string was located at
+ *
+ * This registers a string by the address it was stored in the kernel.
+ * The @fmt passed in is duplicated.
+ */
+int tep_register_print_string(struct tep_handle *tep, const char *fmt,
+ unsigned long long addr)
+{
+ struct printk_list *item = malloc(sizeof(*item));
+ char *p;
+
+ if (!item)
+ return -1;
+
+ item->next = tep->printklist;
+ item->addr = addr;
+
+ /* Strip off quotes and '\n' from the end */
+ if (fmt[0] == '"')
+ fmt++;
+ item->printk = strdup(fmt);
+ if (!item->printk)
+ goto out_free;
+
+ p = item->printk + strlen(item->printk) - 1;
+ if (*p == '"')
+ *p = 0;
+
+ p -= 2;
+ if (strcmp(p, "\\n") == 0)
+ *p = 0;
+
+ tep->printklist = item;
+ tep->printk_count++;
+
+ return 0;
+
+out_free:
+ free(item);
+ errno = ENOMEM;
+ return -1;
+}
+
+/**
+ * tep_print_printk - print out the stored strings
+ * @tep: a handle to the trace event parser context
+ *
+ * This prints the string formats that were stored.
+ */
+void tep_print_printk(struct tep_handle *tep)
+{
+ int i;
+
+ if (!tep->printk_map)
+ printk_map_init(tep);
+
+ for (i = 0; i < (int)tep->printk_count; i++) {
+ printf("%016llx %s\n",
+ tep->printk_map[i].addr,
+ tep->printk_map[i].printk);
+ }
+}
+
+/**
+ * tep_parse_printk_formats - Parse the address to strings
+ * @tep: a handle to the trace event parser
+ * @buf: A string buffer that holds the content of printk_formats and ends with '\0'
+ *
+ * This is a helper function to parse the address to printk formats in
+ * the kernel. Some events use %s to a kernel address that holds a constant
+ * string. The printk_formats file has a mapping of these addresses to the
+ * strings that are in the kernel. This parses the content of that file
+ * and registers those strings and their addresses so that the parsing of
+ * events can display the string as the event only has the address of the string.
+ *
+ * Returns 0 on success, and -1 on error.
+ */
+int tep_parse_printk_formats(struct tep_handle *tep, const char *buf)
+{
+ unsigned long long addr;
+ char *addr_str;
+ char *printk;
+ char *copy;
+ char *line;
+ char *next;
+ char *fmt;
+ int ret = -1;
+
+ copy = strdup(buf);
+ if (!copy)
+ return -1;
+
+ line = strtok_r(copy, "\n", &next);
+ while (line) {
+ addr_str = strtok_r(line, ":", &fmt);
+ if (!addr_str) {
+ tep_warning("printk format with empty entry");
+ break;
+ }
+ addr = strtoull(addr_str, NULL, 16);
+ /* fmt still has a space, skip it */
+ printk = strdup(fmt+1);
+ if (!printk)
+ goto out;
+ line = strtok_r(NULL, "\n", &next);
+ tep_register_print_string(tep, printk, addr);
+ free(printk);
+ }
+ ret = 0;
+ out:
+ free(copy);
+ return ret;
+}
+
+static struct tep_event *alloc_event(void)
+{
+ return calloc(1, sizeof(struct tep_event));
+}
+
+static int add_event(struct tep_handle *tep, struct tep_event *event)
+{
+ int i;
+ struct tep_event **events = realloc(tep->events, sizeof(event) *
+ (tep->nr_events + 1));
+ if (!events)
+ return -1;
+
+ tep->events = events;
+
+ for (i = 0; i < tep->nr_events; i++) {
+ if (tep->events[i]->id > event->id)
+ break;
+ }
+ if (i < tep->nr_events)
+ memmove(&tep->events[i + 1],
+ &tep->events[i],
+ sizeof(event) * (tep->nr_events - i));
+
+ tep->events[i] = event;
+ tep->nr_events++;
+
+ event->tep = tep;
+
+ return 0;
+}
+
+static int event_item_type(enum tep_event_type type)
+{
+ switch (type) {
+ case TEP_EVENT_ITEM ... TEP_EVENT_SQUOTE:
+ return 1;
+ case TEP_EVENT_ERROR ... TEP_EVENT_DELIM:
+ default:
+ return 0;
+ }
+}
+
+static void free_flag_sym(struct tep_print_flag_sym *fsym)
+{
+ struct tep_print_flag_sym *next;
+
+ while (fsym) {
+ next = fsym->next;
+ free(fsym->value);
+ free(fsym->str);
+ free(fsym);
+ fsym = next;
+ }
+}
+
+static void free_arg(struct tep_print_arg *arg)
+{
+ struct tep_print_arg *farg;
+
+ if (!arg)
+ return;
+
+ switch (arg->type) {
+ case TEP_PRINT_ATOM:
+ free(arg->atom.atom);
+ break;
+ case TEP_PRINT_FIELD:
+ free(arg->field.name);
+ break;
+ case TEP_PRINT_FLAGS:
+ free_arg(arg->flags.field);
+ free(arg->flags.delim);
+ free_flag_sym(arg->flags.flags);
+ break;
+ case TEP_PRINT_SYMBOL:
+ free_arg(arg->symbol.field);
+ free_flag_sym(arg->symbol.symbols);
+ break;
+ case TEP_PRINT_HEX:
+ case TEP_PRINT_HEX_STR:
+ free_arg(arg->hex.field);
+ free_arg(arg->hex.size);
+ break;
+ case TEP_PRINT_INT_ARRAY:
+ free_arg(arg->int_array.field);
+ free_arg(arg->int_array.count);
+ free_arg(arg->int_array.el_size);
+ break;
+ case TEP_PRINT_TYPE:
+ free(arg->typecast.type);
+ free_arg(arg->typecast.item);
+ break;
+ case TEP_PRINT_STRING:
+ case TEP_PRINT_BSTRING:
+ free(arg->string.string);
+ break;
+ case TEP_PRINT_BITMASK:
+ case TEP_PRINT_CPUMASK:
+ free(arg->bitmask.bitmask);
+ break;
+ case TEP_PRINT_DYNAMIC_ARRAY:
+ case TEP_PRINT_DYNAMIC_ARRAY_LEN:
+ free(arg->dynarray.index);
+ break;
+ case TEP_PRINT_OP:
+ free(arg->op.op);
+ free_arg(arg->op.left);
+ free_arg(arg->op.right);
+ break;
+ case TEP_PRINT_FUNC:
+ while (arg->func.args) {
+ farg = arg->func.args;
+ arg->func.args = farg->next;
+ free_arg(farg);
+ }
+ break;
+
+ case TEP_PRINT_NULL:
+ default:
+ break;
+ }
+
+ free(arg);
+}
+
+static enum tep_event_type get_type(int ch)
+{
+ if (ch == '\n')
+ return TEP_EVENT_NEWLINE;
+ if (isspace(ch))
+ return TEP_EVENT_SPACE;
+ if (isalnum(ch) || ch == '_')
+ return TEP_EVENT_ITEM;
+ if (ch == '\'')
+ return TEP_EVENT_SQUOTE;
+ if (ch == '"')
+ return TEP_EVENT_DQUOTE;
+ if (!isprint(ch))
+ return TEP_EVENT_NONE;
+ if (ch == '(' || ch == ')' || ch == ',')
+ return TEP_EVENT_DELIM;
+
+ return TEP_EVENT_OP;
+}
+
+static int __read_char(struct tep_handle *tep)
+{
+ if (tep->input_buf_ptr >= tep->input_buf_siz)
+ return -1;
+
+ return tep->input_buf[tep->input_buf_ptr++];
+}
+
+/**
+ * peek_char - peek at the next character that will be read
+ *
+ * Returns the next character read, or -1 if end of buffer.
+ */
+__hidden int peek_char(struct tep_handle *tep)
+{
+ if (tep->input_buf_ptr >= tep->input_buf_siz)
+ return -1;
+
+ return tep->input_buf[tep->input_buf_ptr];
+}
+
+static int extend_token(char **tok, char *buf, int size)
+{
+ char *newtok = realloc(*tok, size);
+
+ if (!newtok) {
+ free(*tok);
+ *tok = NULL;
+ return -1;
+ }
+
+ if (!*tok)
+ strcpy(newtok, buf);
+ else
+ strcat(newtok, buf);
+ *tok = newtok;
+
+ return 0;
+}
+
+static enum tep_event_type force_token(struct tep_handle *tep, const char *str,
+ char **tok);
+
+static enum tep_event_type __read_token(struct tep_handle *tep, char **tok)
+{
+ char buf[BUFSIZ];
+ int ch, last_ch, quote_ch, next_ch;
+ int i = 0;
+ int tok_size = 0;
+ enum tep_event_type type;
+
+ *tok = NULL;
+
+
+ ch = __read_char(tep);
+ if (ch < 0)
+ return TEP_EVENT_NONE;
+
+ type = get_type(ch);
+ if (type == TEP_EVENT_NONE)
+ return type;
+
+ buf[i++] = ch;
+
+ switch (type) {
+ case TEP_EVENT_NEWLINE:
+ case TEP_EVENT_DELIM:
+ if (asprintf(tok, "%c", ch) < 0)
+ return TEP_EVENT_ERROR;
+
+ return type;
+
+ case TEP_EVENT_OP:
+ switch (ch) {
+ case '-':
+ next_ch = peek_char(tep);
+ if (next_ch == '>') {
+ buf[i++] = __read_char(tep);
+ break;
+ }
+ /* fall through */
+ case '+':
+ case '|':
+ case '&':
+ case '>':
+ case '<':
+ last_ch = ch;
+ ch = peek_char(tep);
+ if (ch != last_ch)
+ goto test_equal;
+ buf[i++] = __read_char(tep);
+ switch (last_ch) {
+ case '>':
+ case '<':
+ goto test_equal;
+ default:
+ break;
+ }
+ break;
+ case '!':
+ case '=':
+ goto test_equal;
+ default: /* what should we do instead? */
+ break;
+ }
+ buf[i] = 0;
+ *tok = strdup(buf);
+ return type;
+
+ test_equal:
+ ch = peek_char(tep);
+ if (ch == '=')
+ buf[i++] = __read_char(tep);
+ goto out;
+
+ case TEP_EVENT_DQUOTE:
+ case TEP_EVENT_SQUOTE:
+ /* don't keep quotes */
+ i--;
+ quote_ch = ch;
+ last_ch = 0;
+ concat:
+ do {
+ if (i == (BUFSIZ - 1)) {
+ buf[i] = 0;
+ tok_size += BUFSIZ;
+
+ if (extend_token(tok, buf, tok_size) < 0)
+ return TEP_EVENT_NONE;
+ i = 0;
+ }
+ last_ch = ch;
+ ch = __read_char(tep);
+ buf[i++] = ch;
+ /* the '\' '\' will cancel itself */
+ if (ch == '\\' && last_ch == '\\')
+ last_ch = 0;
+ /* Break out if the file is corrupted and giving non print chars */
+ if (ch <= 0)
+ break;
+ } while ((ch != quote_ch && isprint(ch)) || last_ch == '\\' || ch == '\n');
+ /* remove the last quote */
+ i--;
+
+ if (ch <= 0)
+ type = TEP_EVENT_NONE;
+
+ /*
+ * For strings (double quotes) check the next token.
+ * If it is another string, concatinate the two.
+ */
+ if (type == TEP_EVENT_DQUOTE) {
+ unsigned long long save_input_buf_ptr = tep->input_buf_ptr;
+
+ do {
+ ch = __read_char(tep);
+ } while (isspace(ch));
+ if (ch == '"')
+ goto concat;
+ tep->input_buf_ptr = save_input_buf_ptr;
+ }
+
+ goto out;
+
+ case TEP_EVENT_ERROR ... TEP_EVENT_SPACE:
+ case TEP_EVENT_ITEM:
+ default:
+ break;
+ }
+
+ while (get_type(peek_char(tep)) == type) {
+ if (i == (BUFSIZ - 1)) {
+ buf[i] = 0;
+ tok_size += BUFSIZ;
+
+ if (extend_token(tok, buf, tok_size) < 0)
+ return TEP_EVENT_NONE;
+ i = 0;
+ }
+ ch = __read_char(tep);
+ buf[i++] = ch;
+ }
+
+ out:
+ buf[i] = 0;
+ if (extend_token(tok, buf, tok_size + i + 1) < 0)
+ return TEP_EVENT_NONE;
+
+ if (type == TEP_EVENT_ITEM) {
+ /*
+ * Older versions of the kernel has a bug that
+ * creates invalid symbols and will break the mac80211
+ * parsing. This is a work around to that bug.
+ *
+ * See Linux kernel commit:
+ * 811cb50baf63461ce0bdb234927046131fc7fa8b
+ */
+ if (strcmp(*tok, "LOCAL_PR_FMT") == 0) {
+ free(*tok);
+ *tok = NULL;
+ return force_token(tep, "\"%s\" ", tok);
+ } else if (strcmp(*tok, "STA_PR_FMT") == 0) {
+ free(*tok);
+ *tok = NULL;
+ return force_token(tep, "\" sta:%pM\" ", tok);
+ } else if (strcmp(*tok, "VIF_PR_FMT") == 0) {
+ free(*tok);
+ *tok = NULL;
+ return force_token(tep, "\" vif:%p(%d)\" ", tok);
+ }
+ }
+
+ return type;
+}
+
+static enum tep_event_type force_token(struct tep_handle *tep, const char *str,
+ char **tok)
+{
+ const char *save_input_buf;
+ unsigned long long save_input_buf_ptr;
+ unsigned long long save_input_buf_siz;
+ enum tep_event_type type;
+
+ /* save off the current input pointers */
+ save_input_buf = tep->input_buf;
+ save_input_buf_ptr = tep->input_buf_ptr;
+ save_input_buf_siz = tep->input_buf_siz;
+
+ init_input_buf(tep, str, strlen(str));
+
+ type = __read_token(tep, tok);
+
+ /* reset back to original token */
+ tep->input_buf = save_input_buf;
+ tep->input_buf_ptr = save_input_buf_ptr;
+ tep->input_buf_siz = save_input_buf_siz;
+
+ return type;
+}
+
+/**
+ * free_token - free a token returned by tep_read_token
+ * @token: the token to free
+ */
+__hidden void free_token(char *tok)
+{
+ if (tok)
+ free(tok);
+}
+
+/**
+ * read_token - access to utilities to use the tep parser
+ * @tok: The token to return
+ *
+ * This will parse tokens from the string given by
+ * tep_init_data().
+ *
+ * Returns the token type.
+ */
+__hidden enum tep_event_type read_token(struct tep_handle *tep, char **tok)
+{
+ enum tep_event_type type;
+
+ for (;;) {
+ type = __read_token(tep, tok);
+ if (type != TEP_EVENT_SPACE)
+ return type;
+
+ free_token(*tok);
+ }
+
+ /* not reached */
+ *tok = NULL;
+ return TEP_EVENT_NONE;
+}
+
+/* no newline */
+static enum tep_event_type read_token_item(struct tep_handle *tep, char **tok)
+{
+ enum tep_event_type type;
+
+ for (;;) {
+ type = __read_token(tep, tok);
+ if (type != TEP_EVENT_SPACE && type != TEP_EVENT_NEWLINE)
+ return type;
+ free_token(*tok);
+ *tok = NULL;
+ }
+
+ /* not reached */
+ *tok = NULL;
+ return TEP_EVENT_NONE;
+}
+
+static int test_type(enum tep_event_type type, enum tep_event_type expect)
+{
+ if (type != expect) {
+ do_warning("Error: expected type %d (%s) but read %d (%s)",
+ expect, get_event_type(expect),
+ type, get_event_type(type));
+ return -1;
+ }
+ return 0;
+}
+
+static int test_type_token(enum tep_event_type type, const char *token,
+ enum tep_event_type expect, const char *expect_tok)
+{
+ if (type != expect) {
+ do_warning("Error: expected type %d (%s) but read %d (%s)",
+ expect, get_event_type(expect),
+ type, get_event_type(type));
+ return -1;
+ }
+
+ if (strcmp(token, expect_tok) != 0) {
+ do_warning("Error: expected '%s' but read '%s'",
+ expect_tok, token);
+ return -1;
+ }
+ return 0;
+}
+
+static int __read_expect_type(struct tep_handle *tep, enum tep_event_type expect,
+ char **tok, int newline_ok)
+{
+ enum tep_event_type type;
+
+ if (newline_ok)
+ type = read_token(tep, tok);
+ else
+ type = read_token_item(tep, tok);
+ return test_type(type, expect);
+}
+
+static int read_expect_type(struct tep_handle *tep, enum tep_event_type expect,
+ char **tok)
+{
+ return __read_expect_type(tep, expect, tok, 1);
+}
+
+static int __read_expected(struct tep_handle *tep, enum tep_event_type expect,
+ const char *str, int newline_ok)
+{
+ enum tep_event_type type;
+ char *token;
+ int ret;
+
+ if (newline_ok)
+ type = read_token(tep, &token);
+ else
+ type = read_token_item(tep, &token);
+
+ ret = test_type_token(type, token, expect, str);
+
+ free_token(token);
+
+ return ret;
+}
+
+static int read_expected(struct tep_handle *tep, enum tep_event_type expect,
+ const char *str)
+{
+ return __read_expected(tep, expect, str, 1);
+}
+
+static int read_expected_item(struct tep_handle *tep, enum tep_event_type expect,
+ const char *str)
+{
+ return __read_expected(tep, expect, str, 0);
+}
+
+static char *event_read_name(struct tep_handle *tep)
+{
+ char *token;
+
+ if (read_expected(tep, TEP_EVENT_ITEM, "name") < 0)
+ return NULL;
+
+ if (read_expected(tep, TEP_EVENT_OP, ":") < 0)
+ return NULL;
+
+ if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0)
+ goto fail;
+
+ return token;
+
+ fail:
+ free_token(token);
+ return NULL;
+}
+
+static int event_read_id(struct tep_handle *tep)
+{
+ char *token;
+ int id;
+
+ if (read_expected_item(tep, TEP_EVENT_ITEM, "ID") < 0)
+ return -1;
+
+ if (read_expected(tep, TEP_EVENT_OP, ":") < 0)
+ return -1;
+
+ if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0)
+ goto fail;
+
+ id = strtoul(token, NULL, 0);
+ free_token(token);
+ return id;
+
+ fail:
+ free_token(token);
+ return -1;
+}
+
+static int field_is_string(struct tep_format_field *field)
+{
+ if ((field->flags & TEP_FIELD_IS_ARRAY) &&
+ (strstr(field->type, "char") || strstr(field->type, "u8") ||
+ strstr(field->type, "s8")))
+ return 1;
+
+ return 0;
+}
+
+static int field_is_dynamic(struct tep_format_field *field)
+{
+ if (strncmp(field->type, "__data_loc", 10) == 0)
+ return 1;
+
+ return 0;
+}
+
+static int field_is_relative_dynamic(struct tep_format_field *field)
+{
+ if (strncmp(field->type, "__rel_loc", 9) == 0)
+ return 1;
+
+ return 0;
+}
+
+static int field_is_long(struct tep_format_field *field)
+{
+ /* includes long long */
+ if (strstr(field->type, "long"))
+ return 1;
+
+ return 0;
+}
+
+static unsigned int type_size(const char *name)
+{
+ /* This covers all TEP_FIELD_IS_STRING types. */
+ static struct {
+ const char *type;
+ unsigned int size;
+ } table[] = {
+ { "u8", 1 },
+ { "u16", 2 },
+ { "u32", 4 },
+ { "u64", 8 },
+ { "s8", 1 },
+ { "s16", 2 },
+ { "s32", 4 },
+ { "s64", 8 },
+ { "char", 1 },
+ { },
+ };
+ int i;
+
+ for (i = 0; table[i].type; i++) {
+ if (!strcmp(table[i].type, name))
+ return table[i].size;
+ }
+
+ return 0;
+}
+
+static int append(char **buf, const char *delim, const char *str)
+{
+ char *new_buf;
+
+ new_buf = realloc(*buf, strlen(*buf) + strlen(delim) + strlen(str) + 1);
+ if (!new_buf)
+ return -1;
+ strcat(new_buf, delim);
+ strcat(new_buf, str);
+ *buf = new_buf;
+ return 0;
+}
+
+static int event_read_fields(struct tep_handle *tep, struct tep_event *event,
+ struct tep_format_field **fields)
+{
+ struct tep_format_field *field = NULL;
+ enum tep_event_type type;
+ char *token;
+ char *last_token;
+ char *delim = " ";
+ int count = 0;
+ int ret;
+
+ do {
+ unsigned int size_dynamic = 0;
+
+ type = read_token(tep, &token);
+ if (type == TEP_EVENT_NEWLINE) {
+ free_token(token);
+ return count;
+ }
+
+ count++;
+
+ if (test_type_token(type, token, TEP_EVENT_ITEM, "field"))
+ goto fail;
+ free_token(token);
+
+ type = read_token(tep, &token);
+ /*
+ * The ftrace fields may still use the "special" name.
+ * Just ignore it.
+ */
+ if (event->flags & TEP_EVENT_FL_ISFTRACE &&
+ type == TEP_EVENT_ITEM && strcmp(token, "special") == 0) {
+ free_token(token);
+ type = read_token(tep, &token);
+ }
+
+ if (test_type_token(type, token, TEP_EVENT_OP, ":") < 0)
+ goto fail;
+
+ free_token(token);
+ if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0)
+ goto fail;
+
+ last_token = token;
+
+ field = calloc(1, sizeof(*field));
+ if (!field)
+ goto fail;
+
+ field->event = event;
+
+ /* read the rest of the type */
+ for (;;) {
+ type = read_token(tep, &token);
+ if (type == TEP_EVENT_ITEM ||
+ (type == TEP_EVENT_OP && strcmp(token, "*") == 0) ||
+ /*
+ * Some of the ftrace fields are broken and have
+ * an illegal "." in them.
+ */
+ (event->flags & TEP_EVENT_FL_ISFTRACE &&
+ type == TEP_EVENT_OP && strcmp(token, ".") == 0)) {
+
+ if (strcmp(token, "*") == 0)
+ field->flags |= TEP_FIELD_IS_POINTER;
+
+ if (field->type) {
+ ret = append(&field->type, delim, last_token);
+ free(last_token);
+ if (ret < 0)
+ goto fail;
+ } else
+ field->type = last_token;
+ last_token = token;
+ delim = " ";
+ continue;
+ }
+
+ /* Handle __attribute__((user)) */
+ if ((type == TEP_EVENT_DELIM) &&
+ strcmp("__attribute__", last_token) == 0 &&
+ token[0] == '(') {
+ int depth = 1;
+ int ret;
+
+ ret = append(&field->type, " ", last_token);
+ ret |= append(&field->type, "", "(");
+ if (ret < 0)
+ goto fail;
+
+ delim = " ";
+ while ((type = read_token(tep, &token)) != TEP_EVENT_NONE) {
+ if (type == TEP_EVENT_DELIM) {
+ if (token[0] == '(')
+ depth++;
+ else if (token[0] == ')')
+ depth--;
+ if (!depth)
+ break;
+ ret = append(&field->type, "", token);
+ delim = "";
+ } else {
+ ret = append(&field->type, delim, token);
+ delim = " ";
+ }
+ if (ret < 0)
+ goto fail;
+ free(last_token);
+ last_token = token;
+ }
+ continue;
+ }
+ break;
+ }
+
+ if (!field->type) {
+ do_warning_event(event, "%s: no type found", __func__);
+ goto fail;
+ }
+ field->name = field->alias = last_token;
+
+ if (test_type(type, TEP_EVENT_OP))
+ goto fail;
+
+ if (strcmp(token, "[") == 0) {
+ enum tep_event_type last_type = type;
+ char *brackets = token;
+
+ field->flags |= TEP_FIELD_IS_ARRAY;
+
+ type = read_token(tep, &token);
+
+ if (type == TEP_EVENT_ITEM)
+ field->arraylen = strtoul(token, NULL, 0);
+ else
+ field->arraylen = 0;
+
+ while (strcmp(token, "]") != 0) {
+ const char *delim;
+
+ if (last_type == TEP_EVENT_ITEM &&
+ type == TEP_EVENT_ITEM)
+ delim = " ";
+ else
+ delim = "";
+
+ last_type = type;
+
+ ret = append(&brackets, delim, token);
+ if (ret < 0) {
+ free(brackets);
+ goto fail;
+ }
+ /* We only care about the last token */
+ field->arraylen = strtoul(token, NULL, 0);
+ free_token(token);
+ type = read_token(tep, &token);
+ if (type == TEP_EVENT_NONE) {
+ free(brackets);
+ do_warning_event(event, "failed to find token");
+ goto fail;
+ }
+ }
+
+ free_token(token);
+
+ ret = append(&brackets, "", "]");
+ if (ret < 0) {
+ free(brackets);
+ goto fail_expect;
+ }
+
+ /* add brackets to type */
+
+ type = read_token(tep, &token);
+ /*
+ * If the next token is not an OP, then it is of
+ * the format: type [] item;
+ */
+ if (type == TEP_EVENT_ITEM) {
+ ret = append(&field->type, " ", field->name);
+ if (ret < 0) {
+ free(brackets);
+ goto fail;
+ }
+ ret = append(&field->type, "", brackets);
+
+ size_dynamic = type_size(field->name);
+ free_token(field->name);
+ field->name = field->alias = token;
+ type = read_token(tep, &token);
+ } else {
+ ret = append(&field->type, "", brackets);
+ if (ret < 0) {
+ free(brackets);
+ goto fail;
+ }
+ }
+ free(brackets);
+ }
+
+ if (field_is_string(field))
+ field->flags |= TEP_FIELD_IS_STRING;
+ if (field_is_dynamic(field))
+ field->flags |= TEP_FIELD_IS_DYNAMIC;
+ if (field_is_relative_dynamic(field))
+ field->flags |= TEP_FIELD_IS_DYNAMIC | TEP_FIELD_IS_RELATIVE;
+ if (field_is_long(field))
+ field->flags |= TEP_FIELD_IS_LONG;
+
+ if (test_type_token(type, token, TEP_EVENT_OP, ";"))
+ goto fail;
+ free_token(token);
+
+ if (read_expected(tep, TEP_EVENT_ITEM, "offset") < 0)
+ goto fail_expect;
+
+ if (read_expected(tep, TEP_EVENT_OP, ":") < 0)
+ goto fail_expect;
+
+ if (read_expect_type(tep, TEP_EVENT_ITEM, &token))
+ goto fail;
+ field->offset = strtoul(token, NULL, 0);
+ free_token(token);
+
+ if (read_expected(tep, TEP_EVENT_OP, ";") < 0)
+ goto fail_expect;
+
+ if (read_expected(tep, TEP_EVENT_ITEM, "size") < 0)
+ goto fail_expect;
+
+ if (read_expected(tep, TEP_EVENT_OP, ":") < 0)
+ goto fail_expect;
+
+ if (read_expect_type(tep, TEP_EVENT_ITEM, &token))
+ goto fail;
+ field->size = strtoul(token, NULL, 0);
+ free_token(token);
+
+ /*
+ * The old data format before dynamic arrays had dynamic
+ * strings defined with just a 2 byte offset (the length
+ * is defined by the strlen() of the string. To process them
+ * correctly, check if the field is dynamic and has a size of
+ * 2 bytes. All current dynamic events have a size of 4.
+ */
+ if ((field->flags & TEP_FIELD_IS_DYNAMIC) && field->size == 2)
+ field->flags |= TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY;
+
+ if (read_expected(tep, TEP_EVENT_OP, ";") < 0)
+ goto fail_expect;
+
+ type = read_token(tep, &token);
+ if (type != TEP_EVENT_NEWLINE) {
+ /* newer versions of the kernel have a "signed" type */
+ if (test_type_token(type, token, TEP_EVENT_ITEM, "signed"))
+ goto fail;
+
+ free_token(token);
+
+ if (read_expected(tep, TEP_EVENT_OP, ":") < 0)
+ goto fail_expect;
+
+ if (read_expect_type(tep, TEP_EVENT_ITEM, &token))
+ goto fail;
+
+ if (strtoul(token, NULL, 0))
+ field->flags |= TEP_FIELD_IS_SIGNED;
+
+ free_token(token);
+ if (read_expected(tep, TEP_EVENT_OP, ";") < 0)
+ goto fail_expect;
+
+ if (read_expect_type(tep, TEP_EVENT_NEWLINE, &token))
+ goto fail;
+ }
+
+ free_token(token);
+
+ if (field->flags & (TEP_FIELD_IS_ARRAY | TEP_FIELD_IS_DYNAMIC)) {
+ if (field->arraylen)
+ field->elementsize = field->size / field->arraylen;
+ else if (field->flags & TEP_FIELD_IS_DYNAMIC)
+ field->elementsize = size_dynamic;
+ else if (field->flags & TEP_FIELD_IS_STRING)
+ field->elementsize = 1;
+ else if (field->flags & TEP_FIELD_IS_LONG)
+ field->elementsize = event->tep ?
+ event->tep->long_size :
+ sizeof(long);
+ } else
+ field->elementsize = field->size;
+
+ *fields = field;
+ fields = &field->next;
+ field = NULL;
+
+ } while (1);
+
+ return 0;
+
+fail:
+ free_token(token);
+fail_expect:
+ if (field) {
+ free(field->type);
+ free(field->name);
+ free(field);
+ }
+ return -1;
+}
+
+static int event_read_format(struct tep_event *event)
+{
+ char *token;
+ int ret;
+
+ if (read_expected_item(event->tep, TEP_EVENT_ITEM, "format") < 0)
+ return -1;
+
+ if (read_expected(event->tep, TEP_EVENT_OP, ":") < 0)
+ return -1;
+
+ if (read_expect_type(event->tep, TEP_EVENT_NEWLINE, &token))
+ goto fail;
+ free_token(token);
+
+ ret = event_read_fields(event->tep, event, &event->format.common_fields);
+ if (ret < 0)
+ return ret;
+ event->format.nr_common = ret;
+
+ ret = event_read_fields(event->tep, event, &event->format.fields);
+ if (ret < 0)
+ return ret;
+ event->format.nr_fields = ret;
+
+ return 0;
+
+ fail:
+ free_token(token);
+ return -1;
+}
+
+static enum tep_event_type
+process_arg_token(struct tep_event *event, struct tep_print_arg *arg,
+ char **tok, enum tep_event_type type);
+
+static enum tep_event_type
+process_arg(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ enum tep_event_type type;
+ char *token;
+
+ type = read_token(event->tep, &token);
+ *tok = token;
+
+ return process_arg_token(event, arg, tok, type);
+}
+
+static enum tep_event_type
+process_op(struct tep_event *event, struct tep_print_arg *arg, char **tok);
+
+/*
+ * For __print_symbolic() and __print_flags, we need to completely
+ * evaluate the first argument, which defines what to print next.
+ */
+static enum tep_event_type
+process_field_arg(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ enum tep_event_type type;
+
+ type = process_arg(event, arg, tok);
+
+ while (type == TEP_EVENT_OP) {
+ type = process_op(event, arg, tok);
+ }
+
+ return type;
+}
+
+static enum tep_event_type
+process_cond(struct tep_event *event, struct tep_print_arg *top, char **tok)
+{
+ struct tep_print_arg *arg, *left, *right;
+ enum tep_event_type type;
+ char *token = NULL;
+
+ arg = alloc_arg();
+ left = alloc_arg();
+ right = alloc_arg();
+
+ if (!arg || !left || !right) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ /* arg will be freed at out_free */
+ free_arg(left);
+ free_arg(right);
+ goto out_free;
+ }
+
+ arg->type = TEP_PRINT_OP;
+ arg->op.left = left;
+ arg->op.right = right;
+
+ *tok = NULL;
+ type = process_arg(event, left, &token);
+
+ again:
+ if (type == TEP_EVENT_ERROR)
+ goto out_free;
+
+ /* Handle other operations in the arguments */
+ if (type == TEP_EVENT_OP && strcmp(token, ":") != 0) {
+ type = process_op(event, left, &token);
+ goto again;
+ }
+
+ if (test_type_token(type, token, TEP_EVENT_OP, ":"))
+ goto out_free;
+
+ arg->op.op = token;
+
+ type = process_arg(event, right, &token);
+
+ top->op.right = arg;
+
+ *tok = token;
+ return type;
+
+out_free:
+ /* Top may point to itself */
+ top->op.right = NULL;
+ free_token(token);
+ free_arg(arg);
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_array(struct tep_event *event, struct tep_print_arg *top, char **tok)
+{
+ struct tep_print_arg *arg;
+ enum tep_event_type type;
+ char *token = NULL;
+
+ arg = alloc_arg();
+ if (!arg) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ /* '*tok' is set to top->op.op. No need to free. */
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+ }
+
+ *tok = NULL;
+ type = process_arg(event, arg, &token);
+ if (test_type_token(type, token, TEP_EVENT_OP, "]"))
+ goto out_free;
+
+ top->op.right = arg;
+
+ free_token(token);
+ type = read_token_item(event->tep, &token);
+ *tok = token;
+
+ return type;
+
+out_free:
+ free_token(token);
+ free_arg(arg);
+ return TEP_EVENT_ERROR;
+}
+
+static int get_op_prio(char *op)
+{
+ if (strlen(op) == 1) {
+ switch (op[0]) {
+ case '~':
+ case '!':
+ return 4;
+ case '*':
+ case '/':
+ case '%':
+ return 6;
+ case '+':
+ case '-':
+ return 7;
+ /* '>>' and '<<' are 8 */
+ case '<':
+ case '>':
+ return 9;
+ /* '==' and '!=' are 10 */
+ case '&':
+ return 11;
+ case '^':
+ return 12;
+ case '|':
+ return 13;
+ case '?':
+ return 16;
+ default:
+ do_warning("unknown op '%c'", op[0]);
+ return -1;
+ }
+ } else {
+ if (strcmp(op, "++") == 0 ||
+ strcmp(op, "--") == 0) {
+ return 3;
+ } else if (strcmp(op, ">>") == 0 ||
+ strcmp(op, "<<") == 0) {
+ return 8;
+ } else if (strcmp(op, ">=") == 0 ||
+ strcmp(op, "<=") == 0) {
+ return 9;
+ } else if (strcmp(op, "==") == 0 ||
+ strcmp(op, "!=") == 0) {
+ return 10;
+ } else if (strcmp(op, "&&") == 0) {
+ return 14;
+ } else if (strcmp(op, "||") == 0) {
+ return 15;
+ } else {
+ do_warning("unknown op '%s'", op);
+ return -1;
+ }
+ }
+}
+
+static int set_op_prio(struct tep_print_arg *arg)
+{
+
+ /* single ops are the greatest */
+ if (!arg->op.left || arg->op.left->type == TEP_PRINT_NULL)
+ arg->op.prio = 0;
+ else
+ arg->op.prio = get_op_prio(arg->op.op);
+
+ return arg->op.prio;
+}
+
+static int consolidate_op_arg(struct tep_print_arg *arg)
+{
+ unsigned long long val, left, right;
+ int ret = 0;
+
+ if (arg->type != TEP_PRINT_OP)
+ return 0;
+
+ if (arg->op.left)
+ ret = consolidate_op_arg(arg->op.left);
+ if (ret < 0)
+ return ret;
+
+ if (arg->op.right)
+ ret = consolidate_op_arg(arg->op.right);
+ if (ret < 0)
+ return ret;
+
+ if (!arg->op.left || !arg->op.right)
+ return 0;
+
+ if (arg->op.left->type != TEP_PRINT_ATOM ||
+ arg->op.right->type != TEP_PRINT_ATOM)
+ return 0;
+
+ /* Two atoms, we can do the operation now. */
+ left = strtoull(arg->op.left->atom.atom, NULL, 0);
+ right = strtoull(arg->op.right->atom.atom, NULL, 0);
+
+ switch (arg->op.op[0]) {
+ case '>':
+ switch (arg->op.op[1]) {
+ case '>':
+ val = left >> right;
+ break;
+ case '=':
+ val = left >= right;
+ break;
+ default:
+ val = left > right;
+ break;
+ }
+ break;
+ case '<':
+ switch (arg->op.op[1]) {
+ case '<':
+ val = left << right;
+ break;
+ case '=':
+ val = left <= right;
+ break;
+ default:
+ val = left < right;
+ break;
+ }
+ break;
+ case '&':
+ switch (arg->op.op[1]) {
+ case '&':
+ val = left && right;
+ break;
+ default:
+ val = left & right;
+ break;
+ }
+ break;
+ case '|':
+ switch (arg->op.op[1]) {
+ case '|':
+ val = left || right;
+ break;
+ default:
+ val = left | right;
+ break;
+ }
+ break;
+ case '-':
+ val = left - right;
+ break;
+ case '+':
+ val = left + right;
+ break;
+ case '*':
+ val = left * right;
+ break;
+ case '^':
+ val = left ^ right;
+ break;
+ case '/':
+ val = left / right;
+ break;
+ case '%':
+ val = left % right;
+ break;
+ case '=':
+ /* Only '==' is called here */
+ val = left == right;
+ break;
+ case '!':
+ /* Only '!=' is called here. */
+ val = left != right;
+ break;
+ default:
+ return 0;
+ }
+
+ free_arg(arg->op.left);
+ free_arg(arg->op.right);
+
+ arg->type = TEP_PRINT_ATOM;
+ free(arg->op.op);
+ return asprintf(&arg->atom.atom, "%lld", val) < 0 ? -1 : 0;
+}
+
+/* Note, *tok does not get freed, but will most likely be saved */
+static enum tep_event_type
+process_op(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ struct tep_print_arg *left, *right = NULL;
+ enum tep_event_type type;
+ char *token;
+
+ /* the op is passed in via tok */
+ token = *tok;
+
+ if (arg->type == TEP_PRINT_OP && !arg->op.left) {
+ /* handle single op */
+ switch (token[0]) {
+ case '~':
+ case '!':
+ case '+':
+ case '-':
+ break;
+ default:
+ do_warning_event(event, "bad op token %s", token);
+ goto out_free;
+
+ }
+ if (token[1]) {
+ do_warning_event(event, "bad op token %s", token);
+ goto out_free;
+ }
+
+ /* make an empty left */
+ left = alloc_arg();
+ if (!left)
+ goto out_warn_free;
+
+ left->type = TEP_PRINT_NULL;
+ arg->op.left = left;
+
+ right = alloc_arg();
+ if (!right)
+ goto out_warn_free;
+
+ arg->op.right = right;
+
+ /* do not free the token, it belongs to an op */
+ *tok = NULL;
+ type = process_arg(event, right, tok);
+
+ } else if (strcmp(token, "?") == 0) {
+
+ left = alloc_arg();
+ if (!left)
+ goto out_warn_free;
+
+ /* copy the top arg to the left */
+ *left = *arg;
+
+ arg->type = TEP_PRINT_OP;
+ arg->op.op = token;
+ arg->op.left = left;
+ arg->op.right = NULL;
+ arg->op.prio = 0;
+
+ /* it will set arg->op.right */
+ type = process_cond(event, arg, tok);
+
+ } else if (strcmp(token, ">>") == 0 ||
+ strcmp(token, "<<") == 0 ||
+ strcmp(token, "&") == 0 ||
+ strcmp(token, "|") == 0 ||
+ strcmp(token, "&&") == 0 ||
+ strcmp(token, "||") == 0 ||
+ strcmp(token, "-") == 0 ||
+ strcmp(token, "+") == 0 ||
+ strcmp(token, "*") == 0 ||
+ strcmp(token, "^") == 0 ||
+ strcmp(token, "/") == 0 ||
+ strcmp(token, "%") == 0 ||
+ strcmp(token, "<") == 0 ||
+ strcmp(token, ">") == 0 ||
+ strcmp(token, "<=") == 0 ||
+ strcmp(token, ">=") == 0 ||
+ strcmp(token, "==") == 0 ||
+ strcmp(token, "!=") == 0) {
+
+ left = alloc_arg();
+ if (!left)
+ goto out_warn_free;
+
+ /* copy the top arg to the left */
+ *left = *arg;
+
+ arg->type = TEP_PRINT_OP;
+ arg->op.op = token;
+ arg->op.left = left;
+ arg->op.right = NULL;
+
+ if (set_op_prio(arg) == -1) {
+ event->flags |= TEP_EVENT_FL_FAILED;
+ /* arg->op.op (= token) will be freed at out_free */
+ arg->op.op = NULL;
+ goto out_free;
+ }
+
+ type = read_token_item(event->tep, &token);
+ *tok = token;
+
+ /* could just be a type pointer */
+ if ((strcmp(arg->op.op, "*") == 0) &&
+ type == TEP_EVENT_DELIM && (strcmp(token, ")") == 0)) {
+ int ret;
+
+ if (left->type != TEP_PRINT_ATOM) {
+ do_warning_event(event, "bad pointer type");
+ goto out_free;
+ }
+ ret = append(&left->atom.atom, " ", "*");
+ if (ret < 0)
+ goto out_warn_free;
+
+ free(arg->op.op);
+ *arg = *left;
+ free(left);
+
+ return type;
+ }
+
+ right = alloc_arg();
+ if (!right)
+ goto out_warn_free;
+
+ type = process_arg_token(event, right, tok, type);
+ if (type == TEP_EVENT_ERROR) {
+ free_arg(right);
+ /* token was freed in process_arg_token() via *tok */
+ token = NULL;
+ goto out_free;
+ }
+
+ if (right->type == TEP_PRINT_OP &&
+ get_op_prio(arg->op.op) < get_op_prio(right->op.op)) {
+ struct tep_print_arg tmp;
+
+ /* rotate ops according to the priority */
+ arg->op.right = right->op.left;
+
+ tmp = *arg;
+ *arg = *right;
+ *right = tmp;
+
+ arg->op.left = right;
+ } else {
+ arg->op.right = right;
+ }
+
+ } else if (strcmp(token, "[") == 0) {
+
+ left = alloc_arg();
+ if (!left)
+ goto out_warn_free;
+
+ *left = *arg;
+
+ arg->type = TEP_PRINT_OP;
+ arg->op.op = token;
+ arg->op.left = left;
+ arg->op.right = NULL;
+
+ arg->op.prio = 0;
+
+ /* it will set arg->op.right */
+ type = process_array(event, arg, tok);
+
+ } else {
+ do_warning_event(event, "unknown op '%s'", token);
+ event->flags |= TEP_EVENT_FL_FAILED;
+ /* the arg is now the left side */
+ goto out_free;
+ }
+
+ if (type == TEP_EVENT_OP && strcmp(*tok, ":") != 0) {
+ int prio;
+
+ /* higher prios need to be closer to the root */
+ prio = get_op_prio(*tok);
+
+ if (prio > arg->op.prio)
+ return process_op(event, arg, tok);
+
+ return process_op(event, right, tok);
+ }
+
+ return type;
+
+out_warn_free:
+ do_warning_event(event, "%s: not enough memory!", __func__);
+out_free:
+ free_token(token);
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_entry(struct tep_event *event __maybe_unused, struct tep_print_arg *arg,
+ char **tok)
+{
+ enum tep_event_type type;
+ char *field;
+ char *token;
+
+ type = read_token_item(event->tep, &token);
+ /*
+ * Check if REC happens to be surrounded by parenthesis, and
+ * return if that's the case, as "(REC)->" is valid.
+ * but return TEP_EVENT_ITEM.
+ */
+ if (type == TEP_EVENT_DELIM && strcmp(token, ")") == 0) {
+ *tok = token;
+ return TEP_EVENT_ITEM;
+ }
+
+ if (test_type_token(type, token, TEP_EVENT_OP, "->"))
+ goto out_free;
+
+ free_token(token);
+
+ if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0)
+ goto out_free;
+ field = token;
+
+ arg->type = TEP_PRINT_FIELD;
+ arg->field.name = field;
+
+ arg->field.field = tep_find_any_field(event, arg->field.name);
+
+ if (is_flag_field) {
+ arg->field.field->flags |= TEP_FIELD_IS_FLAG;
+ is_flag_field = 0;
+ } else if (is_symbolic_field) {
+ arg->field.field->flags |= TEP_FIELD_IS_SYMBOLIC;
+ is_symbolic_field = 0;
+ }
+
+ type = read_token(event->tep, &token);
+ *tok = token;
+
+ return type;
+
+ out_free:
+ free_token(token);
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static int alloc_and_process_delim(struct tep_event *event, char *next_token,
+ struct tep_print_arg **print_arg)
+{
+ struct tep_print_arg *field;
+ enum tep_event_type type;
+ char *token;
+ int ret = 0;
+
+ field = alloc_arg();
+ if (!field) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ type = process_arg(event, field, &token);
+
+ /* We do allow operators */
+ if (type == TEP_EVENT_OP) {
+ type = process_op(event, field, &token);
+
+ if (consolidate_op_arg(field) < 0)
+ type = TEP_EVENT_ERROR;
+
+ if (type == TEP_EVENT_ERROR)
+ goto out_error;
+ }
+
+ if (test_type_token(type, token, TEP_EVENT_DELIM, next_token))
+ goto out_error;
+
+ *print_arg = field;
+
+out_free_token:
+ free_token(token);
+
+ return ret;
+out_error:
+ errno = EINVAL;
+ ret = -1;
+ free_arg(field);
+ goto out_free_token;
+}
+
+static char *arg_eval (struct tep_print_arg *arg);
+
+static unsigned long long
+eval_type_str(unsigned long long val, const char *type, int pointer)
+{
+ int sign = 0;
+ char *ref;
+ int len;
+
+ len = strlen(type);
+ if (len < 2) {
+ do_warning("invalid type: %s", type);
+ return val;
+ }
+
+ if (pointer) {
+
+ if (type[len-1] != '*') {
+ do_warning("pointer expected with non pointer type");
+ return val;
+ }
+
+ ref = malloc(len);
+ if (!ref) {
+ do_warning("%s: not enough memory!", __func__);
+ return val;
+ }
+ memcpy(ref, type, len);
+
+ /* chop off the " *" */
+ ref[len - 2] = 0;
+
+ val = eval_type_str(val, ref, 0);
+ free(ref);
+ return val;
+ }
+
+ /* check if this is a pointer */
+ if (type[len - 1] == '*')
+ return val;
+
+ /* Try to figure out the arg size*/
+ if (strncmp(type, "struct", 6) == 0)
+ /* all bets off */
+ return val;
+
+ if (strcmp(type, "u8") == 0)
+ return val & 0xff;
+
+ if (strcmp(type, "u16") == 0)
+ return val & 0xffff;
+
+ if (strcmp(type, "u32") == 0)
+ return val & 0xffffffff;
+
+ if (strcmp(type, "u64") == 0 ||
+ strcmp(type, "s64") == 0)
+ return val;
+
+ if (strcmp(type, "s8") == 0)
+ return (unsigned long long)(char)val & 0xff;
+
+ if (strcmp(type, "s16") == 0)
+ return (unsigned long long)(short)val & 0xffff;
+
+ if (strcmp(type, "s32") == 0)
+ return (unsigned long long)(int)val & 0xffffffff;
+
+ if (strncmp(type, "unsigned ", 9) == 0) {
+ sign = 0;
+ type += 9;
+ }
+
+ if (strcmp(type, "char") == 0) {
+ if (sign)
+ return (unsigned long long)(char)val & 0xff;
+ else
+ return val & 0xff;
+ }
+
+ if (strcmp(type, "short") == 0) {
+ if (sign)
+ return (unsigned long long)(short)val & 0xffff;
+ else
+ return val & 0xffff;
+ }
+
+ if (strcmp(type, "int") == 0) {
+ if (sign)
+ return (unsigned long long)(int)val & 0xffffffff;
+ else
+ return val & 0xffffffff;
+ }
+
+ return val;
+}
+
+/*
+ * Try to figure out the type.
+ */
+static unsigned long long
+eval_type(unsigned long long val, struct tep_print_arg *arg, int pointer)
+{
+ if (arg->type != TEP_PRINT_TYPE) {
+ do_warning("expected type argument");
+ return 0;
+ }
+
+ return eval_type_str(val, arg->typecast.type, pointer);
+}
+
+static int arg_num_eval(struct tep_print_arg *arg, long long *val)
+{
+ long long left, right;
+ int ret = 1;
+
+ switch (arg->type) {
+ case TEP_PRINT_ATOM:
+ *val = strtoll(arg->atom.atom, NULL, 0);
+ break;
+ case TEP_PRINT_TYPE:
+ ret = arg_num_eval(arg->typecast.item, val);
+ if (!ret)
+ break;
+ *val = eval_type(*val, arg, 0);
+ break;
+ case TEP_PRINT_OP:
+ switch (arg->op.op[0]) {
+ case '|':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ if (arg->op.op[1])
+ *val = left || right;
+ else
+ *val = left | right;
+ break;
+ case '&':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ if (arg->op.op[1])
+ *val = left && right;
+ else
+ *val = left & right;
+ break;
+ case '<':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ switch (arg->op.op[1]) {
+ case 0:
+ *val = left < right;
+ break;
+ case '<':
+ *val = left << right;
+ break;
+ case '=':
+ *val = left <= right;
+ break;
+ default:
+ do_warning("unknown op '%s'", arg->op.op);
+ ret = 0;
+ }
+ break;
+ case '>':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ switch (arg->op.op[1]) {
+ case 0:
+ *val = left > right;
+ break;
+ case '>':
+ *val = left >> right;
+ break;
+ case '=':
+ *val = left >= right;
+ break;
+ default:
+ do_warning("unknown op '%s'", arg->op.op);
+ ret = 0;
+ }
+ break;
+ case '=':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+
+ if (arg->op.op[1] != '=') {
+ do_warning("unknown op '%s'", arg->op.op);
+ ret = 0;
+ } else
+ *val = left == right;
+ break;
+ case '!':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+
+ switch (arg->op.op[1]) {
+ case '=':
+ *val = left != right;
+ break;
+ default:
+ do_warning("unknown op '%s'", arg->op.op);
+ ret = 0;
+ }
+ break;
+ case '-':
+ /* check for negative */
+ if (arg->op.left->type == TEP_PRINT_NULL)
+ left = 0;
+ else
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ *val = left - right;
+ break;
+ case '+':
+ if (arg->op.left->type == TEP_PRINT_NULL)
+ left = 0;
+ else
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ *val = left + right;
+ break;
+ case '~':
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ *val = ~right;
+ break;
+ default:
+ do_warning("unknown op '%s'", arg->op.op);
+ ret = 0;
+ }
+ break;
+
+ case TEP_PRINT_NULL:
+ case TEP_PRINT_FIELD ... TEP_PRINT_SYMBOL:
+ case TEP_PRINT_STRING:
+ case TEP_PRINT_BSTRING:
+ case TEP_PRINT_BITMASK:
+ case TEP_PRINT_CPUMASK:
+ default:
+ do_warning("invalid eval type %d", arg->type);
+ ret = 0;
+
+ }
+ return ret;
+}
+
+static char *arg_eval (struct tep_print_arg *arg)
+{
+ long long val;
+ static char buf[24];
+
+ switch (arg->type) {
+ case TEP_PRINT_ATOM:
+ return arg->atom.atom;
+ case TEP_PRINT_TYPE:
+ return arg_eval(arg->typecast.item);
+ case TEP_PRINT_OP:
+ if (!arg_num_eval(arg, &val))
+ break;
+ sprintf(buf, "%lld", val);
+ return buf;
+
+ case TEP_PRINT_NULL:
+ case TEP_PRINT_FIELD ... TEP_PRINT_SYMBOL:
+ case TEP_PRINT_STRING:
+ case TEP_PRINT_BSTRING:
+ case TEP_PRINT_BITMASK:
+ case TEP_PRINT_CPUMASK:
+ default:
+ do_warning("invalid eval type %d", arg->type);
+ break;
+ }
+
+ return NULL;
+}
+
+static enum tep_event_type
+process_fields(struct tep_event *event, struct tep_print_flag_sym **list, char **tok)
+{
+ enum tep_event_type type;
+ struct tep_print_arg *arg = NULL;
+ struct tep_print_flag_sym *field;
+ char *token = *tok;
+ char *value;
+
+ do {
+ free_token(token);
+ type = read_token_item(event->tep, &token);
+ if (test_type_token(type, token, TEP_EVENT_OP, "{"))
+ break;
+
+ arg = alloc_arg();
+ if (!arg)
+ goto out_free;
+
+ free_token(token);
+ type = process_arg(event, arg, &token);
+
+ if (type == TEP_EVENT_OP)
+ type = process_op(event, arg, &token);
+
+ if (type == TEP_EVENT_ERROR)
+ goto out_free;
+
+ if (test_type_token(type, token, TEP_EVENT_DELIM, ","))
+ goto out_free;
+
+ field = calloc(1, sizeof(*field));
+ if (!field)
+ goto out_free;
+
+ value = arg_eval(arg);
+ if (value == NULL)
+ goto out_free_field;
+ field->value = strdup(value);
+ if (field->value == NULL)
+ goto out_free_field;
+
+ free_arg(arg);
+ arg = alloc_arg();
+ if (!arg)
+ goto out_free;
+
+ free_token(token);
+ type = process_arg(event, arg, &token);
+ if (test_type_token(type, token, TEP_EVENT_OP, "}"))
+ goto out_free_field;
+
+ value = arg_eval(arg);
+ if (value == NULL)
+ goto out_free_field;
+ field->str = strdup(value);
+ if (field->str == NULL)
+ goto out_free_field;
+ free_arg(arg);
+ arg = NULL;
+
+ *list = field;
+ list = &field->next;
+
+ free_token(token);
+ type = read_token_item(event->tep, &token);
+ } while (type == TEP_EVENT_DELIM && strcmp(token, ",") == 0);
+
+ *tok = token;
+ return type;
+
+out_free_field:
+ free_flag_sym(field);
+out_free:
+ free_arg(arg);
+ free_token(token);
+ *tok = NULL;
+
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_flags(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ struct tep_print_arg *field;
+ enum tep_event_type type;
+ char *token = NULL;
+
+ memset(arg, 0, sizeof(*arg));
+ arg->type = TEP_PRINT_FLAGS;
+
+ field = alloc_arg();
+ if (!field) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ goto out_free;
+ }
+
+ type = process_field_arg(event, field, &token);
+
+ /* Handle operations in the first argument */
+ while (type == TEP_EVENT_OP)
+ type = process_op(event, field, &token);
+
+ if (test_type_token(type, token, TEP_EVENT_DELIM, ","))
+ goto out_free_field;
+ free_token(token);
+
+ arg->flags.field = field;
+
+ type = read_token_item(event->tep, &token);
+ if (event_item_type(type)) {
+ arg->flags.delim = token;
+ type = read_token_item(event->tep, &token);
+ }
+
+ if (test_type_token(type, token, TEP_EVENT_DELIM, ","))
+ goto out_free;
+
+ type = process_fields(event, &arg->flags.flags, &token);
+ if (test_type_token(type, token, TEP_EVENT_DELIM, ")"))
+ goto out_free;
+
+ free_token(token);
+ type = read_token_item(event->tep, tok);
+ return type;
+
+out_free_field:
+ free_arg(field);
+out_free:
+ free_token(token);
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_symbols(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ struct tep_print_arg *field;
+ enum tep_event_type type;
+ char *token = NULL;
+
+ memset(arg, 0, sizeof(*arg));
+ arg->type = TEP_PRINT_SYMBOL;
+
+ field = alloc_arg();
+ if (!field) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ goto out_free;
+ }
+
+ type = process_field_arg(event, field, &token);
+
+ if (test_type_token(type, token, TEP_EVENT_DELIM, ","))
+ goto out_free_field;
+
+ arg->symbol.field = field;
+
+ type = process_fields(event, &arg->symbol.symbols, &token);
+ if (test_type_token(type, token, TEP_EVENT_DELIM, ")"))
+ goto out_free;
+
+ free_token(token);
+ type = read_token_item(event->tep, tok);
+ return type;
+
+out_free_field:
+ free_arg(field);
+out_free:
+ free_token(token);
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_hex_common(struct tep_event *event, struct tep_print_arg *arg,
+ char **tok, enum tep_print_arg_type type)
+{
+ memset(arg, 0, sizeof(*arg));
+ arg->type = type;
+
+ if (alloc_and_process_delim(event, ",", &arg->hex.field))
+ goto out;
+
+ if (alloc_and_process_delim(event, ")", &arg->hex.size))
+ goto free_field;
+
+ return read_token_item(event->tep, tok);
+
+free_field:
+ free_arg(arg->hex.field);
+ arg->hex.field = NULL;
+out:
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_hex(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ return process_hex_common(event, arg, tok, TEP_PRINT_HEX);
+}
+
+static enum tep_event_type
+process_hex_str(struct tep_event *event, struct tep_print_arg *arg,
+ char **tok)
+{
+ return process_hex_common(event, arg, tok, TEP_PRINT_HEX_STR);
+}
+
+static enum tep_event_type
+process_int_array(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ memset(arg, 0, sizeof(*arg));
+ arg->type = TEP_PRINT_INT_ARRAY;
+
+ if (alloc_and_process_delim(event, ",", &arg->int_array.field))
+ goto out;
+
+ if (alloc_and_process_delim(event, ",", &arg->int_array.count))
+ goto free_field;
+
+ if (alloc_and_process_delim(event, ")", &arg->int_array.el_size))
+ goto free_size;
+
+ return read_token_item(event->tep, tok);
+
+free_size:
+ free_arg(arg->int_array.count);
+ arg->int_array.count = NULL;
+free_field:
+ free_arg(arg->int_array.field);
+ arg->int_array.field = NULL;
+out:
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_dynamic_array(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ struct tep_format_field *field;
+ enum tep_event_type type;
+ char *token;
+
+ memset(arg, 0, sizeof(*arg));
+ arg->type = TEP_PRINT_DYNAMIC_ARRAY;
+
+ /*
+ * The item within the parenthesis is another field that holds
+ * the index into where the array starts.
+ */
+ type = read_token(event->tep, &token);
+ *tok = token;
+ if (type != TEP_EVENT_ITEM)
+ goto out_free;
+
+ /* Find the field */
+
+ field = tep_find_field(event, token);
+ if (!field)
+ goto out_free;
+
+ arg->dynarray.field = field;
+ arg->dynarray.index = 0;
+
+ if (read_expected(event->tep, TEP_EVENT_DELIM, ")") < 0)
+ goto out_free;
+
+ free_token(token);
+ type = read_token_item(event->tep, &token);
+ *tok = token;
+ if (type != TEP_EVENT_OP || strcmp(token, "[") != 0)
+ return type;
+
+ free_token(token);
+ arg = alloc_arg();
+ if (!arg) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+ }
+
+ type = process_arg(event, arg, &token);
+ if (type == TEP_EVENT_ERROR)
+ goto out_free_arg;
+
+ if (!test_type_token(type, token, TEP_EVENT_OP, "]"))
+ goto out_free_arg;
+
+ free_token(token);
+ type = read_token_item(event->tep, tok);
+ return type;
+
+ out_free_arg:
+ free_arg(arg);
+ out_free:
+ free_token(token);
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_dynamic_array_len(struct tep_event *event, struct tep_print_arg *arg,
+ char **tok)
+{
+ struct tep_format_field *field;
+ enum tep_event_type type;
+ char *token;
+
+ if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0)
+ goto out_free;
+
+ arg->type = TEP_PRINT_DYNAMIC_ARRAY_LEN;
+
+ /* Find the field */
+ field = tep_find_field(event, token);
+ if (!field)
+ goto out_free;
+
+ arg->dynarray.field = field;
+ arg->dynarray.index = 0;
+
+ if (read_expected(event->tep, TEP_EVENT_DELIM, ")") < 0)
+ goto out_err;
+
+ free_token(token);
+ type = read_token(event->tep, &token);
+ *tok = token;
+
+ return type;
+
+ out_free:
+ free_token(token);
+ out_err:
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_paren(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ struct tep_print_arg *item_arg;
+ enum tep_event_type type;
+ char *token;
+
+ type = process_arg(event, arg, &token);
+
+ if (type == TEP_EVENT_ERROR)
+ goto out_free;
+
+ if (type == TEP_EVENT_OP)
+ type = process_op(event, arg, &token);
+
+ if (type == TEP_EVENT_ERROR)
+ goto out_free;
+
+ /*
+ * If REC is surrounded by parenthesis, the process_arg()
+ * will return TEP_EVENT_ITEM with token == ")". In
+ * this case, we need to continue processing the item
+ * and return.
+ */
+ if (type == TEP_EVENT_ITEM && strcmp(token, ")") == 0) {
+ free_token(token);
+ return process_entry(event, arg, tok);
+ }
+
+ if (test_type_token(type, token, TEP_EVENT_DELIM, ")"))
+ goto out_free;
+
+ free_token(token);
+ type = read_token_item(event->tep, &token);
+
+ /*
+ * If the next token is an item or another open paren, then
+ * this was a typecast.
+ */
+ if (event_item_type(type) ||
+ (type == TEP_EVENT_DELIM && strcmp(token, "(") == 0)) {
+
+ /* make this a typecast and contine */
+
+ /* prevous must be an atom */
+ if (arg->type != TEP_PRINT_ATOM) {
+ do_warning_event(event, "previous needed to be TEP_PRINT_ATOM");
+ goto out_free;
+ }
+
+ item_arg = alloc_arg();
+ if (!item_arg) {
+ do_warning_event(event, "%s: not enough memory!",
+ __func__);
+ goto out_free;
+ }
+
+ arg->type = TEP_PRINT_TYPE;
+ arg->typecast.type = arg->atom.atom;
+ arg->typecast.item = item_arg;
+ type = process_arg_token(event, item_arg, &token, type);
+
+ }
+
+ *tok = token;
+ return type;
+
+ out_free:
+ free_token(token);
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+
+static enum tep_event_type
+process_str(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ enum tep_event_type type;
+ char *token;
+
+ if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0)
+ goto out_free;
+
+ arg->type = TEP_PRINT_STRING;
+ arg->string.string = token;
+ arg->string.offset = -1;
+ arg->string.field = NULL;
+
+ if (read_expected(event->tep, TEP_EVENT_DELIM, ")") < 0)
+ goto out_err;
+
+ type = read_token(event->tep, &token);
+ *tok = token;
+
+ return type;
+
+ out_free:
+ free_token(token);
+ out_err:
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_bitmask(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ enum tep_event_type type;
+ char *token;
+
+ if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0)
+ goto out_free;
+
+ arg->type = TEP_PRINT_BITMASK;
+ arg->bitmask.bitmask = token;
+ arg->bitmask.offset = -1;
+ arg->bitmask.field = NULL;
+
+ if (read_expected(event->tep, TEP_EVENT_DELIM, ")") < 0)
+ goto out_err;
+
+ type = read_token(event->tep, &token);
+ *tok = token;
+
+ return type;
+
+ out_free:
+ free_token(token);
+ out_err:
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_cpumask(struct tep_event *event __maybe_unused, struct tep_print_arg *arg,
+ char **tok)
+{
+ enum tep_event_type type = process_bitmask(event, arg, tok);
+ if (type != TEP_EVENT_ERROR)
+ arg->type = TEP_PRINT_CPUMASK;
+
+ return type;
+}
+
+static struct tep_function_handler *
+find_func_handler(struct tep_handle *tep, char *func_name)
+{
+ struct tep_function_handler *func;
+
+ if (!tep)
+ return NULL;
+
+ for (func = tep->func_handlers; func; func = func->next) {
+ if (strcmp(func->name, func_name) == 0)
+ break;
+ }
+
+ return func;
+}
+
+static void remove_func_handler(struct tep_handle *tep, char *func_name)
+{
+ struct tep_function_handler *func;
+ struct tep_function_handler **next;
+
+ next = &tep->func_handlers;
+ while ((func = *next)) {
+ if (strcmp(func->name, func_name) == 0) {
+ *next = func->next;
+ free_func_handle(func);
+ break;
+ }
+ next = &func->next;
+ }
+}
+
+static enum tep_event_type
+process_func_handler(struct tep_event *event, struct tep_function_handler *func,
+ struct tep_print_arg *arg, char **tok)
+{
+ struct tep_print_arg **next_arg;
+ struct tep_print_arg *farg;
+ enum tep_event_type type;
+ char *token;
+ int i;
+
+ arg->type = TEP_PRINT_FUNC;
+ arg->func.func = func;
+
+ *tok = NULL;
+
+ next_arg = &(arg->func.args);
+ for (i = 0; i < func->nr_args; i++) {
+ farg = alloc_arg();
+ if (!farg) {
+ do_warning_event(event, "%s: not enough memory!",
+ __func__);
+ return TEP_EVENT_ERROR;
+ }
+
+ type = process_arg(event, farg, &token);
+ if (i < (func->nr_args - 1)) {
+ if (type != TEP_EVENT_DELIM || strcmp(token, ",") != 0) {
+ do_warning_event(event,
+ "Error: function '%s()' expects %d arguments but event %s only uses %d",
+ func->name, func->nr_args,
+ event->name, i + 1);
+ goto err;
+ }
+ } else {
+ if (type != TEP_EVENT_DELIM || strcmp(token, ")") != 0) {
+ do_warning_event(event,
+ "Error: function '%s()' only expects %d arguments but event %s has more",
+ func->name, func->nr_args, event->name);
+ goto err;
+ }
+ }
+
+ *next_arg = farg;
+ next_arg = &(farg->next);
+ free_token(token);
+ }
+
+ type = read_token(event->tep, &token);
+ *tok = token;
+
+ return type;
+
+err:
+ free_arg(farg);
+ free_token(token);
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_builtin_expect(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ enum tep_event_type type;
+ char *token = NULL;
+
+ /* Handle __builtin_expect( cond, #) */
+ type = process_arg(event, arg, &token);
+
+ if (type != TEP_EVENT_DELIM || token[0] != ',')
+ goto out_free;
+
+ free_token(token);
+
+ /* We don't care what the second parameter is of the __builtin_expect() */
+ if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0)
+ goto out_free;
+
+ if (read_expected(event->tep, TEP_EVENT_DELIM, ")") < 0)
+ goto out_free;
+
+ free_token(token);
+ type = read_token_item(event->tep, tok);
+ return type;
+
+out_free:
+ free_token(token);
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_sizeof(struct tep_event *event, struct tep_print_arg *arg, char **tok)
+{
+ struct tep_format_field *field;
+ enum tep_event_type type;
+ char *token = NULL;
+ bool ok = false;
+ int ret;
+
+ type = read_token_item(event->tep, &token);
+
+ arg->type = TEP_PRINT_ATOM;
+
+ /* We handle some sizeof types */
+ if (strcmp(token, "unsigned") == 0) {
+ free_token(token);
+ type = read_token_item(event->tep, &token);
+
+ if (type == TEP_EVENT_ERROR)
+ goto error;
+
+ if (type != TEP_EVENT_ITEM)
+ ok = true;
+ }
+
+ if (ok || strcmp(token, "int") == 0) {
+ arg->atom.atom = strdup("4");
+
+ } else if (strcmp(token, "long") == 0) {
+ free_token(token);
+ type = read_token_item(event->tep, &token);
+
+ if (token && strcmp(token, "long") == 0) {
+ arg->atom.atom = strdup("8");
+ } else {
+ switch (event->tep->long_size) {
+ case 4:
+ arg->atom.atom = strdup("4");
+ break;
+ case 8:
+ arg->atom.atom = strdup("8");
+ break;
+ default:
+ /* long size not defined yet, fail to parse it */
+ goto error;
+ }
+ /* The token is the next token */
+ ok = true;
+ }
+ } else if (strcmp(token, "REC") == 0) {
+
+ free_token(token);
+ type = read_token_item(event->tep, &token);
+
+ if (test_type_token(type, token, TEP_EVENT_OP, "->"))
+ goto error;
+ free_token(token);
+
+ if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0)
+ goto error;
+
+ field = tep_find_any_field(event, token);
+ /* Can't handle arrays (yet) */
+ if (!field || field->flags & TEP_FIELD_IS_ARRAY)
+ goto error;
+
+ ret = asprintf(&arg->atom.atom, "%d", field->size);
+ if (ret < 0)
+ goto error;
+
+ } else if (!ok) {
+ goto error;
+ }
+
+ if (!ok) {
+ free_token(token);
+ type = read_token_item(event->tep, tok);
+ }
+ if (test_type_token(type, token, TEP_EVENT_DELIM, ")"))
+ goto error;
+
+ free_token(token);
+ return read_token_item(event->tep, tok);
+error:
+ free_token(token);
+ *tok = NULL;
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_function(struct tep_event *event, struct tep_print_arg *arg,
+ char *token, char **tok)
+{
+ struct tep_function_handler *func;
+
+ if (strcmp(token, "__print_flags") == 0) {
+ free_token(token);
+ is_flag_field = 1;
+ return process_flags(event, arg, tok);
+ }
+ if (strcmp(token, "__print_symbolic") == 0) {
+ free_token(token);
+ is_symbolic_field = 1;
+ return process_symbols(event, arg, tok);
+ }
+ if (strcmp(token, "__print_hex") == 0) {
+ free_token(token);
+ return process_hex(event, arg, tok);
+ }
+ if (strcmp(token, "__print_hex_str") == 0) {
+ free_token(token);
+ return process_hex_str(event, arg, tok);
+ }
+ if (strcmp(token, "__print_array") == 0) {
+ free_token(token);
+ return process_int_array(event, arg, tok);
+ }
+ if (strcmp(token, "__get_str") == 0 ||
+ strcmp(token, "__get_rel_str") == 0) {
+ free_token(token);
+ return process_str(event, arg, tok);
+ }
+ if (strcmp(token, "__get_bitmask") == 0 ||
+ strcmp(token, "__get_rel_bitmask") == 0) {
+ free_token(token);
+ return process_bitmask(event, arg, tok);
+ }
+ if (strcmp(token, "__get_cpumask") == 0 ||
+ strcmp(token, "__get_rel_cpumask") == 0) {
+ free_token(token);
+ return process_cpumask(event, arg, tok);
+ }
+ if (strcmp(token, "__get_dynamic_array") == 0 ||
+ strcmp(token, "__get_rel_dynamic_array") == 0 ||
+ strcmp(token, "__get_sockaddr") == 0 ||
+ strcmp(token, "__get_sockaddr_rel") == 0) {
+ free_token(token);
+ return process_dynamic_array(event, arg, tok);
+ }
+ if (strcmp(token, "__get_dynamic_array_len") == 0 ||
+ strcmp(token, "__get_rel_dynamic_array_len") == 0) {
+ free_token(token);
+ return process_dynamic_array_len(event, arg, tok);
+ }
+ if (strcmp(token, "__builtin_expect") == 0) {
+ free_token(token);
+ return process_builtin_expect(event, arg, tok);
+ }
+ if (strcmp(token, "sizeof") == 0) {
+ free_token(token);
+ return process_sizeof(event, arg, tok);
+ }
+
+ func = find_func_handler(event->tep, token);
+ if (func) {
+ free_token(token);
+ return process_func_handler(event, func, arg, tok);
+ }
+
+ do_warning_event(event, "function %s not defined", token);
+ free_token(token);
+ return TEP_EVENT_ERROR;
+}
+
+static enum tep_event_type
+process_arg_token(struct tep_event *event, struct tep_print_arg *arg,
+ char **tok, enum tep_event_type type)
+{
+ char *token;
+ char *atom;
+
+ token = *tok;
+
+ switch (type) {
+ case TEP_EVENT_ITEM:
+ if (strcmp(token, "REC") == 0) {
+ free_token(token);
+ type = process_entry(event, arg, &token);
+ break;
+ }
+ atom = token;
+ /* test the next token */
+ type = read_token_item(event->tep, &token);
+
+ /*
+ * If the next token is a parenthesis, then this
+ * is a function.
+ */
+ if (type == TEP_EVENT_DELIM && strcmp(token, "(") == 0) {
+ free_token(token);
+ token = NULL;
+ /* this will free atom. */
+ type = process_function(event, arg, atom, &token);
+ break;
+ }
+ /* atoms can be more than one token long */
+ while (type == TEP_EVENT_ITEM) {
+ int ret;
+
+ ret = append(&atom, " ", token);
+ if (ret < 0) {
+ free(atom);
+ *tok = NULL;
+ free_token(token);
+ return TEP_EVENT_ERROR;
+ }
+ free_token(token);
+ type = read_token_item(event->tep, &token);
+ }
+
+ arg->type = TEP_PRINT_ATOM;
+ arg->atom.atom = atom;
+ break;
+
+ case TEP_EVENT_DQUOTE:
+ case TEP_EVENT_SQUOTE:
+ arg->type = TEP_PRINT_ATOM;
+ arg->atom.atom = token;
+ type = read_token_item(event->tep, &token);
+ break;
+ case TEP_EVENT_DELIM:
+ if (strcmp(token, "(") == 0) {
+ free_token(token);
+ type = process_paren(event, arg, &token);
+ break;
+ }
+ case TEP_EVENT_OP:
+ /* handle single ops */
+ arg->type = TEP_PRINT_OP;
+ arg->op.op = token;
+ arg->op.left = NULL;
+ type = process_op(event, arg, &token);
+
+ /* On error, the op is freed */
+ if (type == TEP_EVENT_ERROR)
+ arg->op.op = NULL;
+
+ /* return error type if errored */
+ break;
+
+ case TEP_EVENT_ERROR ... TEP_EVENT_NEWLINE:
+ default:
+ do_warning_event(event, "unexpected type %d", type);
+ return TEP_EVENT_ERROR;
+ }
+ *tok = token;
+
+ return type;
+}
+
+static int event_read_print_args(struct tep_event *event, struct tep_print_arg **list)
+{
+ enum tep_event_type type = TEP_EVENT_ERROR;
+ struct tep_print_arg *arg;
+ char *token;
+ int args = 0;
+
+ do {
+ if (type == TEP_EVENT_NEWLINE) {
+ type = read_token_item(event->tep, &token);
+ continue;
+ }
+
+ arg = alloc_arg();
+ if (!arg) {
+ do_warning_event(event, "%s: not enough memory!",
+ __func__);
+ return -1;
+ }
+
+ type = process_arg(event, arg, &token);
+
+ if (type == TEP_EVENT_ERROR) {
+ free_token(token);
+ free_arg(arg);
+ return -1;
+ }
+
+ *list = arg;
+ args++;
+
+ if (type == TEP_EVENT_OP) {
+ type = process_op(event, arg, &token);
+ free_token(token);
+
+ if (consolidate_op_arg(arg) < 0)
+ type = TEP_EVENT_ERROR;
+
+ if (type == TEP_EVENT_ERROR) {
+ *list = NULL;
+ free_arg(arg);
+ return -1;
+ }
+ list = &arg->next;
+ continue;
+ }
+
+ if (type == TEP_EVENT_DELIM && strcmp(token, ",") == 0) {
+ free_token(token);
+ *list = arg;
+ list = &arg->next;
+ continue;
+ }
+ break;
+ } while (type != TEP_EVENT_NONE);
+
+ if (type != TEP_EVENT_NONE && type != TEP_EVENT_ERROR)
+ free_token(token);
+
+ return args;
+}
+
+static int event_read_print(struct tep_event *event)
+{
+ enum tep_event_type type;
+ char *token;
+ int ret;
+
+ if (read_expected_item(event->tep, TEP_EVENT_ITEM, "print") < 0)
+ return -1;
+
+ if (read_expected(event->tep, TEP_EVENT_ITEM, "fmt") < 0)
+ return -1;
+
+ if (read_expected(event->tep, TEP_EVENT_OP, ":") < 0)
+ return -1;
+
+ if (read_expect_type(event->tep, TEP_EVENT_DQUOTE, &token) < 0)
+ goto fail;
+
+ concat:
+ event->print_fmt.format = token;
+ event->print_fmt.args = NULL;
+
+ /* ok to have no arg */
+ type = read_token_item(event->tep, &token);
+
+ if (type == TEP_EVENT_NONE)
+ return 0;
+
+ /* Handle concatenation of print lines */
+ if (type == TEP_EVENT_DQUOTE) {
+ char *cat;
+
+ if (asprintf(&cat, "%s%s", event->print_fmt.format, token) < 0)
+ goto fail;
+ free_token(token);
+ free_token(event->print_fmt.format);
+ event->print_fmt.format = NULL;
+ token = cat;
+ goto concat;
+ }
+
+ if (test_type_token(type, token, TEP_EVENT_DELIM, ","))
+ goto fail;
+
+ free_token(token);
+
+ ret = event_read_print_args(event, &event->print_fmt.args);
+ if (ret < 0)
+ return -1;
+
+ return ret;
+
+ fail:
+ free_token(token);
+ return -1;
+}
+
+/**
+ * tep_find_common_field - return a common field by event
+ * @event: handle for the event
+ * @name: the name of the common field to return
+ *
+ * Returns a common field from the event by the given @name.
+ * This only searches the common fields and not all field.
+ */
+struct tep_format_field *
+tep_find_common_field(struct tep_event *event, const char *name)
+{
+ struct tep_format_field *format;
+
+ for (format = event->format.common_fields;
+ format; format = format->next) {
+ if (strcmp(format->name, name) == 0)
+ break;
+ }
+
+ return format;
+}
+
+/**
+ * tep_find_field - find a non-common field
+ * @event: handle for the event
+ * @name: the name of the non-common field
+ *
+ * Returns a non-common field by the given @name.
+ * This does not search common fields.
+ */
+struct tep_format_field *
+tep_find_field(struct tep_event *event, const char *name)
+{
+ struct tep_format_field *format;
+
+ for (format = event->format.fields;
+ format; format = format->next) {
+ if (strcmp(format->name, name) == 0)
+ break;
+ }
+
+ return format;
+}
+
+/**
+ * tep_find_any_field - find any field by name
+ * @event: handle for the event
+ * @name: the name of the field
+ *
+ * Returns a field by the given @name.
+ * This searches the common field names first, then
+ * the non-common ones if a common one was not found.
+ */
+struct tep_format_field *
+tep_find_any_field(struct tep_event *event, const char *name)
+{
+ struct tep_format_field *format;
+
+ format = tep_find_common_field(event, name);
+ if (format)
+ return format;
+ return tep_find_field(event, name);
+}
+
+/**
+ * tep_read_number - read a number from data
+ * @tep: a handle to the trace event parser context
+ * @ptr: the raw data
+ * @size: the size of the data that holds the number
+ *
+ * Returns the number (converted to host) from the
+ * raw data.
+ */
+unsigned long long tep_read_number(struct tep_handle *tep,
+ const void *ptr, int size)
+{
+ unsigned long long val;
+
+ switch (size) {
+ case 1:
+ return *(unsigned char *)ptr;
+ case 2:
+ return data2host2(tep, *(unsigned short *)ptr);
+ case 4:
+ return data2host4(tep, *(unsigned int *)ptr);
+ case 8:
+ memcpy(&val, (ptr), sizeof(unsigned long long));
+ return data2host8(tep, val);
+ default:
+ /* BUG! */
+ return 0;
+ }
+}
+
+/**
+ * tep_read_number_field - read a number from data
+ * @field: a handle to the field
+ * @data: the raw data to read
+ * @value: the value to place the number in
+ *
+ * Reads raw data according to a field offset and size,
+ * and translates it into @value.
+ *
+ * Returns 0 on success, -1 otherwise.
+ */
+int tep_read_number_field(struct tep_format_field *field, const void *data,
+ unsigned long long *value)
+{
+ if (!field)
+ return -1;
+ switch (field->size) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ *value = tep_read_number(field->event->tep,
+ data + field->offset, field->size);
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+static int get_common_info(struct tep_handle *tep,
+ const char *type, int *offset, int *size)
+{
+ struct tep_event *event;
+ struct tep_format_field *field;
+
+ /*
+ * All events should have the same common elements.
+ * Pick any event to find where the type is;
+ */
+ if (!tep->events) {
+ do_warning("no event_list!");
+ return -1;
+ }
+
+ event = tep->events[0];
+ field = tep_find_common_field(event, type);
+ if (!field)
+ return -1;
+
+ *offset = field->offset;
+ *size = field->size;
+
+ return 0;
+}
+
+static int __parse_common(struct tep_handle *tep, void *data,
+ int *size, int *offset, const char *name)
+{
+ int ret;
+
+ if (!*size) {
+ ret = get_common_info(tep, name, offset, size);
+ if (ret < 0)
+ return ret;
+ }
+ return tep_read_number(tep, data + *offset, *size);
+}
+
+static int trace_parse_common_type(struct tep_handle *tep, void *data)
+{
+ return __parse_common(tep, data,
+ &tep->type_size, &tep->type_offset,
+ "common_type");
+}
+
+static int parse_common_pid(struct tep_handle *tep, void *data)
+{
+ return __parse_common(tep, data,
+ &tep->pid_size, &tep->pid_offset,
+ "common_pid");
+}
+
+static int parse_common_pc(struct tep_handle *tep, void *data)
+{
+ return __parse_common(tep, data,
+ &tep->pc_size, &tep->pc_offset,
+ "common_preempt_count");
+}
+
+static int parse_common_flags(struct tep_handle *tep, void *data)
+{
+ return __parse_common(tep, data,
+ &tep->flags_size, &tep->flags_offset,
+ "common_flags");
+}
+
+static int parse_common_lock_depth(struct tep_handle *tep, void *data)
+{
+ return __parse_common(tep, data,
+ &tep->ld_size, &tep->ld_offset,
+ "common_lock_depth");
+}
+
+static int parse_common_migrate_disable(struct tep_handle *tep, void *data)
+{
+ return __parse_common(tep, data,
+ &tep->ld_size, &tep->ld_offset,
+ "common_migrate_disable");
+}
+
+static int events_id_cmp(const void *a, const void *b);
+
+/**
+ * tep_find_event - find an event by given id
+ * @tep: a handle to the trace event parser context
+ * @id: the id of the event
+ *
+ * Returns an event that has a given @id.
+ */
+struct tep_event *tep_find_event(struct tep_handle *tep, int id)
+{
+ struct tep_event **eventptr;
+ struct tep_event key;
+ struct tep_event *pkey = &key;
+
+ /* Check cache first */
+ if (tep->last_event && tep->last_event->id == id)
+ return tep->last_event;
+
+ key.id = id;
+
+ eventptr = bsearch(&pkey, tep->events, tep->nr_events,
+ sizeof(*tep->events), events_id_cmp);
+
+ if (eventptr) {
+ tep->last_event = *eventptr;
+ return *eventptr;
+ }
+
+ return NULL;
+}
+
+/**
+ * tep_find_event_by_name - find an event by given name
+ * @tep: a handle to the trace event parser context
+ * @sys: the system name to search for
+ * @name: the name of the event to search for
+ *
+ * This returns an event with a given @name and under the system
+ * @sys. If @sys is NULL the first event with @name is returned.
+ */
+struct tep_event *
+tep_find_event_by_name(struct tep_handle *tep,
+ const char *sys, const char *name)
+{
+ struct tep_event *event = NULL;
+ int i;
+
+ if (tep->last_event &&
+ strcmp(tep->last_event->name, name) == 0 &&
+ (!sys || strcmp(tep->last_event->system, sys) == 0))
+ return tep->last_event;
+
+ for (i = 0; i < tep->nr_events; i++) {
+ event = tep->events[i];
+ if (strcmp(event->name, name) == 0) {
+ if (!sys)
+ break;
+ if (strcmp(event->system, sys) == 0)
+ break;
+ }
+ }
+ if (i == tep->nr_events)
+ event = NULL;
+
+ tep->last_event = event;
+ return event;
+}
+
+static unsigned long long test_for_symbol(struct tep_handle *tep,
+ struct tep_print_arg *arg)
+{
+ unsigned long long val = 0;
+ struct func_list *item = tep->funclist;
+ char *func;
+ int i;
+
+ if (isdigit(arg->atom.atom[0]))
+ return 0;
+
+ /* Linear search but only happens once (see after the loop) */
+ for (i = 0; i < (int)tep->func_count; i++) {
+ unsigned long long addr;
+ const char *name;
+
+ if (tep->func_map) {
+ addr = tep->func_map[i].addr;
+ name = tep->func_map[i].func;
+ } else if (item) {
+ addr = item->addr;
+ name = item->func;
+ item = item->next;
+ } else
+ break;
+
+ if (strcmp(arg->atom.atom, name) == 0) {
+ val = addr;
+ break;
+ }
+ }
+
+ /*
+ * This modifies the arg to hardcode the value
+ * and will not loop again.
+ */
+ func = realloc(arg->atom.atom, 32);
+ if (func) {
+ snprintf(func, 32, "%lld", val);
+ arg->atom.atom = func;
+ }
+ return val;
+}
+
+#define TEP_OFFSET_LEN_MASK 0xffff
+#define TEP_LEN_SHIFT 16
+
+static void dynamic_offset(struct tep_handle *tep, int size, void *data,
+ int data_size, unsigned int *offset, unsigned int *len)
+{
+ unsigned long long val;
+ unsigned int o, l;
+
+ /*
+ * The total allocated length of the dynamic array is
+ * stored in the top half of the field and the offset
+ * is in the bottom half of the 32 bit field.
+ */
+ val = tep_read_number(tep, data, size);
+
+ /* Check for overflows */
+ o = (unsigned int)(val & TEP_OFFSET_LEN_MASK);
+
+ /* If there's no length, then just make the length the size of the data */
+ if (size == 2)
+ l = data_size - o;
+ else
+ l = (unsigned int)((val >> TEP_LEN_SHIFT) & TEP_OFFSET_LEN_MASK);
+
+ if (offset)
+ *offset = o > data_size ? 0 : o;
+ if (len)
+ *len = o + l > data_size ? 0 : l;
+}
+
+static inline void dynamic_offset_field(struct tep_handle *tep,
+ struct tep_format_field *field,
+ void *data, int size,
+ unsigned int *offset,
+ unsigned int *len)
+{
+ /* Test for overflow */
+ if (field->offset + field->size > size) {
+ if (offset)
+ *offset = 0;
+ if (len)
+ *len = 0;
+ return;
+ }
+ dynamic_offset(tep, field->size, data + field->offset, size, offset, len);
+ if (field->flags & TEP_FIELD_IS_RELATIVE)
+ *offset += field->offset + field->size;
+}
+
+static bool check_data_offset_size(struct tep_event *event, const char *field_name,
+ int data_size, int field_offset, int field_size)
+{
+ /* Check to make sure the field is within the data */
+ if (field_offset + field_size <= data_size)
+ return false;
+
+ tep_warning("Event '%s' field '%s' goes beyond the size of the event (%d > %d)",
+ event->name, field_name, field_offset + field_size, data_size);
+ return true;
+}
+
+static unsigned long long
+eval_num_arg(void *data, int size, struct tep_event *event, struct tep_print_arg *arg)
+{
+ struct tep_handle *tep = event->tep;
+ unsigned long long val = 0;
+ unsigned long long left, right;
+ struct tep_print_arg *typearg = NULL;
+ struct tep_print_arg *larg;
+ unsigned int offset;
+ unsigned int field_size;
+
+ switch (arg->type) {
+ case TEP_PRINT_NULL:
+ /* ?? */
+ return 0;
+ case TEP_PRINT_ATOM:
+ val = strtoull(arg->atom.atom, NULL, 0);
+ if (!val)
+ val = test_for_symbol(tep, arg);
+ return val;
+ case TEP_PRINT_FIELD:
+ if (!arg->field.field) {
+ arg->field.field = tep_find_any_field(event, arg->field.name);
+ if (!arg->field.field)
+ goto out_warning_field;
+ }
+ if (check_data_offset_size(event, arg->field.name, size,
+ arg->field.field->offset,
+ arg->field.field->size)) {
+ val = 0;
+ break;
+ }
+ /* must be a number */
+ val = tep_read_number(tep, data + arg->field.field->offset,
+ arg->field.field->size);
+ break;
+ case TEP_PRINT_FLAGS:
+ case TEP_PRINT_SYMBOL:
+ case TEP_PRINT_INT_ARRAY:
+ case TEP_PRINT_HEX:
+ case TEP_PRINT_HEX_STR:
+ break;
+ case TEP_PRINT_TYPE:
+ val = eval_num_arg(data, size, event, arg->typecast.item);
+ return eval_type(val, arg, 0);
+ case TEP_PRINT_STRING:
+ case TEP_PRINT_BSTRING:
+ case TEP_PRINT_BITMASK:
+ case TEP_PRINT_CPUMASK:
+ return 0;
+ case TEP_PRINT_FUNC: {
+ struct trace_seq s;
+ trace_seq_init(&s);
+ val = process_defined_func(&s, data, size, event, arg);
+ trace_seq_destroy(&s);
+ return val;
+ }
+ case TEP_PRINT_OP:
+ if (strcmp(arg->op.op, "[") == 0) {
+ /*
+ * Arrays are special, since we don't want
+ * to read the arg as is.
+ */
+ right = eval_num_arg(data, size, event, arg->op.right);
+
+ /* handle typecasts */
+ larg = arg->op.left;
+ while (larg->type == TEP_PRINT_TYPE) {
+ if (!typearg)
+ typearg = larg;
+ larg = larg->typecast.item;
+ }
+
+ /* Default to long size */
+ field_size = tep->long_size;
+
+ switch (larg->type) {
+ case TEP_PRINT_DYNAMIC_ARRAY:
+ dynamic_offset_field(tep, larg->dynarray.field, data,
+ size, &offset, NULL);
+ offset += right;
+ if (larg->dynarray.field->elementsize)
+ field_size = larg->dynarray.field->elementsize;
+ break;
+ case TEP_PRINT_FIELD:
+ if (!larg->field.field) {
+ larg->field.field =
+ tep_find_any_field(event, larg->field.name);
+ if (!larg->field.field) {
+ arg = larg;
+ goto out_warning_field;
+ }
+ }
+ field_size = larg->field.field->elementsize;
+ offset = larg->field.field->offset +
+ right * larg->field.field->elementsize;
+ break;
+ default:
+ goto default_op; /* oops, all bets off */
+ }
+ if (check_data_offset_size(event, arg->field.name, size,
+ offset, field_size)) {
+ val = 0;
+ break;
+ }
+ val = tep_read_number(tep,
+ data + offset, field_size);
+ if (typearg)
+ val = eval_type(val, typearg, 1);
+ break;
+ } else if (strcmp(arg->op.op, "?") == 0) {
+ left = eval_num_arg(data, size, event, arg->op.left);
+ arg = arg->op.right;
+ if (left)
+ val = eval_num_arg(data, size, event, arg->op.left);
+ else
+ val = eval_num_arg(data, size, event, arg->op.right);
+ break;
+ }
+ default_op:
+ left = eval_num_arg(data, size, event, arg->op.left);
+ right = eval_num_arg(data, size, event, arg->op.right);
+ switch (arg->op.op[0]) {
+ case '!':
+ switch (arg->op.op[1]) {
+ case 0:
+ val = !right;
+ break;
+ case '=':
+ val = left != right;
+ break;
+ default:
+ goto out_warning_op;
+ }
+ break;
+ case '~':
+ val = ~right;
+ break;
+ case '|':
+ if (arg->op.op[1])
+ val = left || right;
+ else
+ val = left | right;
+ break;
+ case '&':
+ if (arg->op.op[1])
+ val = left && right;
+ else
+ val = left & right;
+ break;
+ case '<':
+ switch (arg->op.op[1]) {
+ case 0:
+ val = left < right;
+ break;
+ case '<':
+ val = left << right;
+ break;
+ case '=':
+ val = left <= right;
+ break;
+ default:
+ goto out_warning_op;
+ }
+ break;
+ case '>':
+ switch (arg->op.op[1]) {
+ case 0:
+ val = left > right;
+ break;
+ case '>':
+ val = left >> right;
+ break;
+ case '=':
+ val = left >= right;
+ break;
+ default:
+ goto out_warning_op;
+ }
+ break;
+ case '=':
+ if (arg->op.op[1] != '=')
+ goto out_warning_op;
+
+ val = left == right;
+ break;
+ case '-':
+ val = left - right;
+ break;
+ case '+':
+ val = left + right;
+ break;
+ case '/':
+ val = left / right;
+ break;
+ case '%':
+ val = left % right;
+ break;
+ case '*':
+ val = left * right;
+ break;
+ default:
+ goto out_warning_op;
+ }
+ break;
+ case TEP_PRINT_DYNAMIC_ARRAY_LEN:
+ dynamic_offset_field(tep, arg->dynarray.field, data, size,
+ NULL, &field_size);
+ val = field_size;
+ break;
+ case TEP_PRINT_DYNAMIC_ARRAY:
+ /* Without [], we pass the address to the dynamic data */
+ dynamic_offset_field(tep, arg->dynarray.field, data, size,
+ &offset, NULL);
+ if (check_data_offset_size(event, arg->field.name, size,
+ offset, 0)) {
+ val = (unsigned long)data;
+ break;
+ }
+ val = (unsigned long)data + offset;
+ break;
+ default: /* not sure what to do there */
+ return 0;
+ }
+ return val;
+
+out_warning_op:
+ do_warning_event(event, "%s: unknown op '%s'", __func__, arg->op.op);
+ return 0;
+
+out_warning_field:
+ do_warning_event(event, "%s: field %s not found",
+ __func__, arg->field.name);
+ return 0;
+}
+
+struct flag {
+ const char *name;
+ unsigned long long value;
+};
+
+static const struct flag flags[] = {
+ { "HI_SOFTIRQ", 0 },
+ { "TIMER_SOFTIRQ", 1 },
+ { "NET_TX_SOFTIRQ", 2 },
+ { "NET_RX_SOFTIRQ", 3 },
+ { "BLOCK_SOFTIRQ", 4 },
+ { "IRQ_POLL_SOFTIRQ", 5 },
+ { "TASKLET_SOFTIRQ", 6 },
+ { "SCHED_SOFTIRQ", 7 },
+ { "HRTIMER_SOFTIRQ", 8 },
+ { "RCU_SOFTIRQ", 9 },
+
+ { "HRTIMER_NORESTART", 0 },
+ { "HRTIMER_RESTART", 1 },
+};
+
+static long long eval_flag(const char *flag)
+{
+ int i;
+
+ /*
+ * Some flags in the format files do not get converted.
+ * If the flag is not numeric, see if it is something that
+ * we already know about.
+ */
+ if (isdigit(flag[0]))
+ return strtoull(flag, NULL, 0);
+
+ for (i = 0; i < (int)(sizeof(flags)/sizeof(flags[0])); i++)
+ if (strcmp(flags[i].name, flag) == 0)
+ return flags[i].value;
+
+ return -1LL;
+}
+
+static void print_str_to_seq(struct trace_seq *s, const char *format,
+ int len_arg, const char *str)
+{
+ if (len_arg >= 0)
+ trace_seq_printf(s, format, len_arg, str);
+ else
+ trace_seq_printf(s, format, str);
+}
+
+static void print_bitmask_to_seq(struct tep_handle *tep,
+ struct trace_seq *s, const char *format,
+ int len_arg, const void *data, int size)
+{
+ int nr_bits = size * 8;
+ int str_size = (nr_bits + 3) / 4;
+ int len = 0;
+ char buf[3];
+ char *str;
+ int index;
+ int i;
+
+ /*
+ * The kernel likes to put in commas every 32 bits, we
+ * can do the same.
+ */
+ str_size += (nr_bits - 1) / 32;
+
+ str = malloc(str_size + 1);
+ if (!str) {
+ do_warning("%s: not enough memory!", __func__);
+ return;
+ }
+ str[str_size] = 0;
+
+ /* Start out with -2 for the two chars per byte */
+ for (i = str_size - 2; i >= 0; i -= 2) {
+ /*
+ * data points to a bit mask of size bytes.
+ * In the kernel, this is an array of long words, thus
+ * endianness is very important.
+ */
+ if (tep->file_bigendian)
+ index = size - (len + 1);
+ else
+ index = len;
+
+ snprintf(buf, 3, "%02x", *((unsigned char *)data + index));
+ memcpy(str + i, buf, 2);
+ len++;
+ if (!(len & 3) && i > 0) {
+ i--;
+ str[i] = ',';
+ }
+ }
+
+ if (len_arg >= 0)
+ trace_seq_printf(s, format, len_arg, str);
+ else
+ trace_seq_printf(s, format, str);
+
+ free(str);
+}
+
+#define log10(n) \
+( \
+ n < 10UL ? 0 : \
+ n < 100UL ? 1 : \
+ n < 1000UL ? 2 : \
+ n < 10000UL ? 3 : \
+ n < 100000UL ? 4 : \
+ n < 1000000UL ? 5 : \
+ n < 10000000UL ? 6 : \
+ n < 100000000UL ? 7 : \
+ n < 1000000000UL ? 8 : \
+ 9 \
+)
+
+/* ilog10(0) should be 1 but the 0 simplifies below math */
+#define ilog10(n) \
+( \
+ n == 0 ? 0UL : \
+ n == 1 ? 10UL : \
+ n == 2 ? 100UL : \
+ n == 3 ? 1000UL : \
+ n == 4 ? 10000UL : \
+ n == 5 ? 100000UL : \
+ n == 6 ? 1000000UL : \
+ n == 7 ? 10000000UL : \
+ n == 8 ? 100000000UL : \
+ 1000000000UL \
+)
+
+static unsigned int cpumask_worst_size(unsigned int nr_bits)
+{
+ /*
+ * Printing all the CPUs separated by a comma is a decent bound for the
+ * maximum memory required to print a cpumask (a slightly better bound
+ * is chunks of 2 bits set, i.e. 0-1,3-4,6-7...).
+ *
+ * e.g. for nr_bits=132:
+ * - 131 commas
+ * - 10 * 1 chars for CPUS [0, 9]
+ * - 90 * 2 chars for CPUS [10-99]
+ * - 32 * 3 chars for CPUS [100-131]
+ */
+ unsigned int last_cpu = nr_bits - 1;
+ unsigned int nr_chars = nr_bits - 1;
+ int last_lvl = log10(last_cpu);
+
+ /* All log10 levels before the last one have all values used */
+ for (int lvl = 0; lvl < last_lvl; lvl++) {
+ int nr_values = ilog10(lvl + 1) - ilog10(lvl);
+
+ nr_chars += nr_values * (lvl + 1);
+ }
+ /* Last level is incomplete */
+ nr_chars += (nr_bits - ilog10(last_lvl)) * (last_lvl + 1);
+
+ return nr_chars;
+}
+
+static void print_cpumask_to_seq(struct tep_handle *tep,
+ struct trace_seq *s, const char *format,
+ int len_arg, const void *data, int size)
+{
+ int firstone = -1, firstzero = -1;
+ int nr_bits = size * 8;
+ bool first = true;
+ int str_size = 0;
+ char buf[12]; /* '-' + log10(2^32) + 1 digits + '\0' */
+ char *str;
+ int index;
+ int i;
+
+ str = malloc(cpumask_worst_size(nr_bits) + 1);
+ if (!str) {
+ do_warning("%s: not enough memory!", __func__);
+ return;
+ }
+
+ for (i = 0; i < size; i++) {
+ unsigned char byte;
+ int fmtsize;
+
+ if (tep->file_bigendian)
+ index = size - (i + 1);
+ else
+ index = i;
+
+ /* Byte by byte scan, not the best... */
+ byte = *(((unsigned char *)data) + index);
+more:
+ /* First find a bit set to one...*/
+ if (firstone < 0 && byte) {
+ /*
+ * Set all lower bits, so a later ffz on this same byte
+ * is guaranteed to find a later bit.
+ */
+ firstone = ffs(byte) - 1;
+ byte |= (1 << firstone) - 1;
+ firstone += i * 8;
+ }
+
+ if (firstone < 0)
+ continue;
+
+ /* ...Then find a bit set to zero */
+ if ((~byte) & 0xFF) {
+ /*
+ * Clear all lower bits, so a later ffs on this same
+ * byte is guaranteed to find a later bit.
+ */
+ firstzero = ffs(~byte) - 1;
+ byte &= ~((1 << (firstzero)) - 1);
+ firstzero += i * 8;
+ } else if (i == size - 1) { /* ...Or reach the end of the mask */
+ firstzero = nr_bits;
+ byte = 0;
+ } else {
+ continue;
+ }
+
+ /* We've found a bit set to one, and a later bit set to zero. */
+ if (!first) {
+ str[str_size] = ',';
+ str_size++;
+ }
+ first = false;
+
+ /* It takes {log10(number) + 1} chars to format a number */
+ fmtsize = log10(firstone) + 1;
+ snprintf(buf, fmtsize + 1, "%d", firstone);
+ memcpy(str + str_size, buf, fmtsize);
+ str_size += fmtsize;
+
+ if (firstzero > firstone + 1) {
+ fmtsize = log10(firstzero - 1) + 2;
+ snprintf(buf, fmtsize + 1, "-%d", firstzero - 1);
+ memcpy(str + str_size, buf, fmtsize);
+ str_size += fmtsize;
+ }
+
+ firstzero = firstone = -1;
+ if (byte)
+ goto more;
+ }
+
+ str[str_size] = 0;
+ str_size++;
+
+ if (len_arg >= 0)
+ trace_seq_printf(s, format, len_arg, str);
+ else
+ trace_seq_printf(s, format, str);
+
+ free(str);
+}
+
+static void print_str_arg(struct trace_seq *s, void *data, int size,
+ struct tep_event *event, const char *format,
+ int len_arg, struct tep_print_arg *arg)
+{
+ struct tep_handle *tep = event->tep;
+ struct tep_print_flag_sym *flag;
+ struct tep_format_field *field;
+ struct printk_map *printk;
+ unsigned int offset, len;
+ long long val, fval;
+ unsigned long long addr;
+ char *str;
+ unsigned char *hex;
+ int print;
+ int i;
+
+ switch (arg->type) {
+ case TEP_PRINT_NULL:
+ /* ?? */
+ return;
+ case TEP_PRINT_ATOM:
+ print_str_to_seq(s, format, len_arg, arg->atom.atom);
+ return;
+ case TEP_PRINT_FIELD:
+ field = arg->field.field;
+ if (!field) {
+ field = tep_find_any_field(event, arg->field.name);
+ if (!field) {
+ str = arg->field.name;
+ goto out_warning_field;
+ }
+ arg->field.field = field;
+ }
+ /* Zero sized fields, mean the rest of the data */
+ len = field->size ? : size - field->offset;
+
+ /*
+ * Some events pass in pointers. If this is not an array
+ * and the size is the same as long_size, assume that it
+ * is a pointer.
+ */
+ if (!(field->flags & TEP_FIELD_IS_ARRAY) &&
+ field->size == tep->long_size) {
+
+ /* Handle heterogeneous recording and processing
+ * architectures
+ *
+ * CASE I:
+ * Traces recorded on 32-bit devices (32-bit
+ * addressing) and processed on 64-bit devices:
+ * In this case, only 32 bits should be read.
+ *
+ * CASE II:
+ * Traces recorded on 64 bit devices and processed
+ * on 32-bit devices:
+ * In this case, 64 bits must be read.
+ */
+ addr = (tep->long_size == 8) ?
+ *(unsigned long long *)(data + field->offset) :
+ (unsigned long long)*(unsigned int *)(data + field->offset);
+
+ /* Check if it matches a print format */
+ printk = find_printk(tep, addr);
+ if (printk)
+ trace_seq_puts(s, printk->printk);
+ else
+ trace_seq_printf(s, "%llx", addr);
+ break;
+ }
+ str = malloc(len + 1);
+ if (!str) {
+ do_warning_event(event, "%s: not enough memory!",
+ __func__);
+ return;
+ }
+ memcpy(str, data + field->offset, len);
+ str[len] = 0;
+ print_str_to_seq(s, format, len_arg, str);
+ free(str);
+ break;
+ case TEP_PRINT_FLAGS:
+ val = eval_num_arg(data, size, event, arg->flags.field);
+ print = 0;
+ for (flag = arg->flags.flags; flag; flag = flag->next) {
+ fval = eval_flag(flag->value);
+ if (!val && fval < 0) {
+ print_str_to_seq(s, format, len_arg, flag->str);
+ break;
+ }
+ if (fval > 0 && (val & fval) == fval) {
+ if (print && arg->flags.delim)
+ trace_seq_puts(s, arg->flags.delim);
+ print_str_to_seq(s, format, len_arg, flag->str);
+ print = 1;
+ val &= ~fval;
+ }
+ }
+ if (val) {
+ if (print && arg->flags.delim)
+ trace_seq_puts(s, arg->flags.delim);
+ trace_seq_printf(s, "0x%llx", val);
+ }
+ break;
+ case TEP_PRINT_SYMBOL:
+ val = eval_num_arg(data, size, event, arg->symbol.field);
+ for (flag = arg->symbol.symbols; flag; flag = flag->next) {
+ fval = eval_flag(flag->value);
+ if (val == fval) {
+ print_str_to_seq(s, format, len_arg, flag->str);
+ break;
+ }
+ }
+ if (!flag)
+ trace_seq_printf(s, "0x%llx", val);
+ break;
+ case TEP_PRINT_HEX:
+ case TEP_PRINT_HEX_STR:
+ if (arg->hex.field->type == TEP_PRINT_DYNAMIC_ARRAY) {
+ dynamic_offset_field(tep, arg->hex.field->dynarray.field, data,
+ size, &offset, NULL);
+ hex = data + offset;
+ } else {
+ field = arg->hex.field->field.field;
+ if (!field) {
+ str = arg->hex.field->field.name;
+ field = tep_find_any_field(event, str);
+ if (!field)
+ goto out_warning_field;
+ arg->hex.field->field.field = field;
+ }
+ hex = data + field->offset;
+ }
+ len = eval_num_arg(data, size, event, arg->hex.size);
+ for (i = 0; i < len; i++) {
+ if (i && arg->type == TEP_PRINT_HEX)
+ trace_seq_putc(s, ' ');
+ trace_seq_printf(s, "%02x", hex[i]);
+ }
+ break;
+
+ case TEP_PRINT_INT_ARRAY: {
+ void *num;
+ int el_size;
+
+ if (arg->int_array.field->type == TEP_PRINT_DYNAMIC_ARRAY) {
+ dynamic_offset_field(tep, arg->int_array.field->dynarray.field, data,
+ size, &offset, NULL);
+ num = data + offset;
+ } else {
+ field = arg->int_array.field->field.field;
+ if (!field) {
+ str = arg->int_array.field->field.name;
+ field = tep_find_any_field(event, str);
+ if (!field)
+ goto out_warning_field;
+ arg->int_array.field->field.field = field;
+ }
+ num = data + field->offset;
+ }
+ len = eval_num_arg(data, size, event, arg->int_array.count);
+ el_size = eval_num_arg(data, size, event,
+ arg->int_array.el_size);
+ for (i = 0; i < len; i++) {
+ if (i)
+ trace_seq_putc(s, ' ');
+
+ if (el_size == 1) {
+ trace_seq_printf(s, "%u", *(uint8_t *)num);
+ } else if (el_size == 2) {
+ trace_seq_printf(s, "%u", *(uint16_t *)num);
+ } else if (el_size == 4) {
+ trace_seq_printf(s, "%u", *(uint32_t *)num);
+ } else if (el_size == 8) {
+ trace_seq_printf(s, "%"PRIu64, *(uint64_t *)num);
+ } else {
+ trace_seq_printf(s, "BAD SIZE:%d 0x%x",
+ el_size, *(uint8_t *)num);
+ el_size = 1;
+ }
+
+ num += el_size;
+ }
+ break;
+ }
+ case TEP_PRINT_TYPE:
+ break;
+ case TEP_PRINT_STRING: {
+ if (!arg->string.field) {
+ arg->string.field = tep_find_any_field(event, arg->string.string);
+ if (!arg->string.field)
+ break;
+ arg->string.offset = arg->string.field->offset;
+ }
+ dynamic_offset_field(tep, arg->string.field, data, size, &offset, &len);
+ /* Do not attempt to save zero length dynamic strings */
+ if (!len)
+ break;
+ print_str_to_seq(s, format, len_arg, ((char *)data) + offset);
+ break;
+ }
+ case TEP_PRINT_BSTRING:
+ print_str_to_seq(s, format, len_arg, arg->string.string);
+ break;
+ case TEP_PRINT_BITMASK: {
+ if (!arg->bitmask.field) {
+ arg->bitmask.field = tep_find_any_field(event, arg->bitmask.bitmask);
+ if (!arg->bitmask.field)
+ break;
+ arg->bitmask.offset = arg->bitmask.field->offset;
+ }
+ dynamic_offset_field(tep, arg->bitmask.field, data, size, &offset, &len);
+ print_bitmask_to_seq(tep, s, format, len_arg,
+ data + offset, len);
+ break;
+ }
+ case TEP_PRINT_CPUMASK: {
+ if (!arg->bitmask.field) {
+ arg->bitmask.field = tep_find_any_field(event, arg->bitmask.bitmask);
+ arg->bitmask.offset = arg->bitmask.field->offset;
+ }
+ if (!arg->bitmask.field)
+ break;
+ dynamic_offset_field(tep, arg->bitmask.field, data, size, &offset, &len);
+ print_cpumask_to_seq(tep, s, format, len_arg,
+ data + offset, len);
+ break;
+ }
+ case TEP_PRINT_OP:
+ /*
+ * The only op for string should be ? :
+ */
+ if (arg->op.op[0] != '?')
+ return;
+ val = eval_num_arg(data, size, event, arg->op.left);
+ if (val)
+ print_str_arg(s, data, size, event,
+ format, len_arg, arg->op.right->op.left);
+ else
+ print_str_arg(s, data, size, event,
+ format, len_arg, arg->op.right->op.right);
+ break;
+ case TEP_PRINT_FUNC:
+ process_defined_func(s, data, size, event, arg);
+ break;
+ default:
+ /* well... */
+ break;
+ }
+
+ return;
+
+out_warning_field:
+ do_warning_event(event, "%s: field %s not found",
+ __func__, arg->field.name);
+}
+
+static unsigned long long
+process_defined_func(struct trace_seq *s, void *data, int size,
+ struct tep_event *event, struct tep_print_arg *arg)
+{
+ struct tep_function_handler *func_handle = arg->func.func;
+ struct func_params *param;
+ unsigned long long *args;
+ unsigned long long ret;
+ struct tep_print_arg *farg;
+ struct trace_seq str;
+ struct save_str {
+ struct save_str *next;
+ char *str;
+ } *strings = NULL, *string;
+ int i;
+
+ if (!func_handle->nr_args) {
+ ret = (*func_handle->func)(s, NULL);
+ goto out;
+ }
+
+ farg = arg->func.args;
+ param = func_handle->params;
+
+ ret = ULLONG_MAX;
+ args = malloc(sizeof(*args) * func_handle->nr_args);
+ if (!args)
+ goto out;
+
+ for (i = 0; i < func_handle->nr_args; i++) {
+ switch (param->type) {
+ case TEP_FUNC_ARG_INT:
+ case TEP_FUNC_ARG_LONG:
+ case TEP_FUNC_ARG_PTR:
+ args[i] = eval_num_arg(data, size, event, farg);
+ break;
+ case TEP_FUNC_ARG_STRING:
+ trace_seq_init(&str);
+ print_str_arg(&str, data, size, event, "%s", -1, farg);
+ trace_seq_terminate(&str);
+ string = malloc(sizeof(*string));
+ if (!string) {
+ do_warning_event(event, "%s(%d): malloc str",
+ __func__, __LINE__);
+ goto out_free;
+ }
+ string->next = strings;
+ string->str = strdup(str.buffer);
+ if (!string->str) {
+ free(string);
+ do_warning_event(event, "%s(%d): malloc str",
+ __func__, __LINE__);
+ goto out_free;
+ }
+ args[i] = (uintptr_t)string->str;
+ strings = string;
+ trace_seq_destroy(&str);
+ break;
+ default:
+ /*
+ * Something went totally wrong, this is not
+ * an input error, something in this code broke.
+ */
+ do_warning_event(event, "Unexpected end of arguments\n");
+ goto out_free;
+ }
+ farg = farg->next;
+ param = param->next;
+ }
+
+ ret = (*func_handle->func)(s, args);
+out_free:
+ free(args);
+ while (strings) {
+ string = strings;
+ strings = string->next;
+ free(string->str);
+ free(string);
+ }
+
+ out:
+ /* TBD : handle return type here */
+ return ret;
+}
+
+static void free_args(struct tep_print_arg *args)
+{
+ struct tep_print_arg *next;
+
+ while (args) {
+ next = args->next;
+
+ free_arg(args);
+ args = next;
+ }
+}
+
+static struct tep_print_arg *make_bprint_args(char *fmt, void *data, int size, struct tep_event *event)
+{
+ struct tep_handle *tep = event->tep;
+ struct tep_format_field *field, *ip_field;
+ struct tep_print_arg *args, *arg, **next;
+ unsigned long long ip, val;
+ char *ptr;
+ void *bptr;
+ int vsize = 0;
+
+ field = tep->bprint_buf_field;
+ ip_field = tep->bprint_ip_field;
+
+ if (!field) {
+ field = tep_find_field(event, "buf");
+ if (!field) {
+ do_warning_event(event, "can't find buffer field for binary printk");
+ return NULL;
+ }
+ ip_field = tep_find_field(event, "ip");
+ if (!ip_field) {
+ do_warning_event(event, "can't find ip field for binary printk");
+ return NULL;
+ }
+ tep->bprint_buf_field = field;
+ tep->bprint_ip_field = ip_field;
+ }
+
+ ip = tep_read_number(tep, data + ip_field->offset, ip_field->size);
+
+ /*
+ * The first arg is the IP pointer.
+ */
+ args = alloc_arg();
+ if (!args) {
+ do_warning_event(event, "%s(%d): not enough memory!",
+ __func__, __LINE__);
+ return NULL;
+ }
+ arg = args;
+ arg->next = NULL;
+ next = &arg->next;
+
+ arg->type = TEP_PRINT_ATOM;
+
+ if (asprintf(&arg->atom.atom, "%lld", ip) < 0)
+ goto out_free;
+
+ /* skip the first "%ps: " */
+ for (ptr = fmt + 5, bptr = data + field->offset;
+ bptr < data + size && *ptr; ptr++) {
+ int ls = 0;
+
+ if (*ptr == '%') {
+ process_again:
+ ptr++;
+ switch (*ptr) {
+ case '%':
+ break;
+ case 'l':
+ ls++;
+ goto process_again;
+ case 'L':
+ ls = 2;
+ goto process_again;
+ case '0' ... '9':
+ goto process_again;
+ case '.':
+ goto process_again;
+ case '#':
+ goto process_again;
+ case 'z':
+ case 'Z':
+ ls = 1;
+ goto process_again;
+ case 'p':
+ ls = 1;
+ if (isalnum(ptr[1])) {
+ ptr++;
+ /* Check for special pointers */
+ switch (*ptr) {
+ case 's':
+ case 'S':
+ case 'x':
+ break;
+ case 'f':
+ case 'F':
+ /*
+ * Pre-5.5 kernels use %pf and
+ * %pF for printing symbols
+ * while kernels since 5.5 use
+ * %pfw for fwnodes. So check
+ * %p[fF] isn't followed by 'w'.
+ */
+ if (ptr[1] != 'w')
+ break;
+ /* fall through */
+ default:
+ /*
+ * Older kernels do not process
+ * dereferenced pointers.
+ * Only process if the pointer
+ * value is a printable.
+ */
+ if (isprint(*(char *)bptr))
+ goto process_string;
+ }
+ }
+ /* fall through */
+ case 'd':
+ case 'u':
+ case 'i':
+ case 'x':
+ case 'X':
+ case 'o':
+ switch (ls) {
+ case 0:
+ vsize = 4;
+ break;
+ case 1:
+ vsize = tep->long_size;
+ break;
+ case 2:
+ vsize = 8;
+ break;
+ default:
+ vsize = ls; /* ? */
+ break;
+ }
+ /* fall through */
+ case '*':
+ if (*ptr == '*')
+ vsize = 4;
+
+ /* the pointers are always 4 bytes aligned */
+ bptr = (void *)(((unsigned long)bptr + 3) &
+ ~3);
+ val = tep_read_number(tep, bptr, vsize);
+ bptr += vsize;
+ arg = alloc_arg();
+ if (!arg) {
+ do_warning_event(event, "%s(%d): not enough memory!",
+ __func__, __LINE__);
+ goto out_free;
+ }
+ arg->next = NULL;
+ arg->type = TEP_PRINT_ATOM;
+ if (asprintf(&arg->atom.atom, "%lld", val) < 0) {
+ free(arg);
+ goto out_free;
+ }
+ *next = arg;
+ next = &arg->next;
+ /*
+ * The '*' case means that an arg is used as the length.
+ * We need to continue to figure out for what.
+ */
+ if (*ptr == '*')
+ goto process_again;
+
+ break;
+ case 's':
+ process_string:
+ arg = alloc_arg();
+ if (!arg) {
+ do_warning_event(event, "%s(%d): not enough memory!",
+ __func__, __LINE__);
+ goto out_free;
+ }
+ arg->next = NULL;
+ arg->type = TEP_PRINT_BSTRING;
+ arg->string.string = strdup(bptr);
+ if (!arg->string.string) {
+ free(arg);
+ goto out_free;
+ }
+ bptr += strlen(bptr) + 1;
+ *next = arg;
+ next = &arg->next;
+ default:
+ break;
+ }
+ }
+ }
+
+ return args;
+
+out_free:
+ free_args(args);
+ return NULL;
+}
+
+static char *
+get_bprint_format(void *data, int size __maybe_unused,
+ struct tep_event *event)
+{
+ struct tep_handle *tep = event->tep;
+ unsigned long long addr;
+ struct tep_format_field *field;
+ struct printk_map *printk;
+ char *format;
+
+ field = tep->bprint_fmt_field;
+
+ if (!field) {
+ field = tep_find_field(event, "fmt");
+ if (!field) {
+ do_warning_event(event, "can't find format field for binary printk");
+ return NULL;
+ }
+ tep->bprint_fmt_field = field;
+ }
+
+ addr = tep_read_number(tep, data + field->offset, field->size);
+
+ printk = find_printk(tep, addr);
+ if (!printk) {
+ if (asprintf(&format, "%%ps: (NO FORMAT FOUND at %llx)\n", addr) < 0)
+ return NULL;
+ return format;
+ }
+
+ if (asprintf(&format, "%s: %s", "%ps", printk->printk) < 0)
+ return NULL;
+
+ return format;
+}
+
+static int print_mac_arg(struct trace_seq *s, const char *format,
+ void *data, int size, struct tep_event *event,
+ struct tep_print_arg *arg)
+{
+ const char *fmt = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x";
+ bool reverse = false;
+ unsigned char *buf;
+ int ret = 0;
+
+ if (arg->type == TEP_PRINT_FUNC) {
+ process_defined_func(s, data, size, event, arg);
+ return 0;
+ }
+
+ /* evaluate if the arg has a type cast */
+ while (arg->type == TEP_PRINT_TYPE)
+ arg = arg->typecast.item;
+
+ if (arg->type != TEP_PRINT_FIELD) {
+ trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d",
+ arg->type);
+ return 0;
+ }
+
+ if (format[0] == 'm') {
+ fmt = "%.2x%.2x%.2x%.2x%.2x%.2x";
+ } else if (format[0] == 'M' && format[1] == 'F') {
+ fmt = "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x";
+ ret++;
+ }
+ if (format[1] == 'R') {
+ reverse = true;
+ ret++;
+ }
+
+ if (!arg->field.field) {
+ arg->field.field =
+ tep_find_any_field(event, arg->field.name);
+ if (!arg->field.field) {
+ do_warning_event(event, "%s: field %s not found",
+ __func__, arg->field.name);
+ return ret;
+ }
+ }
+ if (arg->field.field->size != 6) {
+ trace_seq_printf(s, "INVALIDMAC");
+ return ret;
+ }
+
+ buf = data + arg->field.field->offset;
+ if (reverse)
+ trace_seq_printf(s, fmt, buf[5], buf[4], buf[3], buf[2], buf[1], buf[0]);
+ else
+ trace_seq_printf(s, fmt, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
+
+ return ret;
+}
+
+static int parse_ip4_print_args(struct tep_handle *tep,
+ const char *ptr, bool *reverse)
+{
+ int ret = 0;
+
+ *reverse = false;
+
+ /* hnbl */
+ switch (*ptr) {
+ case 'h':
+ if (tep->file_bigendian)
+ *reverse = false;
+ else
+ *reverse = true;
+ ret++;
+ break;
+ case 'l':
+ *reverse = true;
+ ret++;
+ break;
+ case 'n':
+ case 'b':
+ ret++;
+ /* fall through */
+ default:
+ *reverse = false;
+ break;
+ }
+
+ return ret;
+}
+
+static void print_ip4_addr(struct trace_seq *s, char i, bool reverse, unsigned char *buf)
+{
+ const char *fmt;
+
+ if (i == 'i')
+ fmt = "%03d.%03d.%03d.%03d";
+ else
+ fmt = "%d.%d.%d.%d";
+
+ if (reverse)
+ trace_seq_printf(s, fmt, buf[3], buf[2], buf[1], buf[0]);
+ else
+ trace_seq_printf(s, fmt, buf[0], buf[1], buf[2], buf[3]);
+
+}
+
+static inline bool ipv6_addr_v4mapped(const struct in6_addr *a)
+{
+ return ((unsigned long)(a->s6_addr32[0] | a->s6_addr32[1]) |
+ (unsigned long)(a->s6_addr32[2] ^ htonl(0x0000ffff))) == 0UL;
+}
+
+static inline bool ipv6_addr_is_isatap(const struct in6_addr *addr)
+{
+ return (addr->s6_addr32[2] | htonl(0x02000000)) == htonl(0x02005EFE);
+}
+
+static void print_ip6c_addr(struct trace_seq *s, unsigned char *addr)
+{
+ int i, j, range;
+ unsigned char zerolength[8];
+ int longest = 1;
+ int colonpos = -1;
+ uint16_t word;
+ uint8_t hi, lo;
+ bool needcolon = false;
+ bool useIPv4;
+ struct in6_addr in6;
+
+ memcpy(&in6, addr, sizeof(struct in6_addr));
+
+ useIPv4 = ipv6_addr_v4mapped(&in6) || ipv6_addr_is_isatap(&in6);
+
+ memset(zerolength, 0, sizeof(zerolength));
+
+ if (useIPv4)
+ range = 6;
+ else
+ range = 8;
+
+ /* find position of longest 0 run */
+ for (i = 0; i < range; i++) {
+ for (j = i; j < range; j++) {
+ if (in6.s6_addr16[j] != 0)
+ break;
+ zerolength[i]++;
+ }
+ }
+ for (i = 0; i < range; i++) {
+ if (zerolength[i] > longest) {
+ longest = zerolength[i];
+ colonpos = i;
+ }
+ }
+ if (longest == 1) /* don't compress a single 0 */
+ colonpos = -1;
+
+ /* emit address */
+ for (i = 0; i < range; i++) {
+ if (i == colonpos) {
+ if (needcolon || i == 0)
+ trace_seq_printf(s, ":");
+ trace_seq_printf(s, ":");
+ needcolon = false;
+ i += longest - 1;
+ continue;
+ }
+ if (needcolon) {
+ trace_seq_printf(s, ":");
+ needcolon = false;
+ }
+ /* hex u16 without leading 0s */
+ word = ntohs(in6.s6_addr16[i]);
+ hi = word >> 8;
+ lo = word & 0xff;
+ if (hi)
+ trace_seq_printf(s, "%x%02x", hi, lo);
+ else
+ trace_seq_printf(s, "%x", lo);
+
+ needcolon = true;
+ }
+
+ if (useIPv4) {
+ if (needcolon)
+ trace_seq_printf(s, ":");
+ print_ip4_addr(s, 'I', false, &in6.s6_addr[12]);
+ }
+
+ return;
+}
+
+static void print_ip6_addr(struct trace_seq *s, char i, unsigned char *buf)
+{
+ int j;
+
+ for (j = 0; j < 16; j += 2) {
+ trace_seq_printf(s, "%02x%02x", buf[j], buf[j+1]);
+ if (i == 'I' && j < 14)
+ trace_seq_printf(s, ":");
+ }
+}
+
+/*
+ * %pi4 print an IPv4 address with leading zeros
+ * %pI4 print an IPv4 address without leading zeros
+ * %pi6 print an IPv6 address without colons
+ * %pI6 print an IPv6 address with colons
+ * %pI6c print an IPv6 address in compressed form with colons
+ * %pISpc print an IP address based on sockaddr; p adds port.
+ */
+static int print_ipv4_arg(struct trace_seq *s, const char *ptr, char i,
+ void *data, int size, struct tep_event *event,
+ struct tep_print_arg *arg)
+{
+ bool reverse = false;
+ unsigned char *buf;
+ int ret;
+
+ ret = parse_ip4_print_args(event->tep, ptr, &reverse);
+
+ if (arg->type == TEP_PRINT_FUNC) {
+ process_defined_func(s, data, size, event, arg);
+ return ret;
+ }
+
+ /* evaluate if the arg has a type cast */
+ while (arg->type == TEP_PRINT_TYPE)
+ arg = arg->typecast.item;
+
+ if (arg->type != TEP_PRINT_FIELD) {
+ trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type);
+ return ret;
+ }
+
+ if (!arg->field.field) {
+ arg->field.field =
+ tep_find_any_field(event, arg->field.name);
+ if (!arg->field.field) {
+ do_warning("%s: field %s not found",
+ __func__, arg->field.name);
+ return ret;
+ }
+ }
+
+ buf = data + arg->field.field->offset;
+
+ if (arg->field.field->size != 4) {
+ trace_seq_printf(s, "INVALIDIPv4");
+ return ret;
+ }
+
+ print_ip4_addr(s, i, reverse, buf);
+ return ret;
+
+}
+
+static int print_ipv6_arg(struct trace_seq *s, const char *ptr, char i,
+ void *data, int size, struct tep_event *event,
+ struct tep_print_arg *arg)
+{
+ char have_c = 0;
+ unsigned char *buf;
+ int rc = 0;
+
+ /* pI6c */
+ if (i == 'I' && *ptr == 'c') {
+ have_c = 1;
+ ptr++;
+ rc++;
+ }
+
+ if (arg->type == TEP_PRINT_FUNC) {
+ process_defined_func(s, data, size, event, arg);
+ return rc;
+ }
+
+ /* evaluate if the arg has a type cast */
+ while (arg->type == TEP_PRINT_TYPE)
+ arg = arg->typecast.item;
+
+ if (arg->type != TEP_PRINT_FIELD) {
+ trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type);
+ return rc;
+ }
+
+ if (!arg->field.field) {
+ arg->field.field =
+ tep_find_any_field(event, arg->field.name);
+ if (!arg->field.field) {
+ do_warning("%s: field %s not found",
+ __func__, arg->field.name);
+ return rc;
+ }
+ }
+
+ buf = data + arg->field.field->offset;
+
+ if (arg->field.field->size != 16) {
+ trace_seq_printf(s, "INVALIDIPv6");
+ return rc;
+ }
+
+ if (have_c)
+ print_ip6c_addr(s, buf);
+ else
+ print_ip6_addr(s, i, buf);
+
+ return rc;
+}
+
+static int print_ipsa_arg(struct trace_seq *s, const char *ptr, char i,
+ void *data, int size, struct tep_event *event,
+ struct tep_print_arg *arg)
+{
+ char have_c = 0, have_p = 0;
+ unsigned char *buf;
+ struct sockaddr_storage *sa;
+ bool reverse = false;
+ unsigned int offset;
+ unsigned int len;
+ int rc = 0;
+ int ret;
+
+ /* pISpc */
+ if (i == 'I') {
+ if (*ptr == 'p') {
+ have_p = 1;
+ ptr++;
+ rc++;
+ }
+ if (*ptr == 'c') {
+ have_c = 1;
+ ptr++;
+ rc++;
+ }
+ }
+ ret = parse_ip4_print_args(event->tep, ptr, &reverse);
+ ptr += ret;
+ rc += ret;
+
+ if (arg->type == TEP_PRINT_FUNC) {
+ process_defined_func(s, data, size, event, arg);
+ return rc;
+ }
+
+ /* evaluate if the arg has a type cast */
+ while (arg->type == TEP_PRINT_TYPE)
+ arg = arg->typecast.item;
+
+ if (arg->type == TEP_PRINT_FIELD) {
+
+ if (!arg->field.field) {
+ arg->field.field =
+ tep_find_any_field(event, arg->field.name);
+ if (!arg->field.field) {
+ do_warning("%s: field %s not found",
+ __func__, arg->field.name);
+ return rc;
+ }
+ }
+
+ offset = arg->field.field->offset;
+ len = arg->field.field->size;
+
+ } else if (arg->type == TEP_PRINT_DYNAMIC_ARRAY) {
+
+ dynamic_offset_field(event->tep, arg->dynarray.field, data,
+ size, &offset, &len);
+
+ } else {
+ trace_seq_printf(s, "ARG NOT FIELD NOR DYNAMIC ARRAY BUT TYPE %d",
+ arg->type);
+ return rc;
+ }
+
+ sa = (struct sockaddr_storage *)(data + offset);
+
+ if (sa->ss_family == AF_INET) {
+ struct sockaddr_in *sa4 = (struct sockaddr_in *) sa;
+
+ if (len < sizeof(struct sockaddr_in)) {
+ trace_seq_printf(s, "INVALIDIPv4");
+ return rc;
+ }
+
+ print_ip4_addr(s, i, reverse, (unsigned char *) &sa4->sin_addr);
+ if (have_p)
+ trace_seq_printf(s, ":%d", ntohs(sa4->sin_port));
+
+
+ } else if (sa->ss_family == AF_INET6) {
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *) sa;
+
+ if (len < sizeof(struct sockaddr_in6)) {
+ trace_seq_printf(s, "INVALIDIPv6");
+ return rc;
+ }
+
+ if (have_p)
+ trace_seq_printf(s, "[");
+
+ buf = (unsigned char *) &sa6->sin6_addr;
+ if (have_c)
+ print_ip6c_addr(s, buf);
+ else
+ print_ip6_addr(s, i, buf);
+
+ if (have_p)
+ trace_seq_printf(s, "]:%d", ntohs(sa6->sin6_port));
+ }
+
+ return rc;
+}
+
+static int print_ip_arg(struct trace_seq *s, const char *ptr,
+ void *data, int size, struct tep_event *event,
+ struct tep_print_arg *arg)
+{
+ char i = *ptr; /* 'i' or 'I' */
+ int rc = 1;
+
+ /* IP version */
+ ptr++;
+
+ switch (*ptr) {
+ case '4':
+ rc += print_ipv4_arg(s, ptr + 1, i, data, size, event, arg);
+ break;
+ case '6':
+ rc += print_ipv6_arg(s, ptr + 1, i, data, size, event, arg);
+ break;
+ case 'S':
+ rc += print_ipsa_arg(s, ptr + 1, i, data, size, event, arg);
+ break;
+ default:
+ return 0;
+ }
+
+ return rc;
+}
+
+static const int guid_index[16] = {3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15};
+static const int uuid_index[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+static int print_uuid_arg(struct trace_seq *s, const char *ptr,
+ void *data, int size, struct tep_event *event,
+ struct tep_print_arg *arg)
+{
+ const int *index = uuid_index;
+ char *format = "%02x";
+ int ret = 0;
+ char *buf;
+ int i;
+
+ switch (*(ptr + 1)) {
+ case 'L':
+ format = "%02X";
+ /* fall through */
+ case 'l':
+ index = guid_index;
+ ret++;
+ break;
+ case 'B':
+ format = "%02X";
+ /* fall through */
+ case 'b':
+ ret++;
+ break;
+ }
+
+ if (arg->type == TEP_PRINT_FUNC) {
+ process_defined_func(s, data, size, event, arg);
+ return ret;
+ }
+
+ /* evaluate if the arg has a type cast */
+ while (arg->type == TEP_PRINT_TYPE)
+ arg = arg->typecast.item;
+
+ if (arg->type != TEP_PRINT_FIELD) {
+ trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type);
+ return ret;
+ }
+
+ if (!arg->field.field) {
+ arg->field.field =
+ tep_find_any_field(event, arg->field.name);
+ if (!arg->field.field) {
+ do_warning("%s: field %s not found",
+ __func__, arg->field.name);
+ return ret;
+ }
+ }
+
+ if (arg->field.field->size != 16) {
+ trace_seq_printf(s, "INVALIDUUID");
+ return ret;
+ }
+
+ buf = data + arg->field.field->offset;
+
+ for (i = 0; i < 16; i++) {
+ trace_seq_printf(s, format, buf[index[i]] & 0xff);
+ switch (i) {
+ case 3:
+ case 5:
+ case 7:
+ case 9:
+ trace_seq_printf(s, "-");
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int print_raw_buff_arg(struct trace_seq *s, const char *ptr,
+ void *data, int size, struct tep_event *event,
+ struct tep_print_arg *arg, int print_len)
+{
+ unsigned int offset, arr_len;
+ int plen = print_len;
+ char *delim = " ";
+ int ret = 0;
+ char *buf;
+ int i;
+
+ switch (*(ptr + 1)) {
+ case 'C':
+ delim = ":";
+ ret++;
+ break;
+ case 'D':
+ delim = "-";
+ ret++;
+ break;
+ case 'N':
+ delim = "";
+ ret++;
+ break;
+ }
+
+ if (arg->type == TEP_PRINT_FUNC) {
+ process_defined_func(s, data, size, event, arg);
+ return ret;
+ }
+
+ if (arg->type != TEP_PRINT_DYNAMIC_ARRAY) {
+ trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type);
+ return ret;
+ }
+
+ dynamic_offset_field(event->tep, arg->dynarray.field, data, size,
+ &offset, &arr_len);
+ buf = data + offset;
+
+ if (arr_len < plen)
+ plen = arr_len;
+
+ if (plen < 1)
+ return ret;
+
+ trace_seq_printf(s, "%02x", buf[0] & 0xff);
+ for (i = 1; i < plen; i++)
+ trace_seq_printf(s, "%s%02x", delim, buf[i] & 0xff);
+
+ return ret;
+}
+
+static int is_printable_array(char *p, unsigned int len)
+{
+ unsigned int i;
+
+ for (i = 0; i < len && p[i]; i++)
+ if (!isprint(p[i]) && !isspace(p[i]))
+ return 0;
+ return 1;
+}
+
+static void print_field_raw(struct trace_seq *s, void *data, int size,
+ struct tep_format_field *field)
+{
+ struct tep_handle *tep = field->event->tep;
+ unsigned int offset, len, i;
+ unsigned long long val;
+
+ if (field->flags & TEP_FIELD_IS_ARRAY) {
+ if (field->flags & TEP_FIELD_IS_DYNAMIC) {
+ dynamic_offset_field(tep, field, data, size, &offset, &len);
+ } else {
+ offset = field->offset;
+ len = field->size;
+ }
+ if (field->flags & TEP_FIELD_IS_STRING &&
+ is_printable_array(data + offset, len)) {
+ trace_seq_printf(s, "%s", (char *)data + offset);
+ } else {
+ trace_seq_puts(s, "ARRAY[");
+ for (i = 0; i < len; i++) {
+ if (i)
+ trace_seq_puts(s, ", ");
+ trace_seq_printf(s, "%02x",
+ *((unsigned char *)data + offset + i));
+ }
+ trace_seq_putc(s, ']');
+ field->flags &= ~TEP_FIELD_IS_STRING;
+ }
+ } else {
+ val = tep_read_number(tep, data + field->offset,
+ field->size);
+ if (field->flags & TEP_FIELD_IS_POINTER) {
+ trace_seq_printf(s, "0x%llx", val);
+ } else if (field->flags & TEP_FIELD_IS_SIGNED) {
+ switch (field->size) {
+ case 4:
+ /*
+ * If field is long then print it in hex.
+ * A long usually stores pointers.
+ */
+ if (field->flags & TEP_FIELD_IS_LONG)
+ trace_seq_printf(s, "0x%x", (int)val);
+ else
+ trace_seq_printf(s, "%d", (int)val);
+ break;
+ case 2:
+ trace_seq_printf(s, "%2d", (short)val);
+ break;
+ case 1:
+ trace_seq_printf(s, "%1d", (char)val);
+ break;
+ default:
+ trace_seq_printf(s, "%lld", val);
+ }
+ } else {
+ if (field->flags & TEP_FIELD_IS_LONG)
+ trace_seq_printf(s, "0x%llx", val);
+ else
+ trace_seq_printf(s, "%llu", val);
+ }
+ }
+ trace_seq_terminate(s);
+}
+
+static int print_parse_data(struct tep_print_parse *parse, struct trace_seq *s,
+ void *data, int size, struct tep_event *event);
+
+static inline void print_field(struct trace_seq *s, void *data, int size,
+ struct tep_format_field *field,
+ struct tep_print_parse **parse_ptr)
+{
+ struct tep_event *event = field->event;
+ struct tep_print_parse *start_parse;
+ struct tep_print_parse *parse;
+ struct tep_print_arg *arg;
+ bool has_0x = false;
+
+ parse = parse_ptr ? *parse_ptr : event->print_fmt.print_cache;
+
+ if (!parse || event->flags & TEP_EVENT_FL_FAILED)
+ goto out;
+
+ if (field->flags & (TEP_FIELD_IS_ARRAY | TEP_FIELD_IS_STRING))
+ goto out;
+
+ start_parse = parse;
+ do {
+ if (parse->type == PRINT_FMT_STRING) {
+ int len = strlen(parse->format);
+
+ if (len > 1 &&
+ strcmp(parse->format + (len -2), "0x") == 0)
+ has_0x = true;
+ else
+ has_0x = false;
+
+ goto next;
+ }
+
+ arg = parse->arg;
+
+ while (arg && arg->type == TEP_PRINT_TYPE)
+ arg = arg->typecast.item;
+
+ if (!arg || arg->type != TEP_PRINT_FIELD ||
+ arg->field.field != field) {
+ has_0x = false;
+ goto next;
+ }
+
+ if (has_0x)
+ trace_seq_puts(s, "0x");
+
+ print_parse_data(parse, s, data, size, event);
+
+ if (parse_ptr)
+ *parse_ptr = parse->next;
+
+ return;
+
+ next:
+ parse = parse->next ? parse->next :
+ event->print_fmt.print_cache;
+ } while (parse != start_parse);
+
+ out:
+ /* Not found. */
+ print_field_raw(s, data, size, field);
+}
+
+/**
+ * tep_print_field_content - write out the raw content of a field
+ * @s: The trace_seq to write the content into
+ * @data: The payload to extract the field from.
+ * @size: The size of the payload.
+ * @field: The field to extract
+ *
+ * Use @field to find the field content from within @data and write it
+ * in human readable format into @s.
+ *
+ * It will not write anything on error (s->len will not move)
+ */
+void tep_print_field_content(struct trace_seq *s, void *data, int size,
+ struct tep_format_field *field)
+{
+ print_field(s, data, size, field, NULL);
+}
+
+/** DEPRECATED **/
+void tep_print_field(struct trace_seq *s, void *data,
+ struct tep_format_field *field)
+{
+ /* unsafe to use, should pass in size */
+ print_field(s, data, 4096, field, NULL);
+}
+
+static inline void
+print_selected_fields(struct trace_seq *s, void *data, int size,
+ struct tep_event *event,
+ unsigned long long ignore_mask)
+{
+ struct tep_print_parse *parse = event->print_fmt.print_cache;
+ struct tep_format_field *field;
+ unsigned long long field_mask = 1;
+
+ field = event->format.fields;
+ for(; field; field = field->next, field_mask <<= 1) {
+ if (field_mask & ignore_mask)
+ continue;
+
+ trace_seq_printf(s, " %s=", field->name);
+ print_field(s, data, size, field, &parse);
+ }
+}
+
+void tep_print_fields(struct trace_seq *s, void *data,
+ int size, struct tep_event *event)
+{
+ print_selected_fields(s, data, size, event, 0);
+}
+
+/**
+ * tep_record_print_fields - print the field name followed by the
+ * record's field value.
+ * @s: The seq to print to
+ * @record: The record to get the event from
+ * @event: The event that the field is for
+ */
+void tep_record_print_fields(struct trace_seq *s,
+ struct tep_record *record,
+ struct tep_event *event)
+{
+ print_selected_fields(s, record->data, record->size, event, 0);
+}
+
+/**
+ * tep_record_print_selected_fields - print the field name followed by the
+ * record's field value for a selected subset of record fields.
+ * @s: The seq to print to
+ * @record: The record to get the event from
+ * @event: The event that the field is for
+ * @select_mask: Bit mask defining the fields to print
+ */
+void tep_record_print_selected_fields(struct trace_seq *s,
+ struct tep_record *record,
+ struct tep_event *event,
+ unsigned long long select_mask)
+{
+ unsigned long long ignore_mask = ~select_mask;
+
+ print_selected_fields(s, record->data, record->size, event, ignore_mask);
+}
+
+static int print_function(struct trace_seq *s, const char *format,
+ void *data, int size, struct tep_event *event,
+ struct tep_print_arg *arg)
+{
+ struct func_map *func;
+ unsigned long long val;
+
+ val = eval_num_arg(data, size, event, arg);
+ func = find_func(event->tep, val);
+ if (func) {
+ trace_seq_puts(s, func->func);
+ if (*format == 'F' || *format == 'S')
+ trace_seq_printf(s, "+0x%llx", val - func->addr);
+ } else {
+ if (event->tep->long_size == 4)
+ trace_seq_printf(s, "0x%lx", (long)val);
+ else
+ trace_seq_printf(s, "0x%llx", (long long)val);
+ }
+
+ return 0;
+}
+
+static int print_arg_pointer(struct trace_seq *s, const char *format, int plen,
+ void *data, int size,
+ struct tep_event *event, struct tep_print_arg *arg)
+{
+ unsigned long long val;
+ int ret = 1;
+
+ if (arg->type == TEP_PRINT_BSTRING) {
+ trace_seq_puts(s, arg->string.string);
+ return 0;
+ }
+ while (*format) {
+ if (*format == 'p') {
+ format++;
+ break;
+ }
+ format++;
+ }
+
+ switch (*format) {
+ case 'F':
+ case 'f':
+ case 'S':
+ case 's':
+ ret += print_function(s, format, data, size, event, arg);
+ break;
+ case 'M':
+ case 'm':
+ ret += print_mac_arg(s, format, data, size, event, arg);
+ break;
+ case 'I':
+ case 'i':
+ ret += print_ip_arg(s, format, data, size, event, arg);
+ break;
+ case 'U':
+ ret += print_uuid_arg(s, format, data, size, event, arg);
+ break;
+ case 'h':
+ ret += print_raw_buff_arg(s, format, data, size, event, arg, plen);
+ break;
+ default:
+ ret = 0;
+ val = eval_num_arg(data, size, event, arg);
+ trace_seq_printf(s, "%p", (void *)(intptr_t)val);
+ break;
+ }
+
+ return ret;
+
+}
+
+static int print_arg_number(struct trace_seq *s, const char *format, int plen,
+ void *data, int size, int ls,
+ struct tep_event *event, struct tep_print_arg *arg)
+{
+ unsigned long long val;
+
+ val = eval_num_arg(data, size, event, arg);
+
+ switch (ls) {
+ case -2:
+ if (plen >= 0)
+ trace_seq_printf(s, format, plen, (char)val);
+ else
+ trace_seq_printf(s, format, (char)val);
+ break;
+ case -1:
+ if (plen >= 0)
+ trace_seq_printf(s, format, plen, (short)val);
+ else
+ trace_seq_printf(s, format, (short)val);
+ break;
+ case 0:
+ if (plen >= 0)
+ trace_seq_printf(s, format, plen, (int)val);
+ else
+ trace_seq_printf(s, format, (int)val);
+ break;
+ case 1:
+ if (plen >= 0)
+ trace_seq_printf(s, format, plen, (long)val);
+ else
+ trace_seq_printf(s, format, (long)val);
+ break;
+ case 2:
+ if (plen >= 0)
+ trace_seq_printf(s, format, plen, (long long)val);
+ else
+ trace_seq_printf(s, format, (long long)val);
+ break;
+ default:
+ do_warning_event(event, "bad count (%d)", ls);
+ event->flags |= TEP_EVENT_FL_FAILED;
+ }
+ return 0;
+}
+
+
+static void print_arg_string(struct trace_seq *s, const char *format, int plen,
+ void *data, int size,
+ struct tep_event *event, struct tep_print_arg *arg)
+{
+ struct trace_seq p;
+
+ /* Use helper trace_seq */
+ trace_seq_init(&p);
+ print_str_arg(&p, data, size, event,
+ format, plen, arg);
+ trace_seq_terminate(&p);
+ trace_seq_puts(s, p.buffer);
+ trace_seq_destroy(&p);
+}
+
+static int parse_arg_format_pointer(const char *format)
+{
+ int ret = 0;
+ int index;
+ int loop;
+
+ switch (*format) {
+ case 'F':
+ case 'S':
+ case 'f':
+ case 's':
+ ret++;
+ break;
+ case 'M':
+ case 'm':
+ /* [mM]R , [mM]F */
+ switch (format[1]) {
+ case 'R':
+ case 'F':
+ ret++;
+ break;
+ }
+ ret++;
+ break;
+ case 'I':
+ case 'i':
+ index = 2;
+ loop = 1;
+ switch (format[1]) {
+ case 'S':
+ /*[S][pfs]*/
+ while (loop) {
+ switch (format[index]) {
+ case 'p':
+ case 'f':
+ case 's':
+ ret++;
+ index++;
+ break;
+ default:
+ loop = 0;
+ break;
+ }
+ }
+ /* fall through */
+ case '4':
+ /* [4S][hnbl] */
+ switch (format[index]) {
+ case 'h':
+ case 'n':
+ case 'l':
+ case 'b':
+ ret++;
+ index++;
+ break;
+ }
+ if (format[1] == '4') {
+ ret++;
+ break;
+ }
+ /* fall through */
+ case '6':
+ /* [6S]c */
+ if (format[index] == 'c')
+ ret++;
+ ret++;
+ break;
+ }
+ ret++;
+ break;
+ case 'U':
+ switch (format[1]) {
+ case 'L':
+ case 'l':
+ case 'B':
+ case 'b':
+ ret++;
+ break;
+ }
+ ret++;
+ break;
+ case 'h':
+ switch (format[1]) {
+ case 'C':
+ case 'D':
+ case 'N':
+ ret++;
+ break;
+ }
+ ret++;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void free_parse_args(struct tep_print_parse *arg)
+{
+ struct tep_print_parse *del;
+
+ while (arg) {
+ del = arg;
+ arg = del->next;
+ free(del->format);
+ free(del);
+ }
+}
+
+static int parse_arg_add(struct tep_print_parse **parse, char *format,
+ enum tep_print_parse_type type,
+ struct tep_print_arg *arg,
+ struct tep_print_arg *len_as_arg,
+ int ls)
+{
+ struct tep_print_parse *parg = NULL;
+
+ parg = calloc(1, sizeof(*parg));
+ if (!parg)
+ goto error;
+ parg->format = strdup(format);
+ if (!parg->format)
+ goto error;
+ parg->type = type;
+ parg->arg = arg;
+ parg->len_as_arg = len_as_arg;
+ parg->ls = ls;
+ *parse = parg;
+ return 0;
+error:
+ if (parg) {
+ free(parg->format);
+ free(parg);
+ }
+ return -1;
+}
+
+static int parse_arg_format(struct tep_print_parse **parse,
+ struct tep_event *event,
+ const char *format, struct tep_print_arg **arg)
+{
+ struct tep_print_arg *len_arg = NULL;
+ char print_format[32];
+ const char *start = format;
+ int ret = 0;
+ int ls = 0;
+ int res;
+ int len;
+
+ format++;
+ ret++;
+ for (; *format; format++) {
+ switch (*format) {
+ case '#':
+ /* FIXME: need to handle properly */
+ break;
+ case 'h':
+ ls--;
+ break;
+ case 'l':
+ ls++;
+ break;
+ case 'L':
+ ls = 2;
+ break;
+ case 'z':
+ case 'Z':
+ ls = 1;
+ break;
+ case '.':
+ case '0' ... '9':
+ case '-':
+ break;
+ case '*':
+ /* The argument is the length. */
+ if (!*arg) {
+ do_warning_event(event, "no argument match");
+ event->flags |= TEP_EVENT_FL_FAILED;
+ goto out_failed;
+ }
+ if (len_arg) {
+ do_warning_event(event, "argument already matched");
+ event->flags |= TEP_EVENT_FL_FAILED;
+ goto out_failed;
+ }
+ len_arg = *arg;
+ *arg = (*arg)->next;
+ break;
+ case 'p':
+ if (!*arg) {
+ do_warning_event(event, "no argument match");
+ event->flags |= TEP_EVENT_FL_FAILED;
+ goto out_failed;
+ }
+ res = parse_arg_format_pointer(format + 1);
+ if (res > 0) {
+ format += res;
+ ret += res;
+ }
+ len = ((unsigned long)format + 1) -
+ (unsigned long)start;
+ /* should never happen */
+ if (len > 31) {
+ do_warning_event(event, "bad format!");
+ event->flags |= TEP_EVENT_FL_FAILED;
+ len = 31;
+ }
+ memcpy(print_format, start, len);
+ print_format[len] = 0;
+
+ parse_arg_add(parse, print_format,
+ PRINT_FMT_ARG_POINTER, *arg, len_arg, ls);
+ *arg = (*arg)->next;
+ ret++;
+ return ret;
+ case 'd':
+ case 'u':
+ case 'i':
+ case 'x':
+ case 'X':
+ case 'o':
+ if (!*arg) {
+ do_warning_event(event, "no argument match");
+ event->flags |= TEP_EVENT_FL_FAILED;
+ goto out_failed;
+ }
+
+ len = ((unsigned long)format + 1) -
+ (unsigned long)start;
+
+ /* should never happen */
+ if (len > 30) {
+ do_warning_event(event, "bad format!");
+ event->flags |= TEP_EVENT_FL_FAILED;
+ len = 31;
+ }
+ memcpy(print_format, start, len);
+ print_format[len] = 0;
+
+ if (event->tep->long_size == 8 && ls == 1 &&
+ sizeof(long) != 8) {
+ char *p;
+
+ /* make %l into %ll */
+ if (ls == 1 && (p = strchr(print_format, 'l')))
+ memmove(p+1, p, strlen(p)+1);
+ ls = 2;
+ }
+ if (ls < -2 || ls > 2) {
+ do_warning_event(event, "bad count (%d)", ls);
+ event->flags |= TEP_EVENT_FL_FAILED;
+ }
+ parse_arg_add(parse, print_format,
+ PRINT_FMT_ARG_DIGIT, *arg, len_arg, ls);
+ *arg = (*arg)->next;
+ ret++;
+ return ret;
+ case 's':
+ if (!*arg) {
+ do_warning_event(event, "no matching argument");
+ event->flags |= TEP_EVENT_FL_FAILED;
+ goto out_failed;
+ }
+
+ len = ((unsigned long)format + 1) -
+ (unsigned long)start;
+
+ /* should never happen */
+ if (len > 31) {
+ do_warning_event(event, "bad format!");
+ event->flags |= TEP_EVENT_FL_FAILED;
+ len = 31;
+ }
+
+ memcpy(print_format, start, len);
+ print_format[len] = 0;
+
+ parse_arg_add(parse, print_format,
+ PRINT_FMT_ARG_STRING, *arg, len_arg, 0);
+ *arg = (*arg)->next;
+ ret++;
+ return ret;
+ default:
+ snprintf(print_format, 32, ">%c<", *format);
+ parse_arg_add(parse, print_format,
+ PRINT_FMT_STRING, NULL, NULL, 0);
+ ret++;
+ return ret;
+ }
+ ret++;
+ }
+
+out_failed:
+ return ret;
+
+}
+
+static int parse_arg_string(struct tep_print_parse **parse, const char *format)
+{
+ struct trace_seq s;
+ int ret = 0;
+
+ trace_seq_init(&s);
+ for (; *format; format++) {
+ if (*format == '\\') {
+ format++;
+ ret++;
+ switch (*format) {
+ case 'n':
+ trace_seq_putc(&s, '\n');
+ break;
+ case 't':
+ trace_seq_putc(&s, '\t');
+ break;
+ case 'r':
+ trace_seq_putc(&s, '\r');
+ break;
+ case '\\':
+ trace_seq_putc(&s, '\\');
+ break;
+ default:
+ trace_seq_putc(&s, *format);
+ break;
+ }
+ } else if (*format == '%') {
+ if (*(format + 1) == '%') {
+ trace_seq_putc(&s, '%');
+ format++;
+ ret++;
+ } else
+ break;
+ } else
+ trace_seq_putc(&s, *format);
+
+ ret++;
+ }
+ trace_seq_terminate(&s);
+ parse_arg_add(parse, s.buffer, PRINT_FMT_STRING, NULL, NULL, 0);
+ trace_seq_destroy(&s);
+
+ return ret;
+}
+
+static struct tep_print_parse *
+parse_args(struct tep_event *event, const char *format, struct tep_print_arg *arg)
+{
+ struct tep_print_parse *parse_ret = NULL;
+ struct tep_print_parse **parse = NULL;
+ int ret;
+ int len;
+
+ len = strlen(format);
+ while (*format) {
+ if (!parse_ret)
+ parse = &parse_ret;
+ if (*format == '%' && *(format + 1) != '%')
+ ret = parse_arg_format(parse, event, format, &arg);
+ else
+ ret = parse_arg_string(parse, format);
+ if (*parse)
+ parse = &((*parse)->next);
+
+ len -= ret;
+ if (len > 0)
+ format += ret;
+ else
+ break;
+ }
+ return parse_ret;
+}
+
+static int print_parse_data(struct tep_print_parse *parse, struct trace_seq *s,
+ void *data, int size, struct tep_event *event)
+{
+ int len_arg;
+
+ if (parse->len_as_arg)
+ len_arg = eval_num_arg(data, size, event, parse->len_as_arg);
+
+ switch (parse->type) {
+ case PRINT_FMT_ARG_DIGIT:
+ print_arg_number(s, parse->format,
+ parse->len_as_arg ? len_arg : -1, data,
+ size, parse->ls, event, parse->arg);
+ break;
+ case PRINT_FMT_ARG_POINTER:
+ print_arg_pointer(s, parse->format,
+ parse->len_as_arg ? len_arg : 1,
+ data, size, event, parse->arg);
+ break;
+ case PRINT_FMT_ARG_STRING:
+ print_arg_string(s, parse->format,
+ parse->len_as_arg ? len_arg : -1,
+ data, size, event, parse->arg);
+ break;
+ case PRINT_FMT_STRING:
+ default:
+ trace_seq_printf(s, "%s", parse->format);
+ /* Return 1 on non field. */
+ return 1;
+ }
+ /* Return 0 on field being processed. */
+ return 0;
+}
+
+static void print_event_cache(struct tep_print_parse *parse, struct trace_seq *s,
+ void *data, int size, struct tep_event *event)
+{
+ while (parse) {
+ print_parse_data(parse, s, data, size, event);
+ parse = parse->next;
+ }
+}
+
+static void pretty_print(struct trace_seq *s, void *data, int size, struct tep_event *event)
+{
+ struct tep_print_parse *parse = event->print_fmt.print_cache;
+ struct tep_print_arg *args = NULL;
+ char *bprint_fmt = NULL;
+
+ if (event->flags & TEP_EVENT_FL_FAILED) {
+ trace_seq_printf(s, "[FAILED TO PARSE]");
+ tep_print_fields(s, data, size, event);
+ return;
+ }
+
+ if (event->flags & TEP_EVENT_FL_ISBPRINT) {
+ bprint_fmt = get_bprint_format(data, size, event);
+ args = make_bprint_args(bprint_fmt, data, size, event);
+ parse = parse_args(event, bprint_fmt, args);
+ }
+
+ print_event_cache(parse, s, data, size, event);
+
+ if (event->flags & TEP_EVENT_FL_ISBPRINT) {
+ free_parse_args(parse);
+ free_args(args);
+ free(bprint_fmt);
+ }
+}
+
+/*
+ * This parses out the Latency format (interrupts disabled,
+ * need rescheduling, in hard/soft interrupt, preempt count
+ * and lock depth) and places it into the trace_seq.
+ */
+static void data_latency_format(struct tep_handle *tep, struct trace_seq *s,
+ char *format, struct tep_record *record)
+{
+ static int check_lock_depth = 1;
+ static int check_migrate_disable = 1;
+ static int lock_depth_exists;
+ static int migrate_disable_exists;
+ unsigned int lat_flags;
+ struct trace_seq sq;
+ unsigned int pc;
+ int lock_depth = 0;
+ int migrate_disable = 0;
+ int hardirq;
+ int softirq;
+ void *data = record->data;
+
+ trace_seq_init(&sq);
+ lat_flags = parse_common_flags(tep, data);
+ pc = parse_common_pc(tep, data);
+ /* lock_depth may not always exist */
+ if (lock_depth_exists)
+ lock_depth = parse_common_lock_depth(tep, data);
+ else if (check_lock_depth) {
+ lock_depth = parse_common_lock_depth(tep, data);
+ if (lock_depth < 0)
+ check_lock_depth = 0;
+ else
+ lock_depth_exists = 1;
+ }
+
+ /* migrate_disable may not always exist */
+ if (migrate_disable_exists)
+ migrate_disable = parse_common_migrate_disable(tep, data);
+ else if (check_migrate_disable) {
+ migrate_disable = parse_common_migrate_disable(tep, data);
+ if (migrate_disable < 0)
+ check_migrate_disable = 0;
+ else
+ migrate_disable_exists = 1;
+ }
+
+ hardirq = lat_flags & TRACE_FLAG_HARDIRQ;
+ softirq = lat_flags & TRACE_FLAG_SOFTIRQ;
+
+ trace_seq_printf(&sq, "%c%c%c",
+ (lat_flags & TRACE_FLAG_IRQS_OFF) ? 'd' :
+ (lat_flags & TRACE_FLAG_IRQS_NOSUPPORT) ?
+ 'X' : '.',
+ (lat_flags & TRACE_FLAG_NEED_RESCHED) ?
+ 'N' : '.',
+ (hardirq && softirq) ? 'H' :
+ hardirq ? 'h' : softirq ? 's' : '.');
+
+ if (pc & 0xf)
+ trace_seq_printf(&sq, "%x", pc & 0xf);
+ else
+ trace_seq_printf(&sq, ".");
+
+ if (pc & 0xf0)
+ trace_seq_printf(&sq, "%x", pc >> 4);
+ else
+ trace_seq_printf(&sq, ".");
+
+ if (migrate_disable_exists) {
+ if (migrate_disable < 0)
+ trace_seq_printf(&sq, ".");
+ else
+ trace_seq_printf(&sq, "%d", migrate_disable);
+ }
+
+ if (lock_depth_exists) {
+ if (lock_depth < 0)
+ trace_seq_printf(&sq, ".");
+ else
+ trace_seq_printf(&sq, "%d", lock_depth);
+ }
+
+ if (sq.state == TRACE_SEQ__MEM_ALLOC_FAILED) {
+ s->state = TRACE_SEQ__MEM_ALLOC_FAILED;
+ return;
+ }
+
+ trace_seq_terminate(&sq);
+ trace_seq_puts(s, sq.buffer);
+ trace_seq_destroy(&sq);
+ trace_seq_terminate(s);
+}
+
+/**
+ * tep_data_type - parse out the given event type
+ * @tep: a handle to the trace event parser context
+ * @rec: the record to read from
+ *
+ * This returns the event id from the @rec.
+ */
+int tep_data_type(struct tep_handle *tep, struct tep_record *rec)
+{
+ return trace_parse_common_type(tep, rec->data);
+}
+
+/**
+ * tep_data_pid - parse the PID from record
+ * @tep: a handle to the trace event parser context
+ * @rec: the record to parse
+ *
+ * This returns the PID from a record.
+ */
+int tep_data_pid(struct tep_handle *tep, struct tep_record *rec)
+{
+ return parse_common_pid(tep, rec->data);
+}
+
+/**
+ * tep_data_preempt_count - parse the preempt count from the record
+ * @tep: a handle to the trace event parser context
+ * @rec: the record to parse
+ *
+ * This returns the preempt count from a record.
+ */
+int tep_data_preempt_count(struct tep_handle *tep, struct tep_record *rec)
+{
+ return parse_common_pc(tep, rec->data);
+}
+
+/**
+ * tep_data_flags - parse the latency flags from the record
+ * @tep: a handle to the trace event parser context
+ * @rec: the record to parse
+ *
+ * This returns the latency flags from a record.
+ *
+ * Use trace_flag_type enum for the flags (see event-parse.h).
+ */
+int tep_data_flags(struct tep_handle *tep, struct tep_record *rec)
+{
+ return parse_common_flags(tep, rec->data);
+}
+
+/**
+ * tep_data_comm_from_pid - return the command line from PID
+ * @tep: a handle to the trace event parser context
+ * @pid: the PID of the task to search for
+ *
+ * This returns a pointer to the command line that has the given
+ * @pid.
+ */
+const char *tep_data_comm_from_pid(struct tep_handle *tep, int pid)
+{
+ const char *comm;
+
+ comm = find_cmdline(tep, pid);
+ return comm;
+}
+
+static struct tep_cmdline *
+pid_from_cmdlist(struct tep_handle *tep, const char *comm, struct tep_cmdline *next)
+{
+ struct cmdline_list *cmdlist = (struct cmdline_list *)next;
+
+ if (cmdlist)
+ cmdlist = cmdlist->next;
+ else
+ cmdlist = tep->cmdlist;
+
+ while (cmdlist && strcmp(cmdlist->comm, comm) != 0)
+ cmdlist = cmdlist->next;
+
+ return (struct tep_cmdline *)cmdlist;
+}
+
+/**
+ * tep_data_pid_from_comm - return the pid from a given comm
+ * @tep: a handle to the trace event parser context
+ * @comm: the cmdline to find the pid from
+ * @next: the cmdline structure to find the next comm
+ *
+ * This returns the cmdline structure that holds a pid for a given
+ * comm, or NULL if none found. As there may be more than one pid for
+ * a given comm, the result of this call can be passed back into
+ * a recurring call in the @next parameter, and then it will find the
+ * next pid.
+ * Also, it does a linear search, so it may be slow.
+ */
+struct tep_cmdline *tep_data_pid_from_comm(struct tep_handle *tep, const char *comm,
+ struct tep_cmdline *next)
+{
+ struct tep_cmdline *cmdline;
+
+ /*
+ * If the cmdlines have not been converted yet, then use
+ * the list.
+ */
+ if (!tep->cmdlines)
+ return pid_from_cmdlist(tep, comm, next);
+
+ if (next) {
+ /*
+ * The next pointer could have been still from
+ * a previous call before cmdlines were created
+ */
+ if (next < tep->cmdlines ||
+ next >= tep->cmdlines + tep->cmdline_count)
+ next = NULL;
+ else
+ cmdline = next++;
+ }
+
+ if (!next)
+ cmdline = tep->cmdlines;
+
+ while (cmdline < tep->cmdlines + tep->cmdline_count) {
+ if (strcmp(cmdline->comm, comm) == 0)
+ return cmdline;
+ cmdline++;
+ }
+ return NULL;
+}
+
+/**
+ * tep_cmdline_pid - return the pid associated to a given cmdline
+ * @tep: a handle to the trace event parser context
+ * @cmdline: The cmdline structure to get the pid from
+ *
+ * Returns the pid for a give cmdline. If @cmdline is NULL, then
+ * -1 is returned.
+ */
+int tep_cmdline_pid(struct tep_handle *tep, struct tep_cmdline *cmdline)
+{
+ struct cmdline_list *cmdlist = (struct cmdline_list *)cmdline;
+
+ if (!cmdline)
+ return -1;
+
+ /*
+ * If cmdlines have not been created yet, or cmdline is
+ * not part of the array, then treat it as a cmdlist instead.
+ */
+ if (!tep->cmdlines ||
+ cmdline < tep->cmdlines ||
+ cmdline >= tep->cmdlines + tep->cmdline_count)
+ return cmdlist->pid;
+
+ return cmdline->pid;
+}
+
+/*
+ * This parses the raw @data using the given @event information and
+ * writes the print format into the trace_seq.
+ */
+static void print_event_info(struct trace_seq *s, char *format, bool raw,
+ struct tep_event *event, struct tep_record *record)
+{
+ int print_pretty = 1;
+
+ if (raw || (event->flags & TEP_EVENT_FL_PRINTRAW))
+ tep_print_fields(s, record->data, record->size, event);
+ else {
+
+ if (event->handler && !(event->flags & TEP_EVENT_FL_NOHANDLE))
+ print_pretty = event->handler(s, record, event,
+ event->context);
+
+ if (print_pretty)
+ pretty_print(s, record->data, record->size, event);
+ }
+
+ trace_seq_terminate(s);
+}
+
+/**
+ * tep_find_event_by_record - return the event from a given record
+ * @tep: a handle to the trace event parser context
+ * @record: The record to get the event from
+ *
+ * Returns the associated event for a given record, or NULL if non is
+ * is found.
+ */
+struct tep_event *
+tep_find_event_by_record(struct tep_handle *tep, struct tep_record *record)
+{
+ int type;
+
+ if (record->size < 0) {
+ do_warning("ug! negative record size %d", record->size);
+ return NULL;
+ }
+
+ type = trace_parse_common_type(tep, record->data);
+
+ return tep_find_event(tep, type);
+}
+
+/*
+ * Writes the timestamp of the record into @s. Time divisor and precision can be
+ * specified as part of printf @format string. Example:
+ * "%3.1000d" - divide the time by 1000 and print the first 3 digits
+ * before the dot. Thus, the timestamp "123456000" will be printed as
+ * "123.456"
+ */
+static void print_event_time(struct tep_handle *tep, struct trace_seq *s,
+ char *format, struct tep_event *event,
+ struct tep_record *record)
+{
+ unsigned long long time;
+ char *divstr;
+ int prec = 0, pr;
+ int div = 0;
+ int p10 = 1;
+
+ if (isdigit(*(format + 1)))
+ prec = atoi(format + 1);
+ divstr = strchr(format, '.');
+ if (divstr && isdigit(*(divstr + 1)))
+ div = atoi(divstr + 1);
+ time = record->ts;
+ if (div) {
+ time += div / 2;
+ time /= div;
+ }
+ pr = prec;
+ while (pr--)
+ p10 *= 10;
+
+ if (p10 > 1)
+ trace_seq_printf(s, "%5llu.%0*llu", time / p10, prec, time % p10);
+ else
+ trace_seq_printf(s, "%12llu", time);
+}
+
+struct print_event_type {
+ enum {
+ EVENT_TYPE_INT = 1,
+ EVENT_TYPE_STRING,
+ EVENT_TYPE_UNKNOWN,
+ } type;
+ char format[32];
+};
+
+static void print_string(struct tep_handle *tep, struct trace_seq *s,
+ struct tep_record *record, struct tep_event *event,
+ const char *arg, struct print_event_type *type)
+{
+ const char *comm;
+ int pid;
+
+ if (strncmp(arg, TEP_PRINT_LATENCY, strlen(TEP_PRINT_LATENCY)) == 0) {
+ data_latency_format(tep, s, type->format, record);
+ } else if (strncmp(arg, TEP_PRINT_COMM, strlen(TEP_PRINT_COMM)) == 0) {
+ pid = parse_common_pid(tep, record->data);
+ comm = find_cmdline(tep, pid);
+ trace_seq_printf(s, type->format, comm);
+ } else if (strncmp(arg, TEP_PRINT_INFO_RAW, strlen(TEP_PRINT_INFO_RAW)) == 0) {
+ print_event_info(s, type->format, true, event, record);
+ } else if (strncmp(arg, TEP_PRINT_INFO, strlen(TEP_PRINT_INFO)) == 0) {
+ print_event_info(s, type->format, false, event, record);
+ } else if (strncmp(arg, TEP_PRINT_NAME, strlen(TEP_PRINT_NAME)) == 0) {
+ trace_seq_printf(s, type->format, event->name);
+ } else {
+ trace_seq_printf(s, "[UNKNOWN TEP TYPE %s]", arg);
+ }
+
+}
+
+static void print_int(struct tep_handle *tep, struct trace_seq *s,
+ struct tep_record *record, struct tep_event *event,
+ int arg, struct print_event_type *type)
+{
+ int param;
+
+ switch (arg) {
+ case TEP_PRINT_CPU:
+ param = record->cpu;
+ break;
+ case TEP_PRINT_PID:
+ param = parse_common_pid(tep, record->data);
+ break;
+ case TEP_PRINT_TIME:
+ return print_event_time(tep, s, type->format, event, record);
+ default:
+ return;
+ }
+ trace_seq_printf(s, type->format, param);
+}
+
+static int tep_print_event_param_type(char *format,
+ struct print_event_type *type)
+{
+ char *str = format + 1;
+ int i = 1;
+
+ type->type = EVENT_TYPE_UNKNOWN;
+ while (*str) {
+ switch (*str) {
+ case 'd':
+ case 'u':
+ case 'i':
+ case 'x':
+ case 'X':
+ case 'o':
+ type->type = EVENT_TYPE_INT;
+ break;
+ case 's':
+ type->type = EVENT_TYPE_STRING;
+ break;
+ }
+ str++;
+ i++;
+ if (type->type != EVENT_TYPE_UNKNOWN)
+ break;
+ }
+ memset(type->format, 0, 32);
+ memcpy(type->format, format, i < 32 ? i : 31);
+ return i;
+}
+
+/**
+ * tep_print_event - Write various event information
+ * @tep: a handle to the trace event parser context
+ * @s: the trace_seq to write to
+ * @record: The record to get the event from
+ * @format: a printf format string. Supported event fileds:
+ * TEP_PRINT_PID, "%d" - event PID
+ * TEP_PRINT_CPU, "%d" - event CPU
+ * TEP_PRINT_COMM, "%s" - event command string
+ * TEP_PRINT_NAME, "%s" - event name
+ * TEP_PRINT_LATENCY, "%s" - event latency
+ * TEP_PRINT_TIME, %d - event time stamp. A divisor and precision
+ * can be specified as part of this format string:
+ * "%precision.divisord". Example:
+ * "%3.1000d" - divide the time by 1000 and print the first
+ * 3 digits before the dot. Thus, the time stamp
+ * "123456000" will be printed as "123.456"
+ * TEP_PRINT_INFO, "%s" - event information. If any width is specified in
+ * the format string, the event information will be printed
+ * in raw format.
+ * Writes the specified event information into @s.
+ */
+void tep_print_event(struct tep_handle *tep, struct trace_seq *s,
+ struct tep_record *record, const char *fmt, ...)
+{
+ struct print_event_type type;
+ struct tep_event *event;
+ char *current;
+ char *format;
+ char *str;
+ int offset;
+ va_list args;
+
+ event = tep_find_event_by_record(tep, record);
+ if (!event) {
+ trace_seq_printf(s, "[UNKNOWN EVENT]");
+ return;
+ }
+
+ str = current = format = strdup(fmt);
+ if (!format)
+ return;
+
+ va_start(args, fmt);
+ while (*current) {
+ current = strchr(str, '%');
+ if (!current) {
+ trace_seq_puts(s, str);
+ break;
+ }
+ memset(&type, 0, sizeof(type));
+ offset = tep_print_event_param_type(current, &type);
+ *current = '\0';
+ trace_seq_puts(s, str);
+ current += offset;
+ switch (type.type) {
+ case EVENT_TYPE_STRING:
+ print_string(tep, s, record, event,
+ va_arg(args, char*), &type);
+ break;
+ case EVENT_TYPE_INT:
+ print_int(tep, s, record, event,
+ va_arg(args, int), &type);
+ break;
+ case EVENT_TYPE_UNKNOWN:
+ default:
+ trace_seq_printf(s, "[UNKNOWN TYPE]");
+ break;
+ }
+ str = current;
+
+ }
+ va_end(args);
+ free(format);
+}
+
+static int events_id_cmp(const void *a, const void *b)
+{
+ struct tep_event * const * ea = a;
+ struct tep_event * const * eb = b;
+
+ if ((*ea)->id < (*eb)->id)
+ return -1;
+
+ if ((*ea)->id > (*eb)->id)
+ return 1;
+
+ return 0;
+}
+
+static int events_name_cmp(const void *a, const void *b)
+{
+ struct tep_event * const * ea = a;
+ struct tep_event * const * eb = b;
+ int res;
+
+ res = strcmp((*ea)->name, (*eb)->name);
+ if (res)
+ return res;
+
+ res = strcmp((*ea)->system, (*eb)->system);
+ if (res)
+ return res;
+
+ return events_id_cmp(a, b);
+}
+
+static int events_system_cmp(const void *a, const void *b)
+{
+ struct tep_event * const * ea = a;
+ struct tep_event * const * eb = b;
+ int res;
+
+ res = strcmp((*ea)->system, (*eb)->system);
+ if (res)
+ return res;
+
+ res = strcmp((*ea)->name, (*eb)->name);
+ if (res)
+ return res;
+
+ return events_id_cmp(a, b);
+}
+
+static struct tep_event **list_events_copy(struct tep_handle *tep)
+{
+ struct tep_event **events;
+
+ if (!tep)
+ return NULL;
+
+ events = malloc(sizeof(*events) * (tep->nr_events + 1));
+ if (!events)
+ return NULL;
+
+ memcpy(events, tep->events, sizeof(*events) * tep->nr_events);
+ events[tep->nr_events] = NULL;
+ return events;
+}
+
+static void list_events_sort(struct tep_event **events, int nr_events,
+ enum tep_event_sort_type sort_type)
+{
+ int (*sort)(const void *a, const void *b);
+
+ switch (sort_type) {
+ case TEP_EVENT_SORT_ID:
+ sort = events_id_cmp;
+ break;
+ case TEP_EVENT_SORT_NAME:
+ sort = events_name_cmp;
+ break;
+ case TEP_EVENT_SORT_SYSTEM:
+ sort = events_system_cmp;
+ break;
+ default:
+ sort = NULL;
+ }
+
+ if (sort)
+ qsort(events, nr_events, sizeof(*events), sort);
+}
+
+/**
+ * tep_list_events - Get events, sorted by given criteria.
+ * @tep: a handle to the tep context
+ * @sort_type: desired sort order of the events in the array
+ *
+ * Returns an array of pointers to all events, sorted by the given
+ * @sort_type criteria. The last element of the array is NULL. The returned
+ * memory must not be freed, it is managed by the library.
+ * The function is not thread safe.
+ */
+struct tep_event **tep_list_events(struct tep_handle *tep,
+ enum tep_event_sort_type sort_type)
+{
+ struct tep_event **events;
+
+ if (!tep)
+ return NULL;
+
+ events = tep->sort_events;
+ if (events && tep->last_type == sort_type)
+ return events;
+
+ if (!events) {
+ events = list_events_copy(tep);
+ if (!events)
+ return NULL;
+
+ tep->sort_events = events;
+
+ /* the internal events are sorted by id */
+ if (sort_type == TEP_EVENT_SORT_ID) {
+ tep->last_type = sort_type;
+ return events;
+ }
+ }
+
+ list_events_sort(events, tep->nr_events, sort_type);
+ tep->last_type = sort_type;
+
+ return events;
+}
+
+
+/**
+ * tep_list_events_copy - Thread safe version of tep_list_events()
+ * @tep: a handle to the tep context
+ * @sort_type: desired sort order of the events in the array
+ *
+ * Returns an array of pointers to all events, sorted by the given
+ * @sort_type criteria. The last element of the array is NULL. The returned
+ * array is newly allocated inside the function and must be freed by the caller
+ */
+struct tep_event **tep_list_events_copy(struct tep_handle *tep,
+ enum tep_event_sort_type sort_type)
+{
+ struct tep_event **events;
+
+ if (!tep)
+ return NULL;
+
+ events = list_events_copy(tep);
+ if (!events)
+ return NULL;
+
+ /* the internal events are sorted by id */
+ if (sort_type == TEP_EVENT_SORT_ID)
+ return events;
+
+ list_events_sort(events, tep->nr_events, sort_type);
+
+ return events;
+}
+
+static struct tep_format_field **
+get_event_fields(const char *type, const char *name,
+ int count, struct tep_format_field *list)
+{
+ struct tep_format_field **fields;
+ struct tep_format_field *field;
+ int i = 0;
+
+ fields = malloc(sizeof(*fields) * (count + 1));
+ if (!fields)
+ return NULL;
+
+ for (field = list; field; field = field->next) {
+ fields[i++] = field;
+ if (i == count + 1) {
+ do_warning("event %s has more %s fields than specified",
+ name, type);
+ i--;
+ break;
+ }
+ }
+
+ if (i != count)
+ do_warning("event %s has less %s fields than specified",
+ name, type);
+
+ fields[i] = NULL;
+
+ return fields;
+}
+
+/**
+ * tep_event_common_fields - return a list of common fields for an event
+ * @event: the event to return the common fields of.
+ *
+ * Returns an allocated array of fields. The last item in the array is NULL.
+ * The array must be freed with free().
+ */
+struct tep_format_field **tep_event_common_fields(struct tep_event *event)
+{
+ return get_event_fields("common", event->name,
+ event->format.nr_common,
+ event->format.common_fields);
+}
+
+/**
+ * tep_event_fields - return a list of event specific fields for an event
+ * @event: the event to return the fields of.
+ *
+ * Returns an allocated array of fields. The last item in the array is NULL.
+ * The array must be freed with free().
+ */
+struct tep_format_field **tep_event_fields(struct tep_event *event)
+{
+ return get_event_fields("event", event->name,
+ event->format.nr_fields,
+ event->format.fields);
+}
+
+static void print_fields(struct trace_seq *s, struct tep_print_flag_sym *field)
+{
+ trace_seq_printf(s, "{ %s, %s }", field->value, field->str);
+ if (field->next) {
+ trace_seq_puts(s, ", ");
+ print_fields(s, field->next);
+ }
+}
+
+/* for debugging */
+static void print_args(struct tep_print_arg *args)
+{
+ int print_paren = 1;
+ struct trace_seq s;
+
+ switch (args->type) {
+ case TEP_PRINT_NULL:
+ printf("null");
+ break;
+ case TEP_PRINT_ATOM:
+ printf("%s", args->atom.atom);
+ break;
+ case TEP_PRINT_FIELD:
+ printf("REC->%s", args->field.name);
+ break;
+ case TEP_PRINT_FLAGS:
+ printf("__print_flags(");
+ print_args(args->flags.field);
+ printf(", %s, ", args->flags.delim);
+ trace_seq_init(&s);
+ print_fields(&s, args->flags.flags);
+ trace_seq_do_printf(&s);
+ trace_seq_destroy(&s);
+ printf(")");
+ break;
+ case TEP_PRINT_SYMBOL:
+ printf("__print_symbolic(");
+ print_args(args->symbol.field);
+ printf(", ");
+ trace_seq_init(&s);
+ print_fields(&s, args->symbol.symbols);
+ trace_seq_do_printf(&s);
+ trace_seq_destroy(&s);
+ printf(")");
+ break;
+ case TEP_PRINT_HEX:
+ printf("__print_hex(");
+ print_args(args->hex.field);
+ printf(", ");
+ print_args(args->hex.size);
+ printf(")");
+ break;
+ case TEP_PRINT_HEX_STR:
+ printf("__print_hex_str(");
+ print_args(args->hex.field);
+ printf(", ");
+ print_args(args->hex.size);
+ printf(")");
+ break;
+ case TEP_PRINT_INT_ARRAY:
+ printf("__print_array(");
+ print_args(args->int_array.field);
+ printf(", ");
+ print_args(args->int_array.count);
+ printf(", ");
+ print_args(args->int_array.el_size);
+ printf(")");
+ break;
+ case TEP_PRINT_STRING:
+ case TEP_PRINT_BSTRING:
+ printf("__get_str(%s)", args->string.string);
+ break;
+ case TEP_PRINT_BITMASK:
+ printf("__get_bitmask(%s)", args->bitmask.bitmask);
+ break;
+ case TEP_PRINT_CPUMASK:
+ printf("__get_cpumask(%s)", args->bitmask.bitmask);
+ break;
+ case TEP_PRINT_TYPE:
+ printf("(%s)", args->typecast.type);
+ print_args(args->typecast.item);
+ break;
+ case TEP_PRINT_OP:
+ if (strcmp(args->op.op, ":") == 0)
+ print_paren = 0;
+ if (print_paren)
+ printf("(");
+ print_args(args->op.left);
+ printf(" %s ", args->op.op);
+ print_args(args->op.right);
+ if (print_paren)
+ printf(")");
+ break;
+ default:
+ /* we should warn... */
+ return;
+ }
+ if (args->next) {
+ printf("\n");
+ print_args(args->next);
+ }
+}
+
+static void parse_header_field(struct tep_handle *tep, const char *field,
+ int *offset, int *size, int mandatory)
+{
+ unsigned long long save_input_buf_ptr;
+ unsigned long long save_input_buf_siz;
+ char *token;
+ int type;
+
+ save_input_buf_ptr = tep->input_buf_ptr;
+ save_input_buf_siz = tep->input_buf_siz;
+
+ if (read_expected(tep, TEP_EVENT_ITEM, "field") < 0)
+ return;
+ if (read_expected(tep, TEP_EVENT_OP, ":") < 0)
+ return;
+
+ /* type */
+ if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0)
+ goto fail;
+ free_token(token);
+
+ /*
+ * If this is not a mandatory field, then test it first.
+ */
+ if (mandatory) {
+ if (read_expected(tep, TEP_EVENT_ITEM, field) < 0)
+ return;
+ } else {
+ if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0)
+ goto fail;
+ if (strcmp(token, field) != 0)
+ goto discard;
+ free_token(token);
+ }
+
+ if (read_expected(tep, TEP_EVENT_OP, ";") < 0)
+ return;
+ if (read_expected(tep, TEP_EVENT_ITEM, "offset") < 0)
+ return;
+ if (read_expected(tep, TEP_EVENT_OP, ":") < 0)
+ return;
+ if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0)
+ goto fail;
+ *offset = atoi(token);
+ free_token(token);
+ if (read_expected(tep, TEP_EVENT_OP, ";") < 0)
+ return;
+ if (read_expected(tep, TEP_EVENT_ITEM, "size") < 0)
+ return;
+ if (read_expected(tep, TEP_EVENT_OP, ":") < 0)
+ return;
+ if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0)
+ goto fail;
+ *size = atoi(token);
+ free_token(token);
+ if (read_expected(tep, TEP_EVENT_OP, ";") < 0)
+ return;
+ type = read_token(tep, &token);
+ if (type != TEP_EVENT_NEWLINE) {
+ /* newer versions of the kernel have a "signed" type */
+ if (type != TEP_EVENT_ITEM)
+ goto fail;
+
+ if (strcmp(token, "signed") != 0)
+ goto fail;
+
+ free_token(token);
+
+ if (read_expected(tep, TEP_EVENT_OP, ":") < 0)
+ return;
+
+ if (read_expect_type(tep, TEP_EVENT_ITEM, &token))
+ goto fail;
+
+ free_token(token);
+ if (read_expected(tep, TEP_EVENT_OP, ";") < 0)
+ return;
+
+ if (read_expect_type(tep, TEP_EVENT_NEWLINE, &token))
+ goto fail;
+ }
+ fail:
+ free_token(token);
+ return;
+
+ discard:
+ tep->input_buf_ptr = save_input_buf_ptr;
+ tep->input_buf_siz = save_input_buf_siz;
+ *offset = 0;
+ *size = 0;
+ free_token(token);
+}
+
+/**
+ * tep_parse_header_page - parse the data stored in the header page
+ * @tep: a handle to the trace event parser context
+ * @buf: the buffer storing the header page format string
+ * @size: the size of @buf
+ * @long_size: the long size to use if there is no header
+ *
+ * This parses the header page format for information on the
+ * ring buffer used. The @buf should be copied from
+ *
+ * /sys/kernel/debug/tracing/events/header_page
+ */
+int tep_parse_header_page(struct tep_handle *tep, char *buf, unsigned long size,
+ int long_size)
+{
+ int ignore;
+
+ if (!size) {
+ /*
+ * Old kernels did not have header page info.
+ * Sorry but we just use what we find here in user space.
+ */
+ tep->header_page_ts_size = sizeof(long long);
+ tep->header_page_size_size = long_size;
+ tep->header_page_data_offset = sizeof(long long) + long_size;
+ tep->header_page_data_size = getpagesize() - tep->header_page_data_offset;
+ tep->old_format = 1;
+ return -1;
+ }
+ init_input_buf(tep, buf, size);
+
+ parse_header_field(tep, "timestamp", &tep->header_page_ts_offset,
+ &tep->header_page_ts_size, 1);
+ parse_header_field(tep, "commit", &tep->header_page_size_offset,
+ &tep->header_page_size_size, 1);
+ parse_header_field(tep, "overwrite", &tep->header_page_overwrite,
+ &ignore, 0);
+ parse_header_field(tep, "data", &tep->header_page_data_offset,
+ &tep->header_page_data_size, 1);
+
+ return 0;
+}
+
+static int event_matches(struct tep_event *event,
+ int id, const char *sys_name,
+ const char *event_name)
+{
+ if (id >= 0 && id != event->id)
+ return 0;
+
+ if (event_name && (strcmp(event_name, event->name) != 0))
+ return 0;
+
+ if (sys_name && (strcmp(sys_name, event->system) != 0))
+ return 0;
+
+ return 1;
+}
+
+static void free_handler(struct event_handler *handle)
+{
+ free((void *)handle->sys_name);
+ free((void *)handle->event_name);
+ free(handle);
+}
+
+static int find_event_handle(struct tep_handle *tep, struct tep_event *event)
+{
+ struct event_handler *handle, **next;
+
+ for (next = &tep->handlers; *next;
+ next = &(*next)->next) {
+ handle = *next;
+ if (event_matches(event, handle->id,
+ handle->sys_name,
+ handle->event_name))
+ break;
+ }
+
+ if (!(*next))
+ return 0;
+
+ tep_info("overriding event (%d) %s:%s with new print handler",
+ event->id, event->system, event->name);
+
+ event->handler = handle->func;
+ event->context = handle->context;
+
+ *next = handle->next;
+ free_handler(handle);
+
+ return 1;
+}
+
+/**
+ * parse_format - parse the event format
+ * @buf: the buffer storing the event format string
+ * @size: the size of @buf
+ * @sys: the system the event belongs to
+ *
+ * This parses the event format and creates an event structure
+ * to quickly parse raw data for a given event.
+ *
+ * These files currently come from:
+ *
+ * /sys/kernel/debug/tracing/events/.../.../format
+ */
+static enum tep_errno parse_format(struct tep_event **eventp,
+ struct tep_handle *tep, const char *buf,
+ unsigned long size, const char *sys)
+{
+ struct tep_event *event;
+ int ret;
+
+ init_input_buf(tep, buf, size);
+
+ *eventp = event = alloc_event();
+ if (!event)
+ return TEP_ERRNO__MEM_ALLOC_FAILED;
+
+ event->name = event_read_name(tep);
+ if (!event->name) {
+ /* Bad event? */
+ ret = TEP_ERRNO__MEM_ALLOC_FAILED;
+ goto event_alloc_failed;
+ }
+
+ if (strcmp(sys, "ftrace") == 0) {
+ event->flags |= TEP_EVENT_FL_ISFTRACE;
+
+ if (strcmp(event->name, "bprint") == 0)
+ event->flags |= TEP_EVENT_FL_ISBPRINT;
+ }
+
+ event->id = event_read_id(tep);
+ if (event->id < 0) {
+ ret = TEP_ERRNO__READ_ID_FAILED;
+ /*
+ * This isn't an allocation error actually.
+ * But as the ID is critical, just bail out.
+ */
+ goto event_alloc_failed;
+ }
+
+ event->system = strdup(sys);
+ if (!event->system) {
+ ret = TEP_ERRNO__MEM_ALLOC_FAILED;
+ goto event_alloc_failed;
+ }
+
+ /* Add tep to event so that it can be referenced */
+ event->tep = tep;
+
+ ret = event_read_format(event);
+ if (ret < 0) {
+ ret = TEP_ERRNO__READ_FORMAT_FAILED;
+ goto event_parse_failed;
+ }
+
+ /*
+ * If the event has an override, don't print warnings if the event
+ * print format fails to parse.
+ */
+ if (tep && find_event_handle(tep, event))
+ show_warning = 0;
+
+ ret = event_read_print(event);
+ show_warning = 1;
+
+ if (ret < 0) {
+ ret = TEP_ERRNO__READ_PRINT_FAILED;
+ goto event_parse_failed;
+ }
+
+ if (!ret && (event->flags & TEP_EVENT_FL_ISFTRACE)) {
+ struct tep_format_field *field;
+ struct tep_print_arg *arg, **list;
+
+ /* old ftrace had no args */
+ list = &event->print_fmt.args;
+ for (field = event->format.fields; field; field = field->next) {
+ arg = alloc_arg();
+ if (!arg) {
+ event->flags |= TEP_EVENT_FL_FAILED;
+ return TEP_ERRNO__OLD_FTRACE_ARG_FAILED;
+ }
+ arg->type = TEP_PRINT_FIELD;
+ arg->field.name = strdup(field->name);
+ if (!arg->field.name) {
+ event->flags |= TEP_EVENT_FL_FAILED;
+ free_arg(arg);
+ return TEP_ERRNO__OLD_FTRACE_ARG_FAILED;
+ }
+ arg->field.field = field;
+ *list = arg;
+ list = &arg->next;
+ }
+ }
+
+ if (!(event->flags & TEP_EVENT_FL_ISBPRINT))
+ event->print_fmt.print_cache = parse_args(event,
+ event->print_fmt.format,
+ event->print_fmt.args);
+
+ return 0;
+
+ event_parse_failed:
+ event->flags |= TEP_EVENT_FL_FAILED;
+ return ret;
+
+ event_alloc_failed:
+ free(event->system);
+ free(event->name);
+ free(event);
+ *eventp = NULL;
+ return ret;
+}
+
+static enum tep_errno
+__parse_event(struct tep_handle *tep,
+ struct tep_event **eventp,
+ const char *buf, unsigned long size,
+ const char *sys)
+{
+ int ret = parse_format(eventp, tep, buf, size, sys);
+ struct tep_event *event = *eventp;
+
+ if (event == NULL)
+ return ret;
+
+ if (tep && add_event(tep, event)) {
+ ret = TEP_ERRNO__MEM_ALLOC_FAILED;
+ goto event_add_failed;
+ }
+
+#define PRINT_ARGS 0
+ if (PRINT_ARGS && event->print_fmt.args)
+ print_args(event->print_fmt.args);
+
+ return 0;
+
+event_add_failed:
+ free_tep_event(event);
+ return ret;
+}
+
+/**
+ * tep_parse_format - parse the event format
+ * @tep: a handle to the trace event parser context
+ * @eventp: returned format
+ * @buf: the buffer storing the event format string
+ * @size: the size of @buf
+ * @sys: the system the event belongs to
+ *
+ * This parses the event format and creates an event structure
+ * to quickly parse raw data for a given event.
+ *
+ * These files currently come from:
+ *
+ * /sys/kernel/debug/tracing/events/.../.../format
+ */
+enum tep_errno tep_parse_format(struct tep_handle *tep,
+ struct tep_event **eventp,
+ const char *buf,
+ unsigned long size, const char *sys)
+{
+ return __parse_event(tep, eventp, buf, size, sys);
+}
+
+/**
+ * tep_parse_event - parse the event format
+ * @tep: a handle to the trace event parser context
+ * @buf: the buffer storing the event format string
+ * @size: the size of @buf
+ * @sys: the system the event belongs to
+ *
+ * This parses the event format and creates an event structure
+ * to quickly parse raw data for a given event.
+ *
+ * These files currently come from:
+ *
+ * /sys/kernel/debug/tracing/events/.../.../format
+ */
+enum tep_errno tep_parse_event(struct tep_handle *tep, const char *buf,
+ unsigned long size, const char *sys)
+{
+ struct tep_event *event = NULL;
+ return __parse_event(tep, &event, buf, size, sys);
+}
+
+int get_field_val(struct trace_seq *s, struct tep_format_field *field,
+ const char *name, struct tep_record *record,
+ unsigned long long *val, int err)
+{
+ if (!field) {
+ if (err)
+ trace_seq_printf(s, "<CANT FIND FIELD %s>", name);
+ return -1;
+ }
+
+ if (tep_read_number_field(field, record->data, val)) {
+ if (err)
+ trace_seq_printf(s, " %s=INVALID", name);
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * tep_get_field_raw - return the raw pointer into the data field
+ * @s: The seq to print to on error
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @len: place to store the field length.
+ * @err: print default error if failed.
+ *
+ * Returns a pointer into record->data of the field and places
+ * the length of the field in @len.
+ *
+ * On failure, it returns NULL.
+ */
+void *tep_get_field_raw(struct trace_seq *s, struct tep_event *event,
+ const char *name, struct tep_record *record,
+ int *len, int err)
+{
+ struct tep_format_field *field;
+ void *data = record->data;
+ unsigned offset;
+ int dummy;
+
+ if (!event)
+ return NULL;
+
+ field = tep_find_field(event, name);
+
+ if (!field) {
+ if (err)
+ trace_seq_printf(s, "<CANT FIND FIELD %s>", name);
+ return NULL;
+ }
+
+ /* Allow @len to be NULL */
+ if (!len)
+ len = &dummy;
+
+ offset = field->offset;
+ if (field->flags & TEP_FIELD_IS_DYNAMIC) {
+ offset = tep_read_number(event->tep,
+ data + offset, field->size);
+ *len = offset >> 16;
+ offset &= 0xffff;
+ if (field->flags & TEP_FIELD_IS_RELATIVE)
+ offset += field->offset + field->size;
+ } else
+ *len = field->size;
+
+ return data + offset;
+}
+
+/**
+ * tep_get_field_val - find a field and return its value
+ * @s: The seq to print to on error
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @val: place to store the value of the field.
+ * @err: print default error if failed.
+ *
+ * Returns 0 on success -1 on field not found.
+ */
+int tep_get_field_val(struct trace_seq *s, struct tep_event *event,
+ const char *name, struct tep_record *record,
+ unsigned long long *val, int err)
+{
+ struct tep_format_field *field;
+
+ if (!event)
+ return -1;
+
+ field = tep_find_field(event, name);
+
+ return get_field_val(s, field, name, record, val, err);
+}
+
+/**
+ * tep_get_common_field_val - find a common field and return its value
+ * @s: The seq to print to on error
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @val: place to store the value of the field.
+ * @err: print default error if failed.
+ *
+ * Returns 0 on success -1 on field not found.
+ */
+int tep_get_common_field_val(struct trace_seq *s, struct tep_event *event,
+ const char *name, struct tep_record *record,
+ unsigned long long *val, int err)
+{
+ struct tep_format_field *field;
+
+ if (!event)
+ return -1;
+
+ field = tep_find_common_field(event, name);
+
+ return get_field_val(s, field, name, record, val, err);
+}
+
+/**
+ * tep_get_any_field_val - find a any field and return its value
+ * @s: The seq to print to on error
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @val: place to store the value of the field.
+ * @err: print default error if failed.
+ *
+ * Returns 0 on success -1 on field not found.
+ */
+int tep_get_any_field_val(struct trace_seq *s, struct tep_event *event,
+ const char *name, struct tep_record *record,
+ unsigned long long *val, int err)
+{
+ struct tep_format_field *field;
+
+ if (!event)
+ return -1;
+
+ field = tep_find_any_field(event, name);
+
+ return get_field_val(s, field, name, record, val, err);
+}
+
+/**
+ * tep_print_num_field - print a field and a format
+ * @s: The seq to print to
+ * @fmt: The printf format to print the field with.
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @err: print default error if failed.
+ *
+ * Returns positive value on success, negative in case of an error,
+ * or 0 if buffer is full.
+ */
+int tep_print_num_field(struct trace_seq *s, const char *fmt,
+ struct tep_event *event, const char *name,
+ struct tep_record *record, int err)
+{
+ struct tep_format_field *field = tep_find_field(event, name);
+ unsigned long long val;
+
+ if (!field)
+ goto failed;
+
+ if (tep_read_number_field(field, record->data, &val))
+ goto failed;
+
+ return trace_seq_printf(s, fmt, val);
+
+ failed:
+ if (err)
+ trace_seq_printf(s, "CAN'T FIND FIELD \"%s\"", name);
+ return -1;
+}
+
+/**
+ * tep_print_func_field - print a field and a format for function pointers
+ * @s: The seq to print to
+ * @fmt: The printf format to print the field with.
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @err: print default error if failed.
+ *
+ * Returns positive value on success, negative in case of an error,
+ * or 0 if buffer is full.
+ */
+int tep_print_func_field(struct trace_seq *s, const char *fmt,
+ struct tep_event *event, const char *name,
+ struct tep_record *record, int err)
+{
+ struct tep_format_field *field = tep_find_field(event, name);
+ struct tep_handle *tep = event->tep;
+ unsigned long long val;
+ struct func_map *func;
+ char tmp[128];
+
+ if (!field)
+ goto failed;
+
+ if (tep_read_number_field(field, record->data, &val))
+ goto failed;
+
+ func = find_func(tep, val);
+
+ if (func)
+ snprintf(tmp, 128, "%s/0x%llx", func->func, func->addr - val);
+ else
+ sprintf(tmp, "0x%08llx", val);
+
+ return trace_seq_printf(s, fmt, tmp);
+
+ failed:
+ if (err)
+ trace_seq_printf(s, "CAN'T FIND FIELD \"%s\"", name);
+ return -1;
+}
+
+static void free_func_handle(struct tep_function_handler *func)
+{
+ struct func_params *params;
+
+ free(func->name);
+
+ while (func->params) {
+ params = func->params;
+ func->params = params->next;
+ free(params);
+ }
+
+ free(func);
+}
+
+/**
+ * tep_register_print_function - register a helper function
+ * @tep: a handle to the trace event parser context
+ * @func: the function to process the helper function
+ * @ret_type: the return type of the helper function
+ * @name: the name of the helper function
+ * @parameters: A list of enum tep_func_arg_type
+ *
+ * Some events may have helper functions in the print format arguments.
+ * This allows a plugin to dynamically create a way to process one
+ * of these functions.
+ *
+ * The @parameters is a variable list of tep_func_arg_type enums that
+ * must end with TEP_FUNC_ARG_VOID.
+ */
+int tep_register_print_function(struct tep_handle *tep,
+ tep_func_handler func,
+ enum tep_func_arg_type ret_type,
+ char *name, ...)
+{
+ struct tep_function_handler *func_handle;
+ struct func_params **next_param;
+ struct func_params *param;
+ enum tep_func_arg_type type;
+ va_list ap;
+ int ret;
+
+ func_handle = find_func_handler(tep, name);
+ if (func_handle) {
+ /*
+ * This is most like caused by the users own
+ * plugins updating the function. This overrides the
+ * system defaults.
+ */
+ tep_info("override of function helper '%s'", name);
+ remove_func_handler(tep, name);
+ }
+
+ func_handle = calloc(1, sizeof(*func_handle));
+ if (!func_handle) {
+ do_warning("Failed to allocate function handler");
+ return TEP_ERRNO__MEM_ALLOC_FAILED;
+ }
+
+ func_handle->ret_type = ret_type;
+ func_handle->name = strdup(name);
+ func_handle->func = func;
+ if (!func_handle->name) {
+ do_warning("Failed to allocate function name");
+ free(func_handle);
+ return TEP_ERRNO__MEM_ALLOC_FAILED;
+ }
+
+ next_param = &(func_handle->params);
+ va_start(ap, name);
+ for (;;) {
+ type = va_arg(ap, enum tep_func_arg_type);
+ if (type == TEP_FUNC_ARG_VOID)
+ break;
+
+ if (type >= TEP_FUNC_ARG_MAX_TYPES) {
+ do_warning("Invalid argument type %d", type);
+ ret = TEP_ERRNO__INVALID_ARG_TYPE;
+ goto out_free;
+ }
+
+ param = malloc(sizeof(*param));
+ if (!param) {
+ do_warning("Failed to allocate function param");
+ ret = TEP_ERRNO__MEM_ALLOC_FAILED;
+ goto out_free;
+ }
+ param->type = type;
+ param->next = NULL;
+
+ *next_param = param;
+ next_param = &(param->next);
+
+ func_handle->nr_args++;
+ }
+ va_end(ap);
+
+ func_handle->next = tep->func_handlers;
+ tep->func_handlers = func_handle;
+
+ return 0;
+ out_free:
+ va_end(ap);
+ free_func_handle(func_handle);
+ return ret;
+}
+
+/**
+ * tep_unregister_print_function - unregister a helper function
+ * @tep: a handle to the trace event parser context
+ * @func: the function to process the helper function
+ * @name: the name of the helper function
+ *
+ * This function removes existing print handler for function @name.
+ *
+ * Returns 0 if the handler was removed successully, -1 otherwise.
+ */
+int tep_unregister_print_function(struct tep_handle *tep,
+ tep_func_handler func, char *name)
+{
+ struct tep_function_handler *func_handle;
+
+ func_handle = find_func_handler(tep, name);
+ if (func_handle && func_handle->func == func) {
+ remove_func_handler(tep, name);
+ return 0;
+ }
+ return -1;
+}
+
+static struct tep_event *search_event(struct tep_handle *tep, int id,
+ const char *sys_name,
+ const char *event_name)
+{
+ struct tep_event *event;
+
+ if (id >= 0) {
+ /* search by id */
+ event = tep_find_event(tep, id);
+ if (!event)
+ return NULL;
+ if (event_name && (strcmp(event_name, event->name) != 0))
+ return NULL;
+ if (sys_name && (strcmp(sys_name, event->system) != 0))
+ return NULL;
+ } else {
+ event = tep_find_event_by_name(tep, sys_name, event_name);
+ if (!event)
+ return NULL;
+ }
+ return event;
+}
+
+/**
+ * tep_register_event_handler - register a way to parse an event
+ * @tep: a handle to the trace event parser context
+ * @id: the id of the event to register
+ * @sys_name: the system name the event belongs to
+ * @event_name: the name of the event
+ * @func: the function to call to parse the event information
+ * @context: the data to be passed to @func
+ *
+ * This function allows a developer to override the parsing of
+ * a given event. If for some reason the default print format
+ * is not sufficient, this function will register a function
+ * for an event to be used to parse the data instead.
+ *
+ * If @id is >= 0, then it is used to find the event.
+ * else @sys_name and @event_name are used.
+ *
+ * Returns:
+ * TEP_REGISTER_SUCCESS_OVERWRITE if an existing handler is overwritten
+ * TEP_REGISTER_SUCCESS if a new handler is registered successfully
+ * negative TEP_ERRNO_... in case of an error
+ *
+ */
+int tep_register_event_handler(struct tep_handle *tep, int id,
+ const char *sys_name, const char *event_name,
+ tep_event_handler_func func, void *context)
+{
+ struct tep_event *event;
+ struct event_handler *handle;
+
+ event = search_event(tep, id, sys_name, event_name);
+ if (event == NULL)
+ goto not_found;
+
+ tep_info("overriding event (%d) %s:%s with new print handler",
+ event->id, event->system, event->name);
+
+ event->handler = func;
+ event->context = context;
+ return TEP_REGISTER_SUCCESS_OVERWRITE;
+
+ not_found:
+ /* Save for later use. */
+ handle = calloc(1, sizeof(*handle));
+ if (!handle) {
+ do_warning("Failed to allocate event handler");
+ return TEP_ERRNO__MEM_ALLOC_FAILED;
+ }
+
+ handle->id = id;
+ if (event_name)
+ handle->event_name = strdup(event_name);
+ if (sys_name)
+ handle->sys_name = strdup(sys_name);
+
+ if ((event_name && !handle->event_name) ||
+ (sys_name && !handle->sys_name)) {
+ do_warning("Failed to allocate event/sys name");
+ free((void *)handle->event_name);
+ free((void *)handle->sys_name);
+ free(handle);
+ return TEP_ERRNO__MEM_ALLOC_FAILED;
+ }
+
+ handle->func = func;
+ handle->next = tep->handlers;
+ tep->handlers = handle;
+ handle->context = context;
+
+ return TEP_REGISTER_SUCCESS;
+}
+
+static int handle_matches(struct event_handler *handler, int id,
+ const char *sys_name, const char *event_name,
+ tep_event_handler_func func, void *context)
+{
+ if (id >= 0 && id != handler->id)
+ return 0;
+
+ if (event_name && (strcmp(event_name, handler->event_name) != 0))
+ return 0;
+
+ if (sys_name && (strcmp(sys_name, handler->sys_name) != 0))
+ return 0;
+
+ if (func != handler->func || context != handler->context)
+ return 0;
+
+ return 1;
+}
+
+/**
+ * tep_unregister_event_handler - unregister an existing event handler
+ * @tep: a handle to the trace event parser context
+ * @id: the id of the event to unregister
+ * @sys_name: the system name the handler belongs to
+ * @event_name: the name of the event handler
+ * @func: the function to call to parse the event information
+ * @context: the data to be passed to @func
+ *
+ * This function removes existing event handler (parser).
+ *
+ * If @id is >= 0, then it is used to find the event.
+ * else @sys_name and @event_name are used.
+ *
+ * Returns 0 if handler was removed successfully, -1 if event was not found.
+ */
+int tep_unregister_event_handler(struct tep_handle *tep, int id,
+ const char *sys_name, const char *event_name,
+ tep_event_handler_func func, void *context)
+{
+ struct tep_event *event;
+ struct event_handler *handle;
+ struct event_handler **next;
+
+ event = search_event(tep, id, sys_name, event_name);
+ if (event == NULL)
+ goto not_found;
+
+ if (event->handler == func && event->context == context) {
+ tep_info("removing override handler for event (%d) %s:%s. Going back to default handler.",
+ event->id, event->system, event->name);
+
+ event->handler = NULL;
+ event->context = NULL;
+ return 0;
+ }
+
+not_found:
+ for (next = &tep->handlers; *next; next = &(*next)->next) {
+ handle = *next;
+ if (handle_matches(handle, id, sys_name, event_name,
+ func, context))
+ break;
+ }
+
+ if (!(*next))
+ return -1;
+
+ *next = handle->next;
+ free_handler(handle);
+
+ return 0;
+}
+
+/**
+ * tep_alloc - create a tep handle
+ */
+struct tep_handle *tep_alloc(void)
+{
+ struct tep_handle *tep = calloc(1, sizeof(*tep));
+
+ if (tep) {
+ tep->ref_count = 1;
+ tep->host_bigendian = tep_is_bigendian();
+ }
+
+ return tep;
+}
+
+void tep_ref(struct tep_handle *tep)
+{
+ tep->ref_count++;
+}
+
+int tep_get_ref(struct tep_handle *tep)
+{
+ if (tep)
+ return tep->ref_count;
+ return 0;
+}
+
+__hidden void free_tep_format_field(struct tep_format_field *field)
+{
+ free(field->type);
+ if (field->alias != field->name)
+ free(field->alias);
+ free(field->name);
+ free(field);
+}
+
+static void free_format_fields(struct tep_format_field *field)
+{
+ struct tep_format_field *next;
+
+ while (field) {
+ next = field->next;
+ free_tep_format_field(field);
+ field = next;
+ }
+}
+
+static void free_formats(struct tep_format *format)
+{
+ free_format_fields(format->common_fields);
+ free_format_fields(format->fields);
+}
+
+__hidden void free_tep_event(struct tep_event *event)
+{
+ free(event->name);
+ free(event->system);
+
+ free_formats(&event->format);
+
+ free(event->print_fmt.format);
+ free_args(event->print_fmt.args);
+ free_parse_args(event->print_fmt.print_cache);
+ free(event);
+}
+
+/**
+ * tep_free - free a tep handle
+ * @tep: the tep handle to free
+ */
+void tep_free(struct tep_handle *tep)
+{
+ struct cmdline_list *cmdlist, *cmdnext;
+ struct func_list *funclist, *funcnext;
+ struct printk_list *printklist, *printknext;
+ struct tep_function_handler *func_handler;
+ struct event_handler *handle;
+ int i;
+
+ if (!tep)
+ return;
+
+ cmdlist = tep->cmdlist;
+ funclist = tep->funclist;
+ printklist = tep->printklist;
+
+ tep->ref_count--;
+ if (tep->ref_count)
+ return;
+
+ if (tep->cmdlines) {
+ for (i = 0; i < tep->cmdline_count; i++)
+ free(tep->cmdlines[i].comm);
+ free(tep->cmdlines);
+ }
+
+ while (cmdlist) {
+ cmdnext = cmdlist->next;
+ free(cmdlist->comm);
+ free(cmdlist);
+ cmdlist = cmdnext;
+ }
+
+ if (tep->func_map) {
+ for (i = 0; i < (int)tep->func_count; i++) {
+ free(tep->func_map[i].func);
+ free(tep->func_map[i].mod);
+ }
+ free(tep->func_map);
+ }
+
+ while (funclist) {
+ funcnext = funclist->next;
+ free(funclist->func);
+ free(funclist->mod);
+ free(funclist);
+ funclist = funcnext;
+ }
+
+ while (tep->func_handlers) {
+ func_handler = tep->func_handlers;
+ tep->func_handlers = func_handler->next;
+ free_func_handle(func_handler);
+ }
+
+ if (tep->printk_map) {
+ for (i = 0; i < (int)tep->printk_count; i++)
+ free(tep->printk_map[i].printk);
+ free(tep->printk_map);
+ }
+
+ while (printklist) {
+ printknext = printklist->next;
+ free(printklist->printk);
+ free(printklist);
+ printklist = printknext;
+ }
+
+ for (i = 0; i < tep->nr_events; i++)
+ free_tep_event(tep->events[i]);
+
+ while (tep->handlers) {
+ handle = tep->handlers;
+ tep->handlers = handle->next;
+ free_handler(handle);
+ }
+
+ free(tep->events);
+ free(tep->sort_events);
+ free(tep->func_resolver);
+ free_tep_plugin_paths(tep);
+
+ free(tep);
+}
+
+void tep_unref(struct tep_handle *tep)
+{
+ tep_free(tep);
+}
diff --git a/src/event-plugin.c b/src/event-plugin.c
new file mode 100644
index 0000000..f42243f
--- /dev/null
+++ b/src/event-plugin.c
@@ -0,0 +1,711 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include "event-parse.h"
+#include "event-parse-local.h"
+#include "event-utils.h"
+#include "trace-seq.h"
+
+#define LOCAL_PLUGIN_DIR ".local/lib/traceevent/plugins/"
+
+static struct registered_plugin_options {
+ struct registered_plugin_options *next;
+ struct tep_plugin_option *options;
+} *registered_options;
+
+static struct trace_plugin_options {
+ struct trace_plugin_options *next;
+ char *plugin;
+ char *option;
+ char *value;
+} *trace_plugin_options;
+
+struct tep_plugin_list {
+ struct tep_plugin_list *next;
+ char *name;
+ void *handle;
+};
+
+struct tep_plugins_dir {
+ struct tep_plugins_dir *next;
+ char *path;
+ enum tep_plugin_load_priority prio;
+};
+
+static void lower_case(char *str)
+{
+ if (!str)
+ return;
+ for (; *str; str++)
+ *str = tolower(*str);
+}
+
+static int update_option_value(struct tep_plugin_option *op, const char *val)
+{
+ char *op_val;
+
+ if (!val) {
+ /* toggle, only if option is boolean */
+ if (op->value)
+ /* Warn? */
+ return 0;
+ op->set ^= 1;
+ return 0;
+ }
+
+ /*
+ * If the option has a value then it takes a string
+ * otherwise the option is a boolean.
+ */
+ if (op->value) {
+ op->value = val;
+ return 0;
+ }
+
+ /* Option is boolean, must be either "1", "0", "true" or "false" */
+
+ op_val = strdup(val);
+ if (!op_val)
+ return -1;
+ lower_case(op_val);
+
+ if (strcmp(val, "1") == 0 || strcmp(val, "true") == 0)
+ op->set = 1;
+ else if (strcmp(val, "0") == 0 || strcmp(val, "false") == 0)
+ op->set = 0;
+ free(op_val);
+
+ return 0;
+}
+
+/**
+ * tep_plugin_list_options - get list of plugin options
+ *
+ * Returns an array of char strings that list the currently registered
+ * plugin options in the format of <plugin>:<option>. This list can be
+ * used by toggling the option.
+ *
+ * Returns NULL if there's no options registered. On error it returns
+ * INVALID_PLUGIN_LIST_OPTION
+ *
+ * Must be freed with tep_plugin_free_options_list().
+ */
+char **tep_plugin_list_options(void)
+{
+ struct registered_plugin_options *reg;
+ struct tep_plugin_option *op;
+ char **list = NULL;
+ char *name;
+ int count = 0;
+
+ for (reg = registered_options; reg; reg = reg->next) {
+ for (op = reg->options; op->name; op++) {
+ char *alias = op->plugin_alias ? op->plugin_alias : op->file;
+ char **temp = list;
+ int ret;
+
+ ret = asprintf(&name, "%s:%s", alias, op->name);
+ if (ret < 0)
+ goto err;
+
+ list = realloc(list, count + 2);
+ if (!list) {
+ list = temp;
+ free(name);
+ goto err;
+ }
+ list[count++] = name;
+ list[count] = NULL;
+ }
+ }
+ return list;
+
+ err:
+ while (--count >= 0)
+ free(list[count]);
+ free(list);
+
+ return INVALID_PLUGIN_LIST_OPTION;
+}
+
+void tep_plugin_free_options_list(char **list)
+{
+ int i;
+
+ if (!list)
+ return;
+
+ if (list == INVALID_PLUGIN_LIST_OPTION)
+ return;
+
+ for (i = 0; list[i]; i++)
+ free(list[i]);
+
+ free(list);
+}
+
+static int
+update_option(const char *file, struct tep_plugin_option *option)
+{
+ struct trace_plugin_options *op;
+ char *plugin;
+ int ret = 0;
+
+ if (option->plugin_alias) {
+ plugin = strdup(option->plugin_alias);
+ if (!plugin)
+ return -1;
+ } else {
+ char *p;
+ plugin = strdup(file);
+ if (!plugin)
+ return -1;
+ p = strstr(plugin, ".");
+ if (p)
+ *p = '\0';
+ }
+
+ /* first look for named options */
+ for (op = trace_plugin_options; op; op = op->next) {
+ if (!op->plugin)
+ continue;
+ if (strcmp(op->plugin, plugin) != 0)
+ continue;
+ if (strcmp(op->option, option->name) != 0)
+ continue;
+
+ ret = update_option_value(option, op->value);
+ if (ret)
+ goto out;
+ break;
+ }
+
+ /* first look for unnamed options */
+ for (op = trace_plugin_options; op; op = op->next) {
+ if (op->plugin)
+ continue;
+ if (strcmp(op->option, option->name) != 0)
+ continue;
+
+ ret = update_option_value(option, op->value);
+ break;
+ }
+
+ out:
+ free(plugin);
+ return ret;
+}
+
+/**
+ * tep_plugin_add_options - Add a set of options by a plugin
+ * @name: The name of the plugin adding the options
+ * @options: The set of options being loaded
+ *
+ * Sets the options with the values that have been added by user.
+ */
+int tep_plugin_add_options(const char *name,
+ struct tep_plugin_option *options)
+{
+ struct registered_plugin_options *reg;
+
+ reg = malloc(sizeof(*reg));
+ if (!reg)
+ return -1;
+ reg->next = registered_options;
+ reg->options = options;
+ registered_options = reg;
+
+ while (options->name) {
+ update_option(name, options);
+ options++;
+ }
+ return 0;
+}
+
+/**
+ * tep_plugin_remove_options - remove plugin options that were registered
+ * @options: Options to removed that were registered with tep_plugin_add_options
+ */
+void tep_plugin_remove_options(struct tep_plugin_option *options)
+{
+ struct registered_plugin_options **last;
+ struct registered_plugin_options *reg;
+
+ for (last = &registered_options; *last; last = &(*last)->next) {
+ if ((*last)->options == options) {
+ reg = *last;
+ *last = reg->next;
+ free(reg);
+ return;
+ }
+ }
+}
+
+static int parse_option_name(char **option, char **plugin)
+{
+ char *p;
+
+ *plugin = NULL;
+
+ if ((p = strstr(*option, ":"))) {
+ *plugin = *option;
+ *p = '\0';
+ *option = strdup(p + 1);
+ if (!*option)
+ return -1;
+ }
+ return 0;
+}
+
+static struct tep_plugin_option *
+find_registered_option(const char *plugin, const char *option)
+{
+ struct registered_plugin_options *reg;
+ struct tep_plugin_option *op;
+ const char *op_plugin;
+
+ for (reg = registered_options; reg; reg = reg->next) {
+ for (op = reg->options; op->name; op++) {
+ if (op->plugin_alias)
+ op_plugin = op->plugin_alias;
+ else
+ op_plugin = op->file;
+
+ if (plugin && strcmp(plugin, op_plugin) != 0)
+ continue;
+ if (strcmp(option, op->name) != 0)
+ continue;
+
+ return op;
+ }
+ }
+
+ return NULL;
+}
+
+static int process_option(const char *plugin, const char *option, const char *val)
+{
+ struct tep_plugin_option *op;
+
+ op = find_registered_option(plugin, option);
+ if (!op)
+ return 0;
+
+ return update_option_value(op, val);
+}
+
+/**
+ * tep_plugin_add_option - add an option/val pair to set plugin options
+ * @name: The name of the option (format: <plugin>:<option> or just <option>)
+ * @val: (optional) the value for the option
+ *
+ * Modify a plugin option. If @val is given than the value of the option
+ * is set (note, some options just take a boolean, so @val must be either
+ * "1" or "0" or "true" or "false").
+ */
+int tep_plugin_add_option(const char *name, const char *val)
+{
+ struct trace_plugin_options *op;
+ char *option_str;
+ char *plugin;
+
+ option_str = strdup(name);
+ if (!option_str)
+ return -ENOMEM;
+
+ if (parse_option_name(&option_str, &plugin) < 0)
+ return -ENOMEM;
+
+ /* If the option exists, update the val */
+ for (op = trace_plugin_options; op; op = op->next) {
+ /* Both must be NULL or not NULL */
+ if ((!plugin || !op->plugin) && plugin != op->plugin)
+ continue;
+ if (plugin && strcmp(plugin, op->plugin) != 0)
+ continue;
+ if (strcmp(op->option, option_str) != 0)
+ continue;
+
+ /* update option */
+ free(op->value);
+ if (val) {
+ op->value = strdup(val);
+ if (!op->value)
+ goto out_free;
+ } else
+ op->value = NULL;
+
+ /* plugin and option_str don't get freed at the end */
+ free(plugin);
+ free(option_str);
+
+ plugin = op->plugin;
+ option_str = op->option;
+ break;
+ }
+
+ /* If not found, create */
+ if (!op) {
+ op = malloc(sizeof(*op));
+ if (!op)
+ goto out_free;
+ memset(op, 0, sizeof(*op));
+ op->plugin = plugin;
+ op->option = option_str;
+ if (val) {
+ op->value = strdup(val);
+ if (!op->value) {
+ free(op);
+ goto out_free;
+ }
+ }
+ op->next = trace_plugin_options;
+ trace_plugin_options = op;
+ }
+
+ return process_option(plugin, option_str, val);
+
+out_free:
+ free(plugin);
+ free(option_str);
+ return -ENOMEM;
+}
+
+static void print_op_data(struct trace_seq *s, const char *name,
+ const char *op)
+{
+ if (op)
+ trace_seq_printf(s, "%8s:\t%s\n", name, op);
+}
+
+/**
+ * tep_plugin_print_options - print out the registered plugin options
+ * @s: The trace_seq descriptor to write the plugin options into
+ *
+ * Writes a list of options into trace_seq @s.
+ */
+void tep_plugin_print_options(struct trace_seq *s)
+{
+ struct registered_plugin_options *reg;
+ struct tep_plugin_option *op;
+
+ for (reg = registered_options; reg; reg = reg->next) {
+ if (reg != registered_options)
+ trace_seq_printf(s, "============\n");
+ for (op = reg->options; op->name; op++) {
+ if (op != reg->options)
+ trace_seq_printf(s, "------------\n");
+ print_op_data(s, "file", op->file);
+ print_op_data(s, "plugin", op->plugin_alias);
+ print_op_data(s, "option", op->name);
+ print_op_data(s, "desc", op->description);
+ print_op_data(s, "value", op->value);
+ trace_seq_printf(s, "%8s:\t%d\n", "set", op->set);
+ }
+ }
+}
+
+/**
+ * tep_print_plugins - print out the list of plugins loaded
+ * @s: the trace_seq descripter to write to
+ * @prefix: The prefix string to add before listing the option name
+ * @suffix: The suffix string ot append after the option name
+ * @list: The list of plugins (usually returned by tep_load_plugins()
+ *
+ * Writes to the trace_seq @s the list of plugins (files) that is
+ * returned by tep_load_plugins(). Use @prefix and @suffix for formating:
+ * @prefix = " ", @suffix = "\n".
+ */
+void tep_print_plugins(struct trace_seq *s,
+ const char *prefix, const char *suffix,
+ const struct tep_plugin_list *list)
+{
+ while (list) {
+ trace_seq_printf(s, "%s%s%s", prefix, list->name, suffix);
+ list = list->next;
+ }
+}
+
+static void
+load_plugin(struct tep_handle *tep, const char *path,
+ const char *file, void *data)
+{
+ struct tep_plugin_list **plugin_list = data;
+ struct tep_plugin_option *options;
+ tep_plugin_load_func func;
+ struct tep_plugin_list *list;
+ const char *alias;
+ char *plugin;
+ void *handle;
+ int ret;
+
+ ret = asprintf(&plugin, "%s/%s", path, file);
+ if (ret < 0) {
+ tep_warning("could not allocate plugin memory\n");
+ return;
+ }
+
+ handle = dlopen(plugin, RTLD_NOW | RTLD_GLOBAL);
+ if (!handle) {
+ tep_warning("could not load plugin '%s'\n%s\n",
+ plugin, dlerror());
+ goto out_free;
+ }
+
+ alias = dlsym(handle, TEP_PLUGIN_ALIAS_NAME);
+ if (!alias)
+ alias = file;
+
+ options = dlsym(handle, TEP_PLUGIN_OPTIONS_NAME);
+ if (options) {
+ while (options->name) {
+ ret = update_option(alias, options);
+ if (ret < 0)
+ goto out_free;
+ options++;
+ }
+ }
+
+ func = dlsym(handle, TEP_PLUGIN_LOADER_NAME);
+ if (!func) {
+ tep_warning("could not find func '%s' in plugin '%s'\n%s\n",
+ TEP_PLUGIN_LOADER_NAME, plugin, dlerror());
+ goto out_free;
+ }
+
+ list = malloc(sizeof(*list));
+ if (!list) {
+ tep_warning("could not allocate plugin memory\n");
+ goto out_free;
+ }
+
+ list->next = *plugin_list;
+ list->handle = handle;
+ list->name = plugin;
+ *plugin_list = list;
+
+ tep_info("registering plugin: %s", plugin);
+ func(tep);
+ return;
+
+ out_free:
+ free(plugin);
+}
+
+static void
+load_plugins_dir(struct tep_handle *tep, const char *suffix,
+ const char *path,
+ void (*load_plugin)(struct tep_handle *tep,
+ const char *path,
+ const char *name,
+ void *data),
+ void *data)
+{
+ struct dirent *dent;
+ struct stat st;
+ DIR *dir;
+ int ret;
+
+ ret = stat(path, &st);
+ if (ret < 0)
+ return;
+
+ if (!S_ISDIR(st.st_mode))
+ return;
+
+ dir = opendir(path);
+ if (!dir)
+ return;
+
+ while ((dent = readdir(dir))) {
+ const char *name = dent->d_name;
+
+ if (strcmp(name, ".") == 0 ||
+ strcmp(name, "..") == 0)
+ continue;
+
+ /* Only load plugins that end in suffix */
+ if (strcmp(name + (strlen(name) - strlen(suffix)), suffix) != 0)
+ continue;
+
+ load_plugin(tep, path, name, data);
+ }
+
+ closedir(dir);
+}
+
+/**
+ * tep_load_plugins_hook - call a user specified callback to load a plugin
+ * @tep: handler to traceevent context
+ * @suffix: filter only plugin files with given suffix
+ * @load_plugin: user specified callback, called for each plugin file
+ * @data: custom context, passed to @load_plugin
+ *
+ * Searches for traceevent plugin files and calls @load_plugin for each
+ * The order of plugins search is:
+ * - Directories, specified in @tep->plugins_dir and priority TEP_PLUGIN_FIRST
+ * - Directory, specified at compile time with PLUGIN_TRACEEVENT_DIR
+ * - Directory, specified by environment variable TRACEEVENT_PLUGIN_DIR
+ * - In user's home: ~/.local/lib/traceevent/plugins/
+ * - Directories, specified in @tep->plugins_dir and priority TEP_PLUGIN_LAST
+ *
+ */
+void tep_load_plugins_hook(struct tep_handle *tep, const char *suffix,
+ void (*load_plugin)(struct tep_handle *tep,
+ const char *path,
+ const char *name,
+ void *data),
+ void *data)
+{
+ struct tep_plugins_dir *dir = NULL;
+ char *home;
+ char *path;
+ char *envdir;
+ int ret;
+
+ if (tep && tep->flags & TEP_DISABLE_PLUGINS)
+ return;
+
+ if (tep)
+ dir = tep->plugins_dir;
+ while (dir) {
+ if (dir->prio == TEP_PLUGIN_FIRST)
+ load_plugins_dir(tep, suffix, dir->path,
+ load_plugin, data);
+ dir = dir->next;
+ }
+
+ /*
+ * If a system plugin directory was defined,
+ * check that first.
+ */
+#ifdef PLUGIN_DIR
+ if (!tep || !(tep->flags & TEP_DISABLE_SYS_PLUGINS))
+ load_plugins_dir(tep, suffix, PLUGIN_DIR,
+ load_plugin, data);
+#endif
+
+ /*
+ * Next let the environment-set plugin directory
+ * override the system defaults.
+ */
+ envdir = getenv("TRACEEVENT_PLUGIN_DIR");
+ if (envdir)
+ load_plugins_dir(tep, suffix, envdir, load_plugin, data);
+
+ /*
+ * Now let the home directory override the environment
+ * or system defaults.
+ */
+ home = getenv("HOME");
+ if (!home)
+ return;
+
+ ret = asprintf(&path, "%s/%s", home, LOCAL_PLUGIN_DIR);
+ if (ret < 0) {
+ tep_warning("could not allocate plugin memory\n");
+ return;
+ }
+
+ load_plugins_dir(tep, suffix, path, load_plugin, data);
+
+ if (tep)
+ dir = tep->plugins_dir;
+ while (dir) {
+ if (dir->prio == TEP_PLUGIN_LAST)
+ load_plugins_dir(tep, suffix, dir->path,
+ load_plugin, data);
+ dir = dir->next;
+ }
+
+ free(path);
+}
+
+struct tep_plugin_list*
+tep_load_plugins(struct tep_handle *tep)
+{
+ struct tep_plugin_list *list = NULL;
+
+ tep_load_plugins_hook(tep, ".so", load_plugin, &list);
+ return list;
+}
+
+/**
+ * tep_add_plugin_path - Add a new plugin directory.
+ * @tep: Trace event handler.
+ * @path: Path to a directory. All plugin files in that
+ * directory will be loaded.
+ *@prio: Load priority of the plugins in that directory.
+ *
+ * Returns -1 in case of an error, 0 otherwise.
+ */
+int tep_add_plugin_path(struct tep_handle *tep, char *path,
+ enum tep_plugin_load_priority prio)
+{
+ struct tep_plugins_dir *dir;
+
+ if (!tep || !path)
+ return -1;
+
+ dir = calloc(1, sizeof(*dir));
+ if (!dir)
+ return -1;
+
+ dir->path = strdup(path);
+ if (!dir->path) {
+ free(dir);
+ return -1;
+ }
+ dir->prio = prio;
+ dir->next = tep->plugins_dir;
+ tep->plugins_dir = dir;
+
+ return 0;
+}
+
+__hidden void free_tep_plugin_paths(struct tep_handle *tep)
+{
+ struct tep_plugins_dir *dir;
+
+ if (!tep)
+ return;
+
+ dir = tep->plugins_dir;
+ while (dir) {
+ tep->plugins_dir = tep->plugins_dir->next;
+ free(dir->path);
+ free(dir);
+ dir = tep->plugins_dir;
+ }
+}
+
+void
+tep_unload_plugins(struct tep_plugin_list *plugin_list, struct tep_handle *tep)
+{
+ tep_plugin_unload_func func;
+ struct tep_plugin_list *list;
+
+ while (plugin_list) {
+ list = plugin_list;
+ plugin_list = list->next;
+ func = dlsym(list->handle, TEP_PLUGIN_UNLOADER_NAME);
+ if (func)
+ func(tep);
+ dlclose(list->handle);
+ free(list->name);
+ free(list);
+ }
+}
diff --git a/src/kbuffer-parse.c b/src/kbuffer-parse.c
new file mode 100644
index 0000000..390a789
--- /dev/null
+++ b/src/kbuffer-parse.c
@@ -0,0 +1,848 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include <sys/utsname.h>
+
+#include "kbuffer.h"
+
+#define MISSING_EVENTS (1UL << 31)
+#define MISSING_STORED (1UL << 30)
+
+#define COMMIT_MASK ((1 << 27) - 1)
+
+/* Absolute time stamps do not have the 5 MSB, take from the real time stamp */
+#define TS_MSB (0xf8ULL << 56)
+
+enum {
+ KBUFFER_FL_HOST_BIG_ENDIAN = (1<<0),
+ KBUFFER_FL_BIG_ENDIAN = (1<<1),
+ KBUFFER_FL_LONG_8 = (1<<2),
+ KBUFFER_FL_OLD_FORMAT = (1<<3),
+};
+
+#define ENDIAN_MASK (KBUFFER_FL_HOST_BIG_ENDIAN | KBUFFER_FL_BIG_ENDIAN)
+
+/** kbuffer
+ * @timestamp - timestamp of current event
+ * @lost_events - # of lost events between this subbuffer and previous
+ * @flags - special flags of the kbuffer
+ * @subbuffer - pointer to the sub-buffer page
+ * @data - pointer to the start of data on the sub-buffer page
+ * @index - index from @data to the @curr event data
+ * @curr - offset from @data to the start of current event
+ * (includes metadata)
+ * @next - offset from @data to the start of next event
+ * @size - The size of data on @data
+ * @start - The offset from @subbuffer where @data lives
+ * @first - The offset from @subbuffer where the first non time stamp event lives
+ *
+ * @read_4 - Function to read 4 raw bytes (may swap)
+ * @read_8 - Function to read 8 raw bytes (may swap)
+ * @read_long - Function to read a long word (4 or 8 bytes with needed swap)
+ */
+struct kbuffer {
+ unsigned long long timestamp;
+ long long lost_events;
+ unsigned long flags;
+ void *subbuffer;
+ void *data;
+ unsigned int index;
+ unsigned int curr;
+ unsigned int next;
+ unsigned int size;
+ unsigned int start;
+ unsigned int first;
+
+ unsigned int (*read_4)(void *ptr);
+ unsigned long long (*read_8)(void *ptr);
+ unsigned long long (*read_long)(struct kbuffer *kbuf, void *ptr);
+ int (*next_event)(struct kbuffer *kbuf);
+};
+
+static void *zmalloc(size_t size)
+{
+ return calloc(1, size);
+}
+
+static int host_is_bigendian(void)
+{
+ unsigned char str[] = { 0x1, 0x2, 0x3, 0x4 };
+ unsigned int *ptr;
+
+ ptr = (unsigned int *)str;
+ return *ptr == 0x01020304;
+}
+
+static int do_swap(struct kbuffer *kbuf)
+{
+ return ((kbuf->flags & KBUFFER_FL_HOST_BIG_ENDIAN) + kbuf->flags) &
+ ENDIAN_MASK;
+}
+
+static unsigned long long __read_8(void *ptr)
+{
+ unsigned long long data = *(unsigned long long *)ptr;
+
+ return data;
+}
+
+static unsigned long long __read_8_sw(void *ptr)
+{
+ unsigned long long data = *(unsigned long long *)ptr;
+ unsigned long long swap;
+
+ swap = ((data & 0xffULL) << 56) |
+ ((data & (0xffULL << 8)) << 40) |
+ ((data & (0xffULL << 16)) << 24) |
+ ((data & (0xffULL << 24)) << 8) |
+ ((data & (0xffULL << 32)) >> 8) |
+ ((data & (0xffULL << 40)) >> 24) |
+ ((data & (0xffULL << 48)) >> 40) |
+ ((data & (0xffULL << 56)) >> 56);
+
+ return swap;
+}
+
+static unsigned int __read_4(void *ptr)
+{
+ unsigned int data = *(unsigned int *)ptr;
+
+ return data;
+}
+
+static unsigned int __read_4_sw(void *ptr)
+{
+ unsigned int data = *(unsigned int *)ptr;
+ unsigned int swap;
+
+ swap = ((data & 0xffULL) << 24) |
+ ((data & (0xffULL << 8)) << 8) |
+ ((data & (0xffULL << 16)) >> 8) |
+ ((data & (0xffULL << 24)) >> 24);
+
+ return swap;
+}
+
+static unsigned long long read_8(struct kbuffer *kbuf, void *ptr)
+{
+ return kbuf->read_8(ptr);
+}
+
+static unsigned int read_4(struct kbuffer *kbuf, void *ptr)
+{
+ return kbuf->read_4(ptr);
+}
+
+static unsigned long long __read_long_8(struct kbuffer *kbuf, void *ptr)
+{
+ return kbuf->read_8(ptr);
+}
+
+static unsigned long long __read_long_4(struct kbuffer *kbuf, void *ptr)
+{
+ return kbuf->read_4(ptr);
+}
+
+static unsigned long long read_long(struct kbuffer *kbuf, void *ptr)
+{
+ return kbuf->read_long(kbuf, ptr);
+}
+
+static int calc_index(struct kbuffer *kbuf, void *ptr)
+{
+ return (unsigned long)ptr - (unsigned long)kbuf->data;
+}
+
+static int __next_event(struct kbuffer *kbuf);
+
+/*
+ * Just because sizeof(long) is 4 bytes, doesn't mean the OS isn't
+ * 64bits
+ */
+static bool host_is_32bit(void)
+{
+ struct utsname buf;
+ int ret;
+
+ ret = uname(&buf);
+ if (ret < 0) {
+ /* Oh well, just assume it is 32 bit */
+ return true;
+ }
+ /* If the uname machine value contains 64, assume the kernel is 64 bit */
+ return strstr(buf.machine, "64") == NULL;
+}
+
+/**
+ * kbuffer_alloc - allocat a new kbuffer
+ * @size; enum to denote size of word
+ * @endian: enum to denote endianness
+ *
+ * Allocates and returns a new kbuffer.
+ */
+struct kbuffer *
+kbuffer_alloc(enum kbuffer_long_size size, enum kbuffer_endian endian)
+{
+ struct kbuffer *kbuf;
+ int flags = 0;
+
+ switch (size) {
+ case KBUFFER_LSIZE_4:
+ break;
+ case KBUFFER_LSIZE_SAME_AS_HOST:
+ if (sizeof(long) != 8 && host_is_32bit())
+ break;
+ /* fallthrough */
+ case KBUFFER_LSIZE_8:
+ flags |= KBUFFER_FL_LONG_8;
+ break;
+ default:
+ return NULL;
+ }
+
+ switch (endian) {
+ case KBUFFER_ENDIAN_LITTLE:
+ case KBUFFER_ENDIAN_SAME_AS_HOST:
+ break;
+ case KBUFFER_ENDIAN_BIG:
+ flags |= KBUFFER_FL_BIG_ENDIAN;
+ break;
+ default:
+ return NULL;
+ }
+
+ kbuf = zmalloc(sizeof(*kbuf));
+ if (!kbuf)
+ return NULL;
+
+ kbuf->flags = flags;
+
+ if (host_is_bigendian()) {
+ if (endian == KBUFFER_ENDIAN_SAME_AS_HOST)
+ flags |= KBUFFER_FL_BIG_ENDIAN;
+ kbuf->flags |= KBUFFER_FL_HOST_BIG_ENDIAN;
+ }
+
+ if (do_swap(kbuf)) {
+ kbuf->read_8 = __read_8_sw;
+ kbuf->read_4 = __read_4_sw;
+ } else {
+ kbuf->read_8 = __read_8;
+ kbuf->read_4 = __read_4;
+ }
+
+ if (kbuf->flags & KBUFFER_FL_LONG_8)
+ kbuf->read_long = __read_long_8;
+ else
+ kbuf->read_long = __read_long_4;
+
+ /* May be changed by kbuffer_set_old_format() */
+ kbuf->next_event = __next_event;
+
+ return kbuf;
+}
+
+/** kbuffer_free - free an allocated kbuffer
+ * @kbuf: The kbuffer to free
+ *
+ * Can take NULL as a parameter.
+ */
+void kbuffer_free(struct kbuffer *kbuf)
+{
+ free(kbuf);
+}
+
+static unsigned int type4host(struct kbuffer *kbuf,
+ unsigned int type_len_ts)
+{
+ if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN)
+ return (type_len_ts >> 29) & 3;
+ else
+ return type_len_ts & 3;
+}
+
+static unsigned int len4host(struct kbuffer *kbuf,
+ unsigned int type_len_ts)
+{
+ if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN)
+ return (type_len_ts >> 27) & 7;
+ else
+ return (type_len_ts >> 2) & 7;
+}
+
+static unsigned int type_len4host(struct kbuffer *kbuf,
+ unsigned int type_len_ts)
+{
+ if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN)
+ return (type_len_ts >> 27) & ((1 << 5) - 1);
+ else
+ return type_len_ts & ((1 << 5) - 1);
+}
+
+static unsigned int ts4host(struct kbuffer *kbuf,
+ unsigned int type_len_ts)
+{
+ if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN)
+ return type_len_ts & ((1 << 27) - 1);
+ else
+ return type_len_ts >> 5;
+}
+
+/*
+ * Linux 2.6.30 and earlier (not much ealier) had a different
+ * ring buffer format. It should be obsolete, but we handle it anyway.
+ */
+enum old_ring_buffer_type {
+ OLD_RINGBUF_TYPE_PADDING,
+ OLD_RINGBUF_TYPE_TIME_EXTEND,
+ OLD_RINGBUF_TYPE_TIME_STAMP,
+ OLD_RINGBUF_TYPE_DATA,
+};
+
+static unsigned int old_update_pointers(struct kbuffer *kbuf)
+{
+ unsigned long long extend;
+ unsigned int type_len_ts;
+ unsigned int type;
+ unsigned int len;
+ unsigned int delta;
+ unsigned int length;
+ void *ptr = kbuf->data + kbuf->curr;
+
+ type_len_ts = read_4(kbuf, ptr);
+ ptr += 4;
+
+ type = type4host(kbuf, type_len_ts);
+ len = len4host(kbuf, type_len_ts);
+ delta = ts4host(kbuf, type_len_ts);
+
+ switch (type) {
+ case OLD_RINGBUF_TYPE_PADDING:
+ kbuf->next = kbuf->size;
+ return 0;
+
+ case OLD_RINGBUF_TYPE_TIME_EXTEND:
+ extend = read_4(kbuf, ptr);
+ extend <<= TS_SHIFT;
+ extend += delta;
+ delta = extend;
+ ptr += 4;
+ length = 0;
+ break;
+
+ case OLD_RINGBUF_TYPE_TIME_STAMP:
+ /* should never happen! */
+ kbuf->curr = kbuf->size;
+ kbuf->next = kbuf->size;
+ kbuf->index = kbuf->size;
+ return -1;
+ default:
+ if (len)
+ length = len * 4;
+ else {
+ length = read_4(kbuf, ptr);
+ length -= 4;
+ ptr += 4;
+ }
+ break;
+ }
+
+ kbuf->timestamp += delta;
+ kbuf->index = calc_index(kbuf, ptr);
+ kbuf->next = kbuf->index + length;
+
+ return type;
+}
+
+static int __old_next_event(struct kbuffer *kbuf)
+{
+ int type;
+
+ do {
+ kbuf->curr = kbuf->next;
+ if (kbuf->next >= kbuf->size)
+ return -1;
+ type = old_update_pointers(kbuf);
+ } while (type == OLD_RINGBUF_TYPE_TIME_EXTEND || type == OLD_RINGBUF_TYPE_PADDING);
+
+ return 0;
+}
+
+static unsigned int
+translate_data(struct kbuffer *kbuf, void *data, void **rptr,
+ unsigned long long *delta, int *length)
+{
+ unsigned long long extend, msb = 0;
+ unsigned int type_len_ts;
+ unsigned int type_len;
+
+ type_len_ts = read_4(kbuf, data);
+ data += 4;
+
+ type_len = type_len4host(kbuf, type_len_ts);
+ *delta = ts4host(kbuf, type_len_ts);
+
+ switch (type_len) {
+ case KBUFFER_TYPE_PADDING:
+ *length = read_4(kbuf, data);
+ break;
+
+ case KBUFFER_TYPE_TIME_STAMP:
+ msb = kbuf->timestamp & TS_MSB;
+ /* fall through */
+ case KBUFFER_TYPE_TIME_EXTEND:
+ extend = read_4(kbuf, data);
+ data += 4;
+ extend <<= TS_SHIFT;
+ extend += *delta;
+ *delta = extend | msb;
+ *length = 0;
+ break;
+
+ case 0:
+ *length = read_4(kbuf, data) - 4;
+ *length = (*length + 3) & ~3;
+ data += 4;
+ break;
+ default:
+ *length = type_len * 4;
+ break;
+ }
+
+ *rptr = data;
+
+ return type_len;
+}
+
+static unsigned int update_pointers(struct kbuffer *kbuf)
+{
+ unsigned long long delta;
+ unsigned int type_len;
+ int length;
+ void *ptr = kbuf->data + kbuf->curr;
+
+ type_len = translate_data(kbuf, ptr, &ptr, &delta, &length);
+
+ if (type_len == KBUFFER_TYPE_TIME_STAMP)
+ kbuf->timestamp = delta;
+ else
+ kbuf->timestamp += delta;
+
+ kbuf->index = calc_index(kbuf, ptr);
+ kbuf->next = kbuf->index + length;
+
+ return type_len;
+}
+
+/**
+ * kbuffer_translate_data - read raw data to get a record
+ * @swap: Set to 1 if bytes in words need to be swapped when read
+ * @data: The raw data to read
+ * @size: Address to store the size of the event data.
+ *
+ * Returns a pointer to the event data. To determine the entire
+ * record size (record metadata + data) just add the difference between
+ * @data and the returned value to @size.
+ */
+void *kbuffer_translate_data(int swap, void *data, unsigned int *size)
+{
+ unsigned long long delta;
+ struct kbuffer kbuf;
+ int type_len;
+ int length;
+ void *ptr;
+
+ if (swap) {
+ kbuf.read_8 = __read_8_sw;
+ kbuf.read_4 = __read_4_sw;
+ kbuf.flags = host_is_bigendian() ? 0 : KBUFFER_FL_BIG_ENDIAN;
+ } else {
+ kbuf.read_8 = __read_8;
+ kbuf.read_4 = __read_4;
+ kbuf.flags = host_is_bigendian() ? KBUFFER_FL_BIG_ENDIAN: 0;
+ }
+
+ type_len = translate_data(&kbuf, data, &ptr, &delta, &length);
+ switch (type_len) {
+ case KBUFFER_TYPE_PADDING:
+ case KBUFFER_TYPE_TIME_EXTEND:
+ case KBUFFER_TYPE_TIME_STAMP:
+ return NULL;
+ }
+
+ *size = length;
+
+ return ptr;
+}
+
+static int __next_event(struct kbuffer *kbuf)
+{
+ int type;
+
+ do {
+ kbuf->curr = kbuf->next;
+ if (kbuf->next >= kbuf->size)
+ return -1;
+ type = update_pointers(kbuf);
+ } while (type == KBUFFER_TYPE_TIME_EXTEND ||
+ type == KBUFFER_TYPE_TIME_STAMP ||
+ type == KBUFFER_TYPE_PADDING);
+
+ return 0;
+}
+
+static int next_event(struct kbuffer *kbuf)
+{
+ return kbuf->next_event(kbuf);
+}
+
+/**
+ * kbuffer_next_event - increment the current pointer
+ * @kbuf: The kbuffer to read
+ * @ts: Address to store the next record's timestamp (may be NULL to ignore)
+ *
+ * Increments the pointers into the subbuffer of the kbuffer to point to the
+ * next event so that the next kbuffer_read_event() will return a
+ * new event.
+ *
+ * Returns the data of the next event if a new event exists on the subbuffer,
+ * NULL otherwise.
+ */
+void *kbuffer_next_event(struct kbuffer *kbuf, unsigned long long *ts)
+{
+ int ret;
+
+ if (!kbuf || !kbuf->subbuffer)
+ return NULL;
+
+ ret = next_event(kbuf);
+ if (ret < 0)
+ return NULL;
+
+ if (ts)
+ *ts = kbuf->timestamp;
+
+ return kbuf->data + kbuf->index;
+}
+
+/**
+ * kbuffer_load_subbuffer - load a new subbuffer into the kbuffer
+ * @kbuf: The kbuffer to load
+ * @subbuffer: The subbuffer to load into @kbuf.
+ *
+ * Load a new subbuffer (page) into @kbuf. This will reset all
+ * the pointers and update the @kbuf timestamp. The next read will
+ * return the first event on @subbuffer.
+ *
+ * Returns 0 on succes, -1 otherwise.
+ */
+int kbuffer_load_subbuffer(struct kbuffer *kbuf, void *subbuffer)
+{
+ unsigned long long flags;
+ void *ptr = subbuffer;
+
+ if (!kbuf || !subbuffer)
+ return -1;
+
+ kbuf->subbuffer = subbuffer;
+
+ kbuf->timestamp = read_8(kbuf, ptr);
+ ptr += 8;
+
+ kbuf->curr = 0;
+
+ if (kbuf->flags & KBUFFER_FL_LONG_8)
+ kbuf->start = 16;
+ else
+ kbuf->start = 12;
+
+ kbuf->data = subbuffer + kbuf->start;
+
+ flags = read_long(kbuf, ptr);
+ kbuf->size = (unsigned int)flags & COMMIT_MASK;
+
+ if (flags & MISSING_EVENTS) {
+ if (flags & MISSING_STORED) {
+ ptr = kbuf->data + kbuf->size;
+ kbuf->lost_events = read_long(kbuf, ptr);
+ } else
+ kbuf->lost_events = -1;
+ } else
+ kbuf->lost_events = 0;
+
+ kbuf->index = 0;
+ kbuf->next = 0;
+
+ next_event(kbuf);
+
+ /* save the first record from the page */
+ kbuf->first = kbuf->curr;
+
+ return 0;
+}
+
+/**
+ * kbuffer_subbuf_timestamp - read the timestamp from a sub buffer
+ * @kbuf: The kbuffer to load
+ * @subbuf: The subbuffer to read from.
+ *
+ * Return the timestamp from a subbuffer.
+ */
+unsigned long long kbuffer_subbuf_timestamp(struct kbuffer *kbuf, void *subbuf)
+{
+ return kbuf->read_8(subbuf);
+}
+
+/**
+ * kbuffer_ptr_delta - read the delta field from a record
+ * @kbuf: The kbuffer to load
+ * @ptr: The record in the buffe.
+ *
+ * Return the timestamp delta from a record
+ */
+unsigned int kbuffer_ptr_delta(struct kbuffer *kbuf, void *ptr)
+{
+ unsigned int type_len_ts;
+
+ type_len_ts = read_4(kbuf, ptr);
+ return ts4host(kbuf, type_len_ts);
+}
+
+
+/**
+ * kbuffer_read_event - read the next event in the kbuffer subbuffer
+ * @kbuf: The kbuffer to read from
+ * @ts: The address to store the timestamp of the event (may be NULL to ignore)
+ *
+ * Returns a pointer to the data part of the current event.
+ * NULL if no event is left on the subbuffer.
+ */
+void *kbuffer_read_event(struct kbuffer *kbuf, unsigned long long *ts)
+{
+ if (!kbuf || !kbuf->subbuffer)
+ return NULL;
+
+ if (kbuf->curr >= kbuf->size)
+ return NULL;
+
+ if (ts)
+ *ts = kbuf->timestamp;
+ return kbuf->data + kbuf->index;
+}
+
+/**
+ * kbuffer_timestamp - Return the timestamp of the current event
+ * @kbuf: The kbuffer to read from
+ *
+ * Returns the timestamp of the current (next) event.
+ */
+unsigned long long kbuffer_timestamp(struct kbuffer *kbuf)
+{
+ return kbuf->timestamp;
+}
+
+/**
+ * kbuffer_read_at_offset - read the event that is at offset
+ * @kbuf: The kbuffer to read from
+ * @offset: The offset into the subbuffer
+ * @ts: The address to store the timestamp of the event (may be NULL to ignore)
+ *
+ * The @offset must be an index from the @kbuf subbuffer beginning.
+ * If @offset is bigger than the stored subbuffer, NULL will be returned.
+ *
+ * Returns the data of the record that is at @offset. Note, @offset does
+ * not need to be the start of the record, the offset just needs to be
+ * in the record (or beginning of it).
+ *
+ * Note, the kbuf timestamp and pointers are updated to the
+ * returned record. That is, kbuffer_read_event() will return the same
+ * data and timestamp, and kbuffer_next_event() will increment from
+ * this record.
+ */
+void *kbuffer_read_at_offset(struct kbuffer *kbuf, int offset,
+ unsigned long long *ts)
+{
+ void *data;
+
+ if (offset < kbuf->start)
+ offset = 0;
+ else
+ offset -= kbuf->start;
+
+ /* Reset the buffer */
+ kbuffer_load_subbuffer(kbuf, kbuf->subbuffer);
+ data = kbuffer_read_event(kbuf, ts);
+
+ while (kbuf->curr < offset) {
+ data = kbuffer_next_event(kbuf, ts);
+ if (!data)
+ break;
+ }
+
+ return data;
+}
+
+/**
+ * kbuffer_subbuffer_size - the size of the loaded subbuffer
+ * @kbuf: The kbuffer to read from
+ *
+ * Returns the size of the subbuffer. Note, this size is
+ * where the last event resides. The stored subbuffer may actually be
+ * bigger due to padding and such.
+ */
+int kbuffer_subbuffer_size(struct kbuffer *kbuf)
+{
+ return kbuf->size;
+}
+
+/**
+ * kbuffer_curr_index - Return the index of the record
+ * @kbuf: The kbuffer to read from
+ *
+ * Returns the index from the start of the data part of
+ * the subbuffer to the current location. Note this is not
+ * from the start of the subbuffer. An index of zero will
+ * point to the first record. Use kbuffer_curr_offset() for
+ * the actually offset (that can be used by kbuffer_read_at_offset())
+ */
+int kbuffer_curr_index(struct kbuffer *kbuf)
+{
+ return kbuf->curr;
+}
+
+/**
+ * kbuffer_curr_offset - Return the offset of the record
+ * @kbuf: The kbuffer to read from
+ *
+ * Returns the offset from the start of the subbuffer to the
+ * current location.
+ */
+int kbuffer_curr_offset(struct kbuffer *kbuf)
+{
+ return kbuf->curr + kbuf->start;
+}
+
+/**
+ * kbuffer_event_size - return the size of the event data
+ * @kbuf: The kbuffer to read
+ *
+ * Returns the size of the event data (the payload not counting
+ * the meta data of the record) of the current event.
+ */
+int kbuffer_event_size(struct kbuffer *kbuf)
+{
+ return kbuf->next - kbuf->index;
+}
+
+/**
+ * kbuffer_curr_size - return the size of the entire record
+ * @kbuf: The kbuffer to read
+ *
+ * Returns the size of the entire record (meta data and payload)
+ * of the current event.
+ */
+int kbuffer_curr_size(struct kbuffer *kbuf)
+{
+ return kbuf->next - kbuf->curr;
+}
+
+/**
+ * kbuffer_missed_events - return the # of missed events from last event.
+ * @kbuf: The kbuffer to read from
+ *
+ * Returns the # of missed events (if recorded) before the current
+ * event. Note, only events on the beginning of a subbuffer can
+ * have missed events, all other events within the buffer will be
+ * zero.
+ */
+int kbuffer_missed_events(struct kbuffer *kbuf)
+{
+ /* Only the first event can have missed events */
+ if (kbuf->curr)
+ return 0;
+
+ return kbuf->lost_events;
+}
+
+/**
+ * kbuffer_set_old_forma - set the kbuffer to use the old format parsing
+ * @kbuf: The kbuffer to set
+ *
+ * This is obsolete (or should be). The first kernels to use the
+ * new ring buffer had a slightly different ring buffer format
+ * (2.6.30 and earlier). It is still somewhat supported by kbuffer,
+ * but should not be counted on in the future.
+ */
+void kbuffer_set_old_format(struct kbuffer *kbuf)
+{
+ kbuf->flags |= KBUFFER_FL_OLD_FORMAT;
+
+ kbuf->next_event = __old_next_event;
+}
+
+/**
+ * kbuffer_start_of_data - return offset of where data starts on subbuffer
+ * @kbuf: The kbuffer
+ *
+ * Returns the location on the subbuffer where the data starts.
+ */
+int kbuffer_start_of_data(struct kbuffer *kbuf)
+{
+ return kbuf->first + kbuf->start;
+}
+
+/**
+ * kbuffer_raw_get - get raw buffer info
+ * @kbuf: The kbuffer
+ * @subbuf: Start of mapped subbuffer
+ * @info: Info descriptor to fill in
+ *
+ * For debugging. This can return internals of the ring buffer.
+ * Expects to have info->next set to what it will read.
+ * The type, length and timestamp delta will be filled in, and
+ * @info->next will be updated to the next element.
+ * The @subbuf is used to know if the info is passed the end of
+ * data and NULL will be returned if it is.
+ */
+struct kbuffer_raw_info *
+kbuffer_raw_get(struct kbuffer *kbuf, void *subbuf, struct kbuffer_raw_info *info)
+{
+ unsigned long long flags;
+ unsigned long long delta;
+ unsigned int type_len;
+ unsigned int size;
+ int start;
+ int length;
+ void *ptr = info->next;
+
+ if (!kbuf || !subbuf)
+ return NULL;
+
+ if (kbuf->flags & KBUFFER_FL_LONG_8)
+ start = 16;
+ else
+ start = 12;
+
+ flags = read_long(kbuf, subbuf + 8);
+ size = (unsigned int)flags & COMMIT_MASK;
+
+ if (ptr < subbuf || ptr >= subbuf + start + size)
+ return NULL;
+
+ type_len = translate_data(kbuf, ptr, &ptr, &delta, &length);
+
+ info->next = ptr + length;
+
+ info->type = type_len;
+ info->delta = delta;
+ info->length = length;
+
+ return info;
+}
diff --git a/src/parse-filter.c b/src/parse-filter.c
new file mode 100644
index 0000000..e448ee2
--- /dev/null
+++ b/src/parse-filter.c
@@ -0,0 +1,2283 @@
+// 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(struct tep_handle *tep, char *error_buf, const char *fmt, ...)
+{
+ unsigned long long index;
+ const char *input;
+ va_list ap;
+ int len;
+ int i;
+
+ input = get_input_buf(tep);
+ index = get_input_buf_ptr(tep);
+ 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(struct tep_handle *tep, char **tok)
+{
+ enum tep_event_type type;
+ char *token = NULL;
+
+ do {
+ free_token(token);
+ type = read_token(tep, &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(tep) == '~') {
+ /* 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(tep, &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(&reg, "^%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(&reg, "^%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(event->tep, 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(event->tep, 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(event->tep, 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_handle *tep, 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(tep, 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(tep, 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(tep, error_str,
+ "RegEx '%s' did not compute",
+ str);
+ return TEP_ERRNO__INVALID_REGEX;
+ }
+ break;
+ default:
+ show_error(tep, 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(tep, 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(tep, 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(tep, 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(tep, 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_handle *tep, 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(tep, 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(tep, 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(tep, 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_handle *tep, 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(tep, arg, arg->exp.left, error_str);
+ if (lval != FILTER_VAL_NORM)
+ return lval;
+ rval = test_arg(tep, 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(tep, arg, arg->num.left, error_str);
+ if (lval != FILTER_VAL_NORM)
+ return lval;
+ rval = test_arg(tep, 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(tep, 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(tep, arg, arg->op.right, error_str);
+ if (rval != FILTER_VAL_NORM)
+ return rval;
+
+ return reparent_op_arg(tep, 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(tep, arg, arg->op.right, error_str);
+ if (rval != FILTER_VAL_NORM)
+ return rval;
+
+ return reparent_op_arg(tep, parent, arg, arg->op.right,
+ error_str);
+
+ default:
+ return lval;
+ }
+ }
+
+ rval = test_arg(tep, 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(tep, 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(tep, parent, arg, arg->op.left,
+ error_str);
+ }
+
+ return rval;
+ default:
+ show_error(tep, 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_handle *tep, struct tep_filter_arg *arg,
+ struct tep_filter_arg **arg_collapsed, char *error_str)
+{
+ int ret;
+
+ ret = test_arg(tep, 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(tep, 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(event->tep, &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(event->tep, 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(event->tep, error_str, "Illegal token ','");
+ ret = TEP_ERRNO__ILLEGAL_TOKEN;
+ goto fail;
+ }
+
+ if (*token == '(') {
+ if (left_item) {
+ show_error(event->tep, error_str,
+ "Open paren can not come after item");
+ ret = TEP_ERRNO__INVALID_PAREN;
+ goto fail;
+ }
+ if (current_exp) {
+ show_error(event->tep, 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(event->tep, 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(event->tep, 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(event->tep, 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(event->tep, 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(event->tep, 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(event->tep,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(event->tep, 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(event->tep, error_str, "failed to allocate filter arg");
+ ret = TEP_ERRNO__MEM_ALLOC_FAILED;
+ goto fail;
+ fail_syntax:
+ show_error(event->tep, 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(event->tep, 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(filter->tep, "", 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;
+}
+
diff --git a/src/parse-utils.c b/src/parse-utils.c
new file mode 100644
index 0000000..9c38e1e
--- /dev/null
+++ b/src/parse-utils.c
@@ -0,0 +1,151 @@
+// 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 "event-utils.h"
+#include "event-parse.h"
+#include "kbuffer.h"
+
+#define __weak __attribute__((weak))
+
+static int log_level = TEP_LOG_CRITICAL;
+
+/**
+ * tep_set_loglevel - set log level of the library
+ * @level: desired level of the library messages
+ */
+void tep_set_loglevel(enum tep_loglevel level)
+{
+ log_level = level;
+}
+
+/**
+ * tep_vprint - print library log messages
+ * @name: name of the library.
+ * @level: severity of the log message. This parameter is not used in this implementation, but as
+ * the function is weak and can be overridden, having the log level could be useful
+ * for other implementations.
+ * @print_err: whether to print the errno, if non zero.
+ * @fmt: printf format string of the message.
+ * @ap: list of printf parameters.
+ *
+ * This function is used to print all messages from traceevent, tracefs and trace-cmd libraries.
+ * It is defined as weak, so the application that uses those libraries can override it in order
+ * to implement its own logic for printing library logs.
+ *
+ * Return the value of errno at the function enter.
+ */
+int __weak tep_vprint(const char *name, enum tep_loglevel level,
+ bool print_err, const char *fmt, va_list ap)
+{
+ return __tep_vprint(name, level, print_err, fmt, ap);
+}
+
+/**
+ * __tep_vprint - print library log messages
+ * @name: name of the library.
+ * @level: severity of the log message. This parameter is not used in this implementation, but as
+ * the function is weak and can be overridden, having the log level could be useful
+ * for other implementations.
+ * @print_err: whether to print the errno, if non zero.
+ * @fmt: printf format string of the message.
+ * @ap: list of printf parameters.
+ *
+ * This function is used to print all messages from traceevent, tracefs and trace-cmd libraries.
+ * It is defined as weak, so the application that uses those libraries can override it in order
+ * to implement its own logic for printing library logs.
+ *
+ * Return the value of errno at the function enter.
+ */
+int __tep_vprint(const char *name, enum tep_loglevel level,
+ bool print_err, const char *fmt, va_list ap)
+{
+ int ret = errno;
+ FILE *fp = stdout;
+
+ if (level <= TEP_LOG_WARNING) {
+ fp = stderr;
+ if (errno && print_err) {
+ perror(name);
+ fprintf(stderr, " ");
+ }
+ }
+ vfprintf(fp, fmt, ap);
+ fprintf(fp, "\n");
+
+ return ret;
+}
+
+void tep_warning(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (log_level < TEP_LOG_WARNING)
+ return;
+
+ va_start(ap, fmt);
+ tep_vprint("libtraceevent", TEP_LOG_WARNING, true, fmt, ap);
+ va_end(ap);
+}
+
+
+void tep_info(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (log_level < TEP_LOG_INFO)
+ return;
+
+ va_start(ap, fmt);
+ tep_vprint("libtraceevent", TEP_LOG_INFO, false, fmt, ap);
+ va_end(ap);
+}
+
+/* The below is for backward compatibility */
+int __weak tep_vwarning(const char *name, const char *fmt, va_list ap)
+{
+ return tep_vprint(name, TEP_LOG_WARNING, true, fmt, ap);
+}
+
+void pr_stat(const char *fmt, ...) __attribute__((weak, alias("tep_info")));
+void __pr_stat(const char *fmt, ...) __attribute__((weak, alias("tep_info")));
+
+void __weak __vpr_stat(const char *fmt, va_list ap)
+{
+ tep_vprint("libtraceevent", TEP_LOG_INFO, false, fmt, ap);
+}
+
+void vpr_stat(const char *fmt, va_list ap) __attribute__((weak, alias("__vpr_stat")));
+
+/**
+ * tep_kbuffer - return an allocated kbuffer that can be used for the tep handle
+ * @tep: the handle that will work with the kbuffer descriptor
+ *
+ * Allocates and returns a new kbuffer.
+ * The return must be freed by kbuffer_free();
+ */
+struct kbuffer *tep_kbuffer(struct tep_handle *tep)
+{
+ enum kbuffer_endian endian;
+ int long_size;
+
+ long_size = tep_get_long_size(tep);
+ if (long_size == 8)
+ long_size = KBUFFER_LSIZE_8;
+ else
+ long_size = KBUFFER_LSIZE_4;
+
+ if (tep_is_file_bigendian(tep))
+ endian = KBUFFER_ENDIAN_BIG;
+ else
+ endian = KBUFFER_ENDIAN_LITTLE;
+
+ return kbuffer_alloc(long_size, endian);
+}
diff --git a/src/tep_strerror.c b/src/tep_strerror.c
new file mode 100644
index 0000000..4ac2644
--- /dev/null
+++ b/src/tep_strerror.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: LGPL-2.1
+#undef _GNU_SOURCE
+#include <string.h>
+#include <stdio.h>
+
+#include "event-parse.h"
+
+#undef _PE
+#define _PE(code, str) str
+static const char * const tep_error_str[] = {
+ TEP_ERRORS
+};
+#undef _PE
+
+/*
+ * The tools so far have been using the strerror_r() GNU variant, that returns
+ * a string, be it the buffer passed or something else.
+ *
+ * But that, besides being tricky in cases where we expect that the function
+ * using strerror_r() returns the error formatted in a provided buffer (we have
+ * to check if it returned something else and copy that instead), breaks the
+ * build on systems not using glibc, like Alpine Linux, where musl libc is
+ * used.
+ *
+ * So, introduce yet another wrapper, str_error_r(), that has the GNU
+ * interface, but uses the portable XSI variant of strerror_r(), so that users
+ * rest asured that the provided buffer is used and it is what is returned.
+ */
+int tep_strerror(struct tep_handle *tep __maybe_unused,
+ enum tep_errno errnum, char *buf, size_t buflen)
+{
+ const char *msg;
+ int idx;
+
+ if (!buflen)
+ return 0;
+
+ if (errnum >= 0) {
+ int err = strerror_r(errnum, buf, buflen);
+ buf[buflen - 1] = 0;
+ return err;
+ }
+
+ if (errnum <= __TEP_ERRNO__START ||
+ errnum >= __TEP_ERRNO__END)
+ return -1;
+
+ idx = errnum - __TEP_ERRNO__START - 1;
+ msg = tep_error_str[idx];
+ snprintf(buf, buflen, "%s", msg);
+
+ return 0;
+}
diff --git a/src/trace-seq.c b/src/trace-seq.c
new file mode 100644
index 0000000..8d5ecd2
--- /dev/null
+++ b/src/trace-seq.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * Copyright (C) 2009 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ */
+#include "trace-seq.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <asm/bug.h>
+#include "event-parse.h"
+#include "event-utils.h"
+
+/*
+ * The TRACE_SEQ_POISON is to catch the use of using
+ * a trace_seq structure after it was destroyed.
+ */
+#define TRACE_SEQ_POISON ((void *)0xdeadbeef)
+#define TRACE_SEQ_CHECK(s) \
+do { \
+ if (WARN_ONCE((s)->buffer == TRACE_SEQ_POISON, \
+ "Usage of trace_seq after it was destroyed")) \
+ (s)->state = TRACE_SEQ__BUFFER_POISONED; \
+} while (0)
+
+#define TRACE_SEQ_CHECK_RET_N(s, n) \
+do { \
+ TRACE_SEQ_CHECK(s); \
+ if ((s)->state != TRACE_SEQ__GOOD) \
+ return n; \
+} while (0)
+
+#define TRACE_SEQ_CHECK_RET(s) TRACE_SEQ_CHECK_RET_N(s, )
+#define TRACE_SEQ_CHECK_RET0(s) TRACE_SEQ_CHECK_RET_N(s, 0)
+
+/**
+ * trace_seq_init - initialize the trace_seq structure
+ * @s: a pointer to the trace_seq structure to initialize
+ */
+void trace_seq_init(struct trace_seq *s)
+{
+ s->len = 0;
+ s->readpos = 0;
+ s->buffer_size = TRACE_SEQ_BUF_SIZE;
+ s->buffer = malloc(s->buffer_size);
+ if (s->buffer != NULL)
+ s->state = TRACE_SEQ__GOOD;
+ else
+ s->state = TRACE_SEQ__MEM_ALLOC_FAILED;
+}
+
+/**
+ * trace_seq_reset - re-initialize the trace_seq structure
+ * @s: a pointer to the trace_seq structure to reset
+ */
+void trace_seq_reset(struct trace_seq *s)
+{
+ if (!s)
+ return;
+ TRACE_SEQ_CHECK(s);
+ s->len = 0;
+ s->readpos = 0;
+}
+
+/**
+ * trace_seq_destroy - free up memory of a trace_seq
+ * @s: a pointer to the trace_seq to free the buffer
+ *
+ * Only frees the buffer, not the trace_seq struct itself.
+ */
+void trace_seq_destroy(struct trace_seq *s)
+{
+ if (!s)
+ return;
+ TRACE_SEQ_CHECK_RET(s);
+ free(s->buffer);
+ s->buffer = TRACE_SEQ_POISON;
+}
+
+static void expand_buffer(struct trace_seq *s)
+{
+ char *buf;
+
+ buf = realloc(s->buffer, s->buffer_size + TRACE_SEQ_BUF_SIZE);
+ if (WARN_ONCE(!buf, "Can't allocate trace_seq buffer memory")) {
+ s->state = TRACE_SEQ__MEM_ALLOC_FAILED;
+ return;
+ }
+
+ s->buffer = buf;
+ s->buffer_size += TRACE_SEQ_BUF_SIZE;
+}
+
+/**
+ * trace_seq_printf - sequence printing of trace information
+ * @s: trace sequence descriptor
+ * @fmt: printf format string
+ *
+ * It returns 0 if the trace oversizes the buffer's free
+ * space, the number of characters printed, or a negative
+ * value in case of an error.
+ *
+ * The tracer may use either sequence operations or its own
+ * copy to user routines. To simplify formating of a trace
+ * trace_seq_printf is used to store strings into a special
+ * buffer (@s). Then the output may be either used by
+ * the sequencer or pulled into another buffer.
+ */
+int
+trace_seq_printf(struct trace_seq *s, const char *fmt, ...)
+{
+ va_list ap;
+ int len;
+ int ret;
+
+ try_again:
+ TRACE_SEQ_CHECK_RET0(s);
+
+ len = (s->buffer_size - 1) - s->len;
+
+ va_start(ap, fmt);
+ ret = vsnprintf(s->buffer + s->len, len, fmt, ap);
+ va_end(ap);
+
+ if (ret >= len) {
+ expand_buffer(s);
+ goto try_again;
+ }
+
+ if (ret > 0)
+ s->len += ret;
+
+ return ret;
+}
+
+/**
+ * trace_seq_vprintf - sequence printing of trace information
+ * @s: trace sequence descriptor
+ * @fmt: printf format string
+ *
+ * It returns 0 if the trace oversizes the buffer's free
+ * space, the number of characters printed, or a negative
+ * value in case of an error.
+ * *
+ * The tracer may use either sequence operations or its own
+ * copy to user routines. To simplify formating of a trace
+ * trace_seq_printf is used to store strings into a special
+ * buffer (@s). Then the output may be either used by
+ * the sequencer or pulled into another buffer.
+ */
+int
+trace_seq_vprintf(struct trace_seq *s, const char *fmt, va_list args)
+{
+ int len;
+ int ret;
+
+ try_again:
+ TRACE_SEQ_CHECK_RET0(s);
+
+ len = (s->buffer_size - 1) - s->len;
+
+ ret = vsnprintf(s->buffer + s->len, len, fmt, args);
+
+ if (ret >= len) {
+ expand_buffer(s);
+ goto try_again;
+ }
+
+ if (ret > 0)
+ s->len += ret;
+
+ return ret;
+}
+
+/**
+ * trace_seq_puts - trace sequence printing of simple string
+ * @s: trace sequence descriptor
+ * @str: simple string to record
+ *
+ * The tracer may use either the sequence operations or its own
+ * copy to user routines. This function records a simple string
+ * into a special buffer (@s) for later retrieval by a sequencer
+ * or other mechanism.
+ */
+int trace_seq_puts(struct trace_seq *s, const char *str)
+{
+ int len;
+
+ TRACE_SEQ_CHECK_RET0(s);
+
+ len = strlen(str);
+
+ while (len > ((s->buffer_size - 1) - s->len))
+ expand_buffer(s);
+
+ TRACE_SEQ_CHECK_RET0(s);
+
+ memcpy(s->buffer + s->len, str, len);
+ s->len += len;
+
+ return len;
+}
+
+int trace_seq_putc(struct trace_seq *s, unsigned char c)
+{
+ TRACE_SEQ_CHECK_RET0(s);
+
+ while (s->len >= (s->buffer_size - 1))
+ expand_buffer(s);
+
+ TRACE_SEQ_CHECK_RET0(s);
+
+ s->buffer[s->len++] = c;
+
+ return 1;
+}
+
+void trace_seq_terminate(struct trace_seq *s)
+{
+ TRACE_SEQ_CHECK_RET(s);
+
+ /* There's always one character left on the buffer */
+ s->buffer[s->len] = 0;
+}
+
+int trace_seq_do_fprintf(struct trace_seq *s, FILE *fp)
+{
+ TRACE_SEQ_CHECK(s);
+
+ switch (s->state) {
+ case TRACE_SEQ__GOOD:
+ return fprintf(fp, "%.*s", s->len, s->buffer);
+ case TRACE_SEQ__BUFFER_POISONED:
+ fprintf(fp, "%s\n", "Usage of trace_seq after it was destroyed");
+ break;
+ case TRACE_SEQ__MEM_ALLOC_FAILED:
+ fprintf(fp, "%s\n", "Can't allocate trace_seq buffer memory");
+ break;
+ }
+ return -1;
+}
+
+int trace_seq_do_printf(struct trace_seq *s)
+{
+ return trace_seq_do_fprintf(s, stdout);
+}