diff options
Diffstat (limited to 'src/event-parse.c')
-rw-r--r-- | src/event-parse.c | 8592 |
1 files changed, 8592 insertions, 0 deletions
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); +} |