diff options
Diffstat (limited to 'lib/trace-cmd/trace-input.c')
-rw-r--r-- | lib/trace-cmd/trace-input.c | 5886 |
1 files changed, 5886 insertions, 0 deletions
diff --git a/lib/trace-cmd/trace-input.c b/lib/trace-cmd/trace-input.c new file mode 100644 index 00000000..8ffdf04b --- /dev/null +++ b/lib/trace-cmd/trace-input.c @@ -0,0 +1,5886 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ +#define _LARGEFILE64_SOURCE +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <regex.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include <linux/time64.h> + +#include "trace-write-local.h" +#include "trace-cmd-local.h" +#include "trace-local.h" +#include "kbuffer.h" +#include "list.h" + +#define _STRINGIFY(x) #x +#define STRINGIFY(x) _STRINGIFY(x) + +#define MISSING_EVENTS (1 << 31) +#define MISSING_STORED (1 << 30) + +#define COMMIT_MASK ((1 << 27) - 1) + +/* force uncompressing in memory */ +#define INMEMORY_DECOMPRESS + +/* for debugging read instead of mmap */ +static int force_read = 0; + +struct page_map { + struct list_head list; + off64_t offset; + off64_t size; + void *map; + int ref_count; +}; + +struct page { + struct list_head list; + off64_t offset; + struct tracecmd_input *handle; + struct page_map *page_map; + void *map; + int ref_count; + int cpu; + long long lost_events; +#if DEBUG_RECORD + struct tep_record *records; +#endif +}; + +struct zchunk_cache { + struct list_head list; + struct tracecmd_compress_chunk *chunk; + void *map; + int ref; +}; + +struct cpu_zdata { + /* uncompressed cpu data */ + int fd; +#ifdef __ANDROID__ + char file[37]; /* strlen(COMPR_TEMP_FILE) */ +#else /* !__ANDROID__ */ + char file[26]; /* strlen(COMPR_TEMP_FILE) */ +#endif /* __ANDROID__ */ + + unsigned int count; + unsigned int last_chunk; + struct list_head cache; + struct tracecmd_compress_chunk *chunks; +}; + +#ifdef __ANDROID__ +#define COMPR_TEMP_FILE "/data/local/tmp/trace_cpu_dataXXXXXX" +#else /* !__ANDROID__ */ +#define COMPR_TEMP_FILE "/tmp/trace_cpu_dataXXXXXX" +#endif /* __ANDROID__ */ + +struct cpu_data { + /* the first two never change */ + unsigned long long file_offset; + unsigned long long file_size; + unsigned long long offset; + unsigned long long size; + unsigned long long timestamp; + unsigned long long first_ts; + struct list_head page_maps; + struct page_map *page_map; + struct page **pages; + struct tep_record *next; + struct page *page; + struct kbuffer *kbuf; + int nr_pages; + int page_cnt; + int cpu; + int pipe_fd; + struct cpu_zdata compress; +}; + +struct cpu_file_data { + int cpu; + unsigned long long offset; + unsigned long long size; +}; + +struct input_buffer_instance { + char *name; + size_t offset; + char *clock; + bool latency; + int page_size; + int cpus; + struct cpu_file_data *cpu_data; +}; + +struct ts_offset_sample { + long long time; + long long offset; + long long scaling; + long long fraction; +}; + +struct guest_trace_info { + struct guest_trace_info *next; + char *name; + unsigned long long trace_id; + int vcpu_count; + int *cpu_pid; +}; + +struct timesync_offsets { + int ts_samples_count; + struct ts_offset_sample *ts_samples; +}; + +struct host_trace_info { + unsigned long long peer_trace_id; + unsigned int flags; + bool sync_enable; + int ts_samples_count; + struct ts_offset_sample *ts_samples; + int cpu_count; + struct timesync_offsets *ts_offsets; +}; + +struct tsc2nsec { + int mult; + int shift; + unsigned long long offset; +}; + +struct file_section { + unsigned long long section_offset; + unsigned long long data_offset; + int id; + int flags; + struct file_section *next; +}; + +struct tracecmd_input { + struct tep_handle *pevent; + struct tep_plugin_list *plugin_list; + struct tracecmd_input *parent; + unsigned long file_state; + unsigned long long trace_id; + unsigned long long next_offset; + unsigned long flags; + int fd; + int long_size; + int page_size; + int page_map_size; + int max_cpu; + int cpus; + int ref; + int nr_buffers; /* buffer instances */ + bool use_trace_clock; + bool read_page; + bool use_pipe; + bool read_zpage; /* uncompress pages in memory, do not use tmp files */ + bool cpu_compressed; + int file_version; + unsigned int cpustats_size; + struct cpu_zdata latz; + struct cpu_data *cpu_data; + long long ts_offset; + struct tsc2nsec tsc_calc; + + unsigned int strings_size; /* size of the metadata strings */ + char *strings; /* metadata strings */ + + bool read_compress; + struct tracecmd_compression *compress; + + struct host_trace_info host; + double ts2secs; + char * cpustats; + char * uname; + char * version; + char * trace_clock; + struct input_buffer_instance top_buffer; + struct input_buffer_instance *buffers; + int parsing_failures; + struct guest_trace_info *guest; + + struct tracecmd_ftrace finfo; + + struct hook_list *hooks; + struct pid_addr_maps *pid_maps; + /* file information */ + struct file_section *sections; + bool options_init; + unsigned long long options_start; + unsigned long long options_last_offset; + size_t total_file_size; + + /* For custom profilers. */ + tracecmd_show_data_func show_data_func; +}; + +__thread struct tracecmd_input *tracecmd_curr_thread_handle; + +#define CHECK_READ_STATE(H, S) ((H)->file_version < FILE_VERSION_SECTIONS && (H)->file_state >= (S)) +#define HAS_SECTIONS(H) ((H)->flags & TRACECMD_FL_SECTIONED) +#define HAS_COMPRESSION(H) ((H)->flags & TRACECMD_FL_COMPRESSION) + +static int read_options_type(struct tracecmd_input *handle); + +void tracecmd_set_flag(struct tracecmd_input *handle, int flag) +{ + handle->flags |= flag; +} + +void tracecmd_clear_flag(struct tracecmd_input *handle, int flag) +{ + handle->flags &= ~flag; +} + +unsigned long tracecmd_get_flags(struct tracecmd_input *handle) +{ + return handle->flags; +} + +enum tracecmd_file_states tracecmd_get_file_state(struct tracecmd_input *handle) +{ + return handle->file_state; +} + +#if DEBUG_RECORD +static void remove_record(struct page *page, struct tep_record *record) +{ + if (record->prev) + record->prev->next = record->next; + else + page->records = record->next; + if (record->next) + record->next->prev = record->prev; +} +static void add_record(struct page *page, struct tep_record *record) +{ + if (page->records) + page->records->prev = record; + record->next = page->records; + record->prev = NULL; + page->records = record; +} +static const char *show_records(struct page **pages, int nr_pages) +{ + static char buf[BUFSIZ + 1]; + struct tep_record *record; + struct page *page; + int len; + int i; + + memset(buf, 0, sizeof(buf)); + len = 0; + for (i = 0; i < nr_pages; i++) { + page = pages[i]; + if (!page) + continue; + for (record = page->records; record; record = record->next) { + int n; + n = snprintf(buf+len, BUFSIZ - len, " 0x%lx", record->alloc_addr); + len += n; + if (len >= BUFSIZ) + break; + } + } + return buf; +} +#else +static inline void remove_record(struct page *page, struct tep_record *record) {} +static inline void add_record(struct page *page, struct tep_record *record) {} +static const char *show_records(struct page **pages, int nr_pages) +{ + return ""; +} +#endif + +static int init_cpu(struct tracecmd_input *handle, int cpu); + +static ssize_t do_read_fd(int fd, void *data, size_t size) +{ + ssize_t tot = 0; + ssize_t r; + + do { + r = read(fd, data + tot, size - tot); + tot += r; + + if (!r) + break; + if (r < 0) + return r; + } while (tot != size); + + return tot; +} + +static inline int do_lseek(struct tracecmd_input *handle, int offset, int whence) +{ + if (handle->read_compress) + return tracecmd_compress_lseek(handle->compress, offset, whence); + else + return lseek(handle->fd, offset, whence); +} + +static inline ssize_t do_read(struct tracecmd_input *handle, void *data, size_t size) +{ + if (handle->read_compress) + return tracecmd_compress_buffer_read(handle->compress, data, size); + else + return do_read_fd(handle->fd, data, size); +} + +static ssize_t +do_read_check(struct tracecmd_input *handle, void *data, size_t size) +{ + ssize_t ret; + + ret = do_read(handle, data, size); + if (ret < 0) + return ret; + if (ret != size) + return -1; + + return 0; +} + +static char *read_string(struct tracecmd_input *handle) +{ + char buf[BUFSIZ]; + char *str = NULL; + size_t size = 0; + ssize_t i; + ssize_t r; + + for (;;) { + r = do_read(handle, buf, BUFSIZ); + if (r <= 0) + goto fail; + + for (i = 0; i < r; i++) { + if (!buf[i]) + break; + } + if (i < r) + break; + + if (str) { + size += BUFSIZ; + str = realloc(str, size); + if (!str) + return NULL; + memcpy(str + (size - BUFSIZ), buf, BUFSIZ); + } else { + size = BUFSIZ; + str = malloc(size); + if (!str) + return NULL; + memcpy(str, buf, size); + } + } + + /* move the file descriptor to the end of the string */ + r = do_lseek(handle, -(r - (i+1)), SEEK_CUR); + if (r < 0) + goto fail; + + if (str) { + size += i + 1; + str = realloc(str, size); + if (!str) + return NULL; + memcpy(str + (size - i), buf, i); + str[size] = 0; + } else { + size = i + 1; + str = malloc(size); + if (!str) + return NULL; + memcpy(str, buf, i); + str[i] = 0; + } + + return str; + + fail: + if (str) + free(str); + return NULL; +} + +static int read2(struct tracecmd_input *handle, unsigned short *size) +{ + struct tep_handle *pevent = handle->pevent; + unsigned short data; + + if (do_read_check(handle, &data, 2)) + return -1; + + *size = tep_read_number(pevent, &data, 2); + return 0; +} + +static int read4(struct tracecmd_input *handle, unsigned int *size) +{ + struct tep_handle *pevent = handle->pevent; + unsigned int data; + + if (do_read_check(handle, &data, 4)) + return -1; + + *size = tep_read_number(pevent, &data, 4); + return 0; +} + +static int read8(struct tracecmd_input *handle, unsigned long long *size) +{ + struct tep_handle *pevent = handle->pevent; + unsigned long long data; + + if (do_read_check(handle, &data, 8)) + return -1; + + *size = tep_read_number(pevent, &data, 8); + return 0; +} + +__hidden void in_uncompress_reset(struct tracecmd_input *handle) +{ + if (handle->compress) { + handle->read_compress = false; + tracecmd_compress_reset(handle->compress); + } +} + +__hidden int in_uncompress_block(struct tracecmd_input *handle) +{ + int ret = 0; + + if (handle->compress) { + ret = tracecmd_uncompress_block(handle->compress); + if (!ret) + handle->read_compress = true; + } + return ret; +} + +static struct file_section *section_get(struct tracecmd_input *handle, int id) +{ + struct file_section *sec; + + for (sec = handle->sections; sec; sec = sec->next) { + if (sec->id == id) + return sec; + } + + return NULL; +} + +static struct file_section *section_open(struct tracecmd_input *handle, int id) +{ + struct file_section *sec = section_get(handle, id); + + if (!sec) + return NULL; + + if (lseek64(handle->fd, sec->data_offset, SEEK_SET) == (off64_t)-1) + return NULL; + + if ((sec->flags & TRACECMD_SEC_FL_COMPRESS) && in_uncompress_block(handle)) + return NULL; + + return sec; +} + +static void section_close(struct tracecmd_input *handle, struct file_section *sec) +{ + if (sec->flags & TRACECMD_SEC_FL_COMPRESS) + in_uncompress_reset(handle); +} + +static int section_add_or_update(struct tracecmd_input *handle, int id, int flags, + unsigned long long section_offset, + unsigned long long data_offset) +{ + struct file_section *sec = section_get(handle, id); + + if (!sec) { + sec = calloc(1, sizeof(struct file_section)); + if (!sec) + return -1; + sec->next = handle->sections; + handle->sections = sec; + sec->id = id; + } + + if (section_offset) + sec->section_offset = section_offset; + if (data_offset) + sec->data_offset = data_offset; + if (flags >= 0) + sec->flags = flags; + + return 0; +} + +static int read_header_files(struct tracecmd_input *handle) +{ + struct tep_handle *pevent = handle->pevent; + unsigned long long size; + char *header; + char buf[BUFSIZ]; + + if (CHECK_READ_STATE(handle, TRACECMD_FILE_HEADERS)) + return 0; + + if (!HAS_SECTIONS(handle)) + section_add_or_update(handle, TRACECMD_OPTION_HEADER_INFO, 0, 0, + lseek64(handle->fd, 0, SEEK_CUR)); + + if (do_read_check(handle, buf, 12)) + return -1; + + if (memcmp(buf, "header_page", 12) != 0) + return -1; + + if (read8(handle, &size) < 0) + return -1; + + header = malloc(size); + if (!header) + return -1; + + if (do_read_check(handle, header, size)) + goto failed_read; + + tep_parse_header_page(pevent, header, size, handle->long_size); + free(header); + + /* + * The size field in the page is of type long, + * use that instead, since it represents the kernel. + */ + handle->long_size = tep_get_header_page_size(pevent); + + if (do_read_check(handle, buf, 13)) + return -1; + + if (memcmp(buf, "header_event", 13) != 0) + return -1; + + if (read8(handle, &size) < 0) + return -1; + + header = malloc(size); + if (!header) + return -1; + + if (do_read_check(handle, header, size)) + goto failed_read; + + free(header); + + handle->file_state = TRACECMD_FILE_HEADERS; + + return 0; + + failed_read: + free(header); + return -1; +} + +static int regex_event_buf(const char *file, int size, regex_t *epreg) +{ + char *buf; + char *line; + int ret; + + buf = malloc(size + 1); + if (!buf) { + tracecmd_warning("Insufficient memory"); + return 0; + } + + strncpy(buf, file, size); + buf[size] = 0; + + /* get the name from the first line */ + line = strtok(buf, "\n"); + if (!line) { + tracecmd_warning("No newline found in '%s'", buf); + return 0; + } + /* skip name if it is there */ + if (strncmp(line, "name: ", 6) == 0) + line += 6; + + ret = regexec(epreg, line, 0, NULL, 0) == 0; + + free(buf); + + return ret; +} + +static int read_ftrace_file(struct tracecmd_input *handle, + unsigned long long size, + int print, regex_t *epreg) +{ + struct tep_handle *pevent = handle->pevent; + char *buf; + + buf = malloc(size); + if (!buf) + return -1; + if (do_read_check(handle, buf, size)) { + free(buf); + return -1; + } + + if (epreg) { + if (print || regex_event_buf(buf, size, epreg)) + printf("%.*s\n", (int)size, buf); + } else { + if (tep_parse_event(pevent, buf, size, "ftrace")) + handle->parsing_failures++; + } + free(buf); + + return 0; +} + +static int read_event_file(struct tracecmd_input *handle, + char *system, unsigned long long size, + int print, int *sys_printed, + regex_t *epreg) +{ + struct tep_handle *pevent = handle->pevent; + char *buf; + + buf = malloc(size); + if (!buf) + return -1; + + if (do_read_check(handle, buf, size)) { + free(buf); + return -1; + } + + if (epreg) { + if (print || regex_event_buf(buf, size, epreg)) { + if (!*sys_printed) { + printf("\nsystem: %s\n", system); + *sys_printed = 1; + } + printf("%.*s\n", (int)size, buf); + } + } else { + if (tep_parse_event(pevent, buf, size, system)) + handle->parsing_failures++; + } + free(buf); + + return 0; +} + +static int make_preg_files(const char *regex, regex_t *system, + regex_t *event, int *unique) +{ + char *buf; + char *sstr; + char *estr; + int ret; + + /* unique is set if a colon is found */ + *unique = 0; + + /* split "system:event" into "system" and "event" */ + + buf = strdup(regex); + if (!buf) + return -ENOMEM; + + sstr = strtok(buf, ":"); + estr = strtok(NULL, ":"); + + /* If no colon is found, set event == system */ + if (!estr) + estr = sstr; + else + *unique = 1; + + ret = regcomp(system, sstr, REG_ICASE|REG_NOSUB); + if (ret) { + tracecmd_warning("Bad regular expression '%s'", sstr); + goto out; + } + + ret = regcomp(event, estr, REG_ICASE|REG_NOSUB); + if (ret) { + tracecmd_warning("Bad regular expression '%s'", estr); + goto out; + } + + out: + free(buf); + return ret; +} + +static int read_ftrace_files(struct tracecmd_input *handle, const char *regex) +{ + unsigned long long size; + regex_t spreg; + regex_t epreg; + regex_t *sreg = NULL; + regex_t *ereg = NULL; + unsigned int count, i; + int print_all = 0; + int unique; + int ret; + + if (CHECK_READ_STATE(handle, TRACECMD_FILE_FTRACE_EVENTS)) + return 0; + + if (!HAS_SECTIONS(handle)) + section_add_or_update(handle, TRACECMD_OPTION_FTRACE_EVENTS, 0, 0, + lseek64(handle->fd, 0, SEEK_CUR)); + + if (regex) { + sreg = &spreg; + ereg = &epreg; + ret = make_preg_files(regex, sreg, ereg, &unique); + if (ret) + return -1; + + if (regexec(sreg, "ftrace", 0, NULL, 0) == 0) { + /* + * If the system matches a regex that did + * not contain a colon, then print all events. + */ + if (!unique) + print_all = 1; + } else if (unique) { + /* + * The user specified a unique event that did + * not match the ftrace system. Don't print any + * events here. + */ + regfree(sreg); + regfree(ereg); + sreg = NULL; + ereg = NULL; + } + } + + ret = read4(handle, &count); + if (ret < 0) + goto out; + + for (i = 0; i < count; i++) { + ret = read8(handle, &size); + if (ret < 0) + goto out; + ret = read_ftrace_file(handle, size, print_all, ereg); + if (ret < 0) + goto out; + } + + handle->file_state = TRACECMD_FILE_FTRACE_EVENTS; + ret = 0; +out: + if (sreg) { + regfree(sreg); + regfree(ereg); + } + + return ret; +} + +static int read_event_files(struct tracecmd_input *handle, const char *regex) +{ + unsigned long long size; + char *system = NULL; + regex_t spreg; + regex_t epreg; + regex_t *sreg = NULL; + regex_t *ereg = NULL; + regex_t *reg; + unsigned int systems; + unsigned int count; + unsigned int i, x; + int print_all; + int sys_printed; + int unique; + int ret; + + if (CHECK_READ_STATE(handle, TRACECMD_FILE_ALL_EVENTS)) + return 0; + + if (!HAS_SECTIONS(handle)) + section_add_or_update(handle, TRACECMD_OPTION_EVENT_FORMATS, 0, 0, + lseek64(handle->fd, 0, SEEK_CUR)); + + if (regex) { + sreg = &spreg; + ereg = &epreg; + ret = make_preg_files(regex, sreg, ereg, &unique); + if (ret) + return -1; + } + + ret = read4(handle, &systems); + if (ret < 0) + goto out; + + for (i = 0; i < systems; i++) { + system = read_string(handle); + if (!system) { + ret = -1; + goto out; + } + + sys_printed = 0; + print_all = 0; + reg = ereg; + + if (sreg) { + if (regexec(sreg, system, 0, NULL, 0) == 0) { + /* + * If the user passed in a regex that + * did not contain a colon, then we can + * print all the events of this system. + */ + if (!unique) + print_all = 1; + } else if (unique) { + /* + * The user passed in a unique event that + * specified a specific system and event. + * Since this system doesn't match this + * event, then we don't print any events + * for this system. + */ + reg = NULL; + } + } + + ret = read4(handle, &count); + if (ret < 0) + goto out; + + for (x=0; x < count; x++) { + ret = read8(handle, &size); + if (ret < 0) + goto out; + + ret = read_event_file(handle, system, size, + print_all, &sys_printed, + reg); + if (ret < 0) + goto out; + } + free(system); + } + system = NULL; + + handle->file_state = TRACECMD_FILE_ALL_EVENTS; + ret = 0; + out: + if (sreg) { + regfree(sreg); + regfree(ereg); + } + + free(system); + return ret; +} + +static int read_proc_kallsyms(struct tracecmd_input *handle) +{ + struct tep_handle *tep = handle->pevent; + unsigned int size; + char *buf; + + if (CHECK_READ_STATE(handle, TRACECMD_FILE_KALLSYMS)) + return 0; + if (!HAS_SECTIONS(handle)) + section_add_or_update(handle, TRACECMD_OPTION_KALLSYMS, 0, 0, + lseek64(handle->fd, 0, SEEK_CUR)); + + if (read4(handle, &size) < 0) + return -1; + if (!size) { + handle->file_state = TRACECMD_FILE_KALLSYMS; + return 0; /* OK? */ + } + + buf = malloc(size+1); + if (!buf) + return -1; + if (do_read_check(handle, buf, size)){ + free(buf); + return -1; + } + buf[size] = 0; + + tep_parse_kallsyms(tep, buf); + + free(buf); + + handle->file_state = TRACECMD_FILE_KALLSYMS; + + return 0; +} + +static int read_ftrace_printk(struct tracecmd_input *handle) +{ + unsigned int size; + char *buf; + + if (CHECK_READ_STATE(handle, TRACECMD_FILE_PRINTK)) + return 0; + + if (!HAS_SECTIONS(handle)) + section_add_or_update(handle, TRACECMD_OPTION_PRINTK, 0, 0, + lseek64(handle->fd, 0, SEEK_CUR)); + + if (read4(handle, &size) < 0) + return -1; + if (!size) { + handle->file_state = TRACECMD_FILE_PRINTK; + return 0; /* OK? */ + } + + buf = malloc(size + 1); + if (!buf) + return -1; + if (do_read_check(handle, buf, size)) { + free(buf); + return -1; + } + + buf[size] = 0; + + tep_parse_printk_formats(handle->pevent, buf); + + free(buf); + + handle->file_state = TRACECMD_FILE_PRINTK; + + return 0; +} + +static int read_and_parse_cmdlines(struct tracecmd_input *handle); + +/** + * tracecmd_get_parsing_failures - get the count of parsing failures + * @handle: input handle for the trace.dat file + * + * This returns the count of failures while parsing the event files + */ +int tracecmd_get_parsing_failures(struct tracecmd_input *handle) +{ + if (handle) + return handle->parsing_failures; + return 0; +} + +static int read_cpus(struct tracecmd_input *handle) +{ + unsigned int cpus; + + if (CHECK_READ_STATE(handle, TRACECMD_FILE_CPU_COUNT)) + return 0; + + if (read4(handle, &cpus) < 0) + return -1; + + handle->cpus = cpus; + handle->max_cpu = cpus; + tep_set_cpus(handle->pevent, handle->cpus); + handle->file_state = TRACECMD_FILE_CPU_COUNT; + + return 0; +} + +static int read_headers_v6(struct tracecmd_input *handle, enum tracecmd_file_states state, + const char *regex) +{ + int ret; + + /* Set to read all if state is zero */ + if (!state) + state = TRACECMD_FILE_OPTIONS; + + if (state <= handle->file_state) + return 0; + + handle->parsing_failures = 0; + + ret = read_header_files(handle); + if (ret < 0) + return -1; + + if (state <= handle->file_state) + return 0; + + ret = read_ftrace_files(handle, NULL); + if (ret < 0) + return -1; + + if (state <= handle->file_state) + return 0; + + ret = read_event_files(handle, regex); + if (ret < 0) + return -1; + + if (state <= handle->file_state) + return 0; + + ret = read_proc_kallsyms(handle); + if (ret < 0) + return -1; + + if (state <= handle->file_state) + return 0; + + ret = read_ftrace_printk(handle); + if (ret < 0) + return -1; + + if (state <= handle->file_state) + return 0; + + if (read_and_parse_cmdlines(handle) < 0) + return -1; + + if (state <= handle->file_state) + return 0; + + if (read_cpus(handle) < 0) + return -1; + + if (state <= handle->file_state) + return 0; + + if (read_options_type(handle) < 0) + return -1; + + return 0; +} + +static int handle_options(struct tracecmd_input *handle); + +static const char *get_metadata_string(struct tracecmd_input *handle, int offset) +{ + if (!handle || !handle->strings || offset < 0 || handle->strings_size >= offset) + return NULL; + + return handle->strings + offset; +} + +static int read_section_header(struct tracecmd_input *handle, unsigned short *id, + unsigned short *flags, unsigned long long *size, const char **description) +{ + unsigned short fl; + unsigned short sec_id; + unsigned long long sz; + int desc; + + if (read2(handle, &sec_id)) + return -1; + if (read2(handle, &fl)) + return -1; + if (read4(handle, (unsigned int *)&desc)) + return -1; + if (read8(handle, &sz)) + return -1; + + if (id) + *id = sec_id; + if (flags) + *flags = fl; + if (size) + *size = sz; + if (description) + *description = get_metadata_string(handle, desc); + + return 0; +} + +static int handle_section(struct tracecmd_input *handle, struct file_section *section, + const char *regex) +{ + unsigned short id, flags; + unsigned long long size; + int ret; + + if (lseek64(handle->fd, section->section_offset, SEEK_SET) == (off_t)-1) + return -1; + if (read_section_header(handle, &id, &flags, &size, NULL)) + return -1; + section->flags = flags; + if (id != section->id) + return -1; + + section->data_offset = lseek64(handle->fd, 0, SEEK_CUR); + if ((section->flags & TRACECMD_SEC_FL_COMPRESS) && in_uncompress_block(handle)) + return -1; + + switch (section->id) { + case TRACECMD_OPTION_HEADER_INFO: + ret = read_header_files(handle); + break; + case TRACECMD_OPTION_FTRACE_EVENTS: + ret = read_ftrace_files(handle, NULL); + break; + case TRACECMD_OPTION_EVENT_FORMATS: + ret = read_event_files(handle, regex); + break; + case TRACECMD_OPTION_KALLSYMS: + ret = read_proc_kallsyms(handle); + break; + case TRACECMD_OPTION_PRINTK: + ret = read_ftrace_printk(handle); + break; + case TRACECMD_OPTION_CMDLINES: + ret = read_and_parse_cmdlines(handle); + break; + default: + ret = 0; + break; + } + + if (section->flags & TRACECMD_SEC_FL_COMPRESS) + in_uncompress_reset(handle); + + return ret; +} + +static int read_headers(struct tracecmd_input *handle, const char *regex) +{ + struct file_section *section; + + if (handle->options_init) + return 0; + + if (!handle->options_start) + return -1; + + if (lseek64(handle->fd, handle->options_start, SEEK_SET) == (off64_t)-1) { + tracecmd_warning("Filed to goto options offset %lld", handle->options_start); + return -1; + } + + if (handle_options(handle)) + return -1; + + section = handle->sections; + while (section) { + if (handle_section(handle, section, NULL)) + return -1; + section = section->next; + } + + handle->options_init = true; + return 0; +} + +/** + * tracecmd_read_headers - read the header information from trace.dat + * @handle: input handle for the trace.dat file + * @state: The state to read up to or zero to read up to options. + * + * This reads the trace.dat file for various information. Like the + * format of the ring buffer, event formats, ftrace formats, kallsyms + * and printk. This may be called multiple times with different @state + * values, to read partial data at a time. It will always continue + * where it left off. + */ +int tracecmd_read_headers(struct tracecmd_input *handle, + enum tracecmd_file_states state) +{ + if (!HAS_SECTIONS(handle)) + return read_headers_v6(handle, state, NULL); + return read_headers(handle, NULL); +} + +static unsigned long long calc_page_offset(struct tracecmd_input *handle, + unsigned long long offset) +{ + return offset & ~(handle->page_size - 1); +} + +static int read_page(struct tracecmd_input *handle, off64_t offset, + int cpu, void *map) +{ + off64_t save_seek; + off64_t ret; + + if (handle->use_pipe) { + ret = read(handle->cpu_data[cpu].pipe_fd, map, handle->page_size); + /* Set EAGAIN if the pipe is empty */ + if (ret < 0) { + errno = EAGAIN; + return -1; + + } else if (ret == 0) { + /* Set EINVAL when the pipe has closed */ + errno = EINVAL; + return -1; + } + return 0; + } + + /* other parts of the code may expect the pointer to not move */ + save_seek = lseek64(handle->fd, 0, SEEK_CUR); + + ret = lseek64(handle->fd, offset, SEEK_SET); + if (ret < 0) + return -1; + ret = read(handle->fd, map, handle->page_size); + if (ret < 0) + return -1; + + /* reset the file pointer back */ + lseek64(handle->fd, save_seek, SEEK_SET); + + return 0; +} + +/* page_map_size must be a power of two */ +static unsigned long long normalize_size(unsigned long long size) +{ + /* From Hacker's Delight: or bits after first set bit to all 1s */ + size |= (size >> 1); + size |= (size >> 2); + size |= (size >> 4); + size |= (size >> 8); + size |= (size >> 16); + size |= (size >> 32); + + /* Clear all bits except first one for previous power of two */ + return size - (size >> 1); +} + +static void free_page_map(struct page_map *page_map) +{ + page_map->ref_count--; + if (page_map->ref_count) + return; + + munmap(page_map->map, page_map->size); + list_del(&page_map->list); + free(page_map); +} + +#define CHUNK_CHECK_OFFSET(C, O) ((O) >= (C)->offset && (O) < ((C)->offset + (C)->size)) + +static int chunk_cmp(const void *A, const void *B) +{ + const struct tracecmd_compress_chunk *a = A; + const struct tracecmd_compress_chunk *b = B; + + if (CHUNK_CHECK_OFFSET(b, a->offset)) + return 0; + + if (b->offset < a->offset) + return -1; + + return 1; +} + +static struct tracecmd_compress_chunk *get_zchunk(struct cpu_data *cpu, off64_t offset) +{ + struct cpu_zdata *cpuz = &cpu->compress; + struct tracecmd_compress_chunk *chunk; + struct tracecmd_compress_chunk key; + + if (!cpuz->chunks) + return NULL; + + if (offset > (cpuz->chunks[cpuz->count - 1].offset + cpuz->chunks[cpuz->count - 1].size)) + return NULL; + + /* check if the requested offset is in the last requested chunk or in the next chunk */ + if (CHUNK_CHECK_OFFSET(cpuz->chunks + cpuz->last_chunk, offset)) + return cpuz->chunks + cpuz->last_chunk; + + cpuz->last_chunk++; + if (cpuz->last_chunk < cpuz->count && + CHUNK_CHECK_OFFSET(cpuz->chunks + cpuz->last_chunk, offset)) + return cpuz->chunks + cpuz->last_chunk; + + key.offset = offset; + chunk = bsearch(&key, cpuz->chunks, cpuz->count, sizeof(*chunk), chunk_cmp); + + if (!chunk) /* should never happen */ + return NULL; + + cpuz->last_chunk = chunk - cpuz->chunks; + return chunk; +} + +static void free_zpage(struct cpu_data *cpu_data, void *map) +{ + struct zchunk_cache *cache; + + list_for_each_entry(cache, &cpu_data->compress.cache, list) { + if (map <= cache->map && map > (cache->map + cache->chunk->size)) + goto found; + } + return; + +found: + cache->ref--; + if (cache->ref) + return; + list_del(&cache->list); + free(cache->map); + free(cache); +} + +static void *read_zpage(struct tracecmd_input *handle, int cpu, off64_t offset) +{ + struct cpu_data *cpu_data = &handle->cpu_data[cpu]; + struct tracecmd_compress_chunk *chunk; + struct zchunk_cache *cache; + void *map = NULL; + int pindex; + int size; + + offset -= cpu_data->file_offset; + + /* Look in the cache of already loaded chunks */ + list_for_each_entry(cache, &cpu_data->compress.cache, list) { + if (CHUNK_CHECK_OFFSET(cache->chunk, offset)) { + cache->ref++; + goto out; + } + } + + chunk = get_zchunk(cpu_data, offset); + if (!chunk) + return NULL; + + size = handle->page_size > chunk->size ? handle->page_size : chunk->size; + map = malloc(size); + if (!map) + return NULL; + + if (tracecmd_uncompress_chunk(handle->compress, chunk, map) < 0) + goto error; + + cache = calloc(1, sizeof(struct zchunk_cache)); + if (!cache) + goto error; + + cache->ref = 1; + cache->chunk = chunk; + cache->map = map; + list_add(&cache->list, &cpu_data->compress.cache); + + /* a chunk can hold multiple pages, get the requested one */ +out: + pindex = (offset - cache->chunk->offset) / handle->page_size; + return cache->map + (pindex * handle->page_size); +error: + free(map); + return NULL; +} + +static void *allocate_page_map(struct tracecmd_input *handle, + struct page *page, int cpu, off64_t offset) +{ + struct cpu_data *cpu_data = &handle->cpu_data[cpu]; + struct page_map *page_map; + off64_t map_size; + off64_t map_offset; + void *map; + int ret; + int fd; + + if (handle->cpu_compressed && handle->read_zpage) + return read_zpage(handle, cpu, offset); + + if (handle->read_page) { + map = malloc(handle->page_size); + if (!map) + return NULL; + ret = read_page(handle, offset, cpu, map); + if (ret < 0) { + free(map); + return NULL; + } + return map; + } + + map_size = handle->page_map_size; + map_offset = offset & ~(map_size - 1); + + if (map_offset < cpu_data->file_offset) { + map_size -= cpu_data->file_offset - map_offset; + map_offset = cpu_data->file_offset; + } + + page_map = cpu_data->page_map; + + if (page_map && page_map->offset == map_offset) + goto out; + + list_for_each_entry(page_map, &cpu_data->page_maps, list) { + if (page_map->offset == map_offset) + goto out; + } + + page_map = calloc(1, sizeof(*page_map)); + if (!page_map) + return NULL; + + if (map_offset + map_size > cpu_data->file_offset + cpu_data->file_size) + map_size -= map_offset + map_size - + (cpu_data->file_offset + cpu_data->file_size); + + if (cpu_data->compress.fd >= 0) { + map_offset -= cpu_data->file_offset; + fd = cpu_data->compress.fd; + } else + fd = handle->fd; + again: + page_map->size = map_size; + page_map->offset = map_offset; + + page_map->map = mmap(NULL, map_size, PROT_READ, MAP_PRIVATE, fd, map_offset); + + if (page_map->map == MAP_FAILED) { + /* Try a smaller map */ + map_size >>= 1; + if (map_size < handle->page_size) { + free(page_map); + return NULL; + } + handle->page_map_size = map_size; + map_offset = offset & ~(map_size - 1); + /* + * Note, it is now possible to get duplicate memory + * maps. But that's fine, the previous maps with + * larger sizes will eventually be unmapped. + */ + goto again; + } + + list_add(&page_map->list, &cpu_data->page_maps); + out: + if (cpu_data->page_map != page_map) { + struct page_map *old_map = cpu_data->page_map; + cpu_data->page_map = page_map; + page_map->ref_count++; + if (old_map) + free_page_map(old_map); + } + page->page_map = page_map; + page_map->ref_count++; + return page_map->map + offset - page_map->offset; +} + +static struct page *allocate_page(struct tracecmd_input *handle, + int cpu, off64_t offset) +{ + struct cpu_data *cpu_data = &handle->cpu_data[cpu]; + struct page **pages; + struct page *page; + int index; + + index = (offset - cpu_data->file_offset) / handle->page_size; + if (index >= cpu_data->nr_pages) { + pages = realloc(cpu_data->pages, (index + 1) * sizeof(*cpu_data->pages)); + if (!pages) + return NULL; + memset(pages + cpu_data->nr_pages, 0, + (index + 1 - cpu_data->nr_pages) * sizeof(*cpu_data->pages)); + cpu_data->pages = pages; + cpu_data->nr_pages = index + 1; + } + if (cpu_data->pages[index]) { + cpu_data->pages[index]->ref_count++; + return cpu_data->pages[index]; + } + + page = malloc(sizeof(*page)); + if (!page) + return NULL; + + memset(page, 0, sizeof(*page)); + page->offset = offset; + page->handle = handle; + page->cpu = cpu; + + page->map = allocate_page_map(handle, page, cpu, offset); + + if (!page->map) { + free(page); + return NULL; + } + + cpu_data->pages[index] = page; + cpu_data->page_cnt++; + page->ref_count = 1; + + return page; +} + +static void __free_page(struct tracecmd_input *handle, struct page *page) +{ + struct cpu_data *cpu_data = &handle->cpu_data[page->cpu]; + struct page **pages; + int index; + + if (!page->ref_count) { + tracecmd_critical("Page ref count is zero!"); + return; + } + + page->ref_count--; + if (page->ref_count) + return; + + if (handle->read_page) + free(page->map); + else if (handle->read_zpage) + free_zpage(cpu_data, page->map); + else + free_page_map(page->page_map); + + index = (page->offset - cpu_data->file_offset) / handle->page_size; + cpu_data->pages[index] = NULL; + cpu_data->page_cnt--; + + free(page); + + if (handle->use_pipe) { + for (index = cpu_data->nr_pages - 1; index > 0; index--) + if (cpu_data->pages[index]) + break; + if (index < (cpu_data->nr_pages - 1)) { + pages = realloc(cpu_data->pages, (index + 1) * sizeof(*cpu_data->pages)); + if (!pages) + return; + cpu_data->pages = pages; + cpu_data->nr_pages = index + 1; + } + } +} + +static void free_page(struct tracecmd_input *handle, int cpu) +{ + if (!handle->cpu_data || cpu >= handle->cpus || + !handle->cpu_data[cpu].page) + return; + + __free_page(handle, handle->cpu_data[cpu].page); + + handle->cpu_data[cpu].page = NULL; +} + +static void __free_record(struct tep_record *record) +{ + if (record->priv) { + struct page *page = record->priv; + remove_record(page, record); + __free_page(page->handle, page); + } + + free(record); +} + +void tracecmd_free_record(struct tep_record *record) +{ + if (!record) + return; + + if (!record->ref_count) { + tracecmd_critical("record ref count is zero!"); + return; + } + + record->ref_count--; + + if (record->ref_count) + return; + + if (record->locked) { + tracecmd_critical("freeing record when it is locked!"); + return; + } + + record->data = NULL; + + __free_record(record); +} + +void tracecmd_record_ref(struct tep_record *record) +{ + record->ref_count++; +#if DEBUG_RECORD + /* Update locating of last reference */ + record->alloc_addr = (unsigned long)__builtin_return_address(0); +#endif +} + +static void free_next(struct tracecmd_input *handle, int cpu) +{ + struct tep_record *record; + + if (!handle->cpu_data || cpu >= handle->cpus) + return; + + record = handle->cpu_data[cpu].next; + if (!record) + return; + + handle->cpu_data[cpu].next = NULL; + + record->locked = 0; + tracecmd_free_record(record); +} + +/* This functions was taken from the Linux kernel */ +static unsigned long long mul_u64_u32_shr(unsigned long long a, + unsigned long long mul, unsigned int shift) +{ + unsigned int ah, al; + unsigned long long ret; + + al = a; + ah = a >> 32; + + ret = (al * mul) >> shift; + if (ah) + ret += (ah * mul) << (32 - shift); + + return ret; +} + +static inline unsigned long long +timestamp_correction_calc(unsigned long long ts, unsigned int flags, + struct ts_offset_sample *min, + struct ts_offset_sample *max) +{ + long long tscor; + + if (flags & TRACECMD_TSYNC_FLAG_INTERPOLATE) { + long long delta = max->time - min->time; + long long offset = ((long long)ts - min->time) * + (max->offset - min->offset); + + tscor = min->offset + (offset + delta / 2) / delta; + } else { + tscor = min->offset; + } + + ts = (ts * min->scaling) >> min->fraction; + if (tscor < 0) + return ts - llabs(tscor); + + return ts + tscor; +} + +static unsigned long long timestamp_host_sync(unsigned long long ts, int cpu, + struct tracecmd_input *handle) +{ + struct timesync_offsets *tsync; + int min, mid, max; + + if (cpu >= handle->host.cpu_count) + return ts; + tsync = &handle->host.ts_offsets[cpu]; + + /* We have one sample, nothing to calc here */ + if (tsync->ts_samples_count == 1) + return ts + tsync->ts_samples[0].offset; + + /* We have two samples, nothing to search here */ + if (tsync->ts_samples_count == 2) + return timestamp_correction_calc(ts, handle->host.flags, + &tsync->ts_samples[0], + &tsync->ts_samples[1]); + + /* We have more than two samples */ + if (ts <= tsync->ts_samples[0].time) + return timestamp_correction_calc(ts, handle->host.flags, + &tsync->ts_samples[0], + &tsync->ts_samples[1]); + else if (ts >= tsync->ts_samples[tsync->ts_samples_count-1].time) + return timestamp_correction_calc(ts, handle->host.flags, + &tsync->ts_samples[tsync->ts_samples_count-2], + &tsync->ts_samples[tsync->ts_samples_count-1]); + min = 0; + max = tsync->ts_samples_count-1; + mid = (min + max)/2; + while (min <= max) { + if (ts < tsync->ts_samples[mid].time) + max = mid - 1; + else if (ts > tsync->ts_samples[mid].time) + min = mid + 1; + else + break; + mid = (min + max)/2; + } + + return timestamp_correction_calc(ts, handle->host.flags, + &tsync->ts_samples[mid], + &tsync->ts_samples[mid+1]); +} + +static unsigned long long timestamp_calc(unsigned long long ts, int cpu, + struct tracecmd_input *handle) +{ + /* do not modify raw timestamps */ + if (handle->flags & TRACECMD_FL_RAW_TS) + return ts; + + /* Guest trace file, sync with host timestamps */ + if (handle->host.sync_enable) + ts = timestamp_host_sync(ts, cpu, handle); + + if (handle->ts2secs) { + /* user specified clock frequency */ + ts *= handle->ts2secs; + } else if (handle->tsc_calc.mult) { + /* auto calculated TSC clock frequency */ + ts = mul_u64_u32_shr(ts, handle->tsc_calc.mult, handle->tsc_calc.shift); + } + + /* User specified time offset with --ts-offset or --date options */ + ts += handle->ts_offset; + + return ts; +} + +/* + * Page is mapped, now read in the page header info. + */ +static int update_page_info(struct tracecmd_input *handle, int cpu) +{ + struct tep_handle *pevent = handle->pevent; + void *ptr = handle->cpu_data[cpu].page->map; + struct kbuffer *kbuf = handle->cpu_data[cpu].kbuf; + + /* FIXME: handle header page */ + if (tep_get_header_timestamp_size(pevent) != 8) { + tracecmd_warning("expected a long long type for timestamp"); + return -1; + } + + kbuffer_load_subbuffer(kbuf, ptr); + if (kbuffer_subbuffer_size(kbuf) > handle->page_size) { + tracecmd_warning("bad page read, with size of %d", kbuffer_subbuffer_size(kbuf)); + return -1; + } + handle->cpu_data[cpu].timestamp = timestamp_calc(kbuffer_timestamp(kbuf), + cpu, handle); + + return 0; +} + +/* + * get_page maps a page for a given cpu. + * + * Returns 1 if the page was already mapped, + * 0 if it mapped successfully + * -1 on error + */ +static int get_page(struct tracecmd_input *handle, int cpu, + off64_t offset) +{ + /* Don't map if the page is already where we want */ + if (handle->cpu_data[cpu].offset == offset && + handle->cpu_data[cpu].page) + return 1; + + /* Do not map no data for CPU */ + if (!handle->cpu_data[cpu].size) + return -1; + + if (offset & (handle->page_size - 1)) { + errno = -EINVAL; + tracecmd_critical("bad page offset %llx", offset); + return -1; + } + + if (offset < handle->cpu_data[cpu].file_offset || + offset > handle->cpu_data[cpu].file_offset + + handle->cpu_data[cpu].file_size) { + errno = -EINVAL; + tracecmd_critical("bad page offset %llx", offset); + return -1; + } + + handle->cpu_data[cpu].offset = offset; + handle->cpu_data[cpu].size = (handle->cpu_data[cpu].file_offset + + handle->cpu_data[cpu].file_size) - + offset; + + free_page(handle, cpu); + + handle->cpu_data[cpu].page = allocate_page(handle, cpu, offset); + if (!handle->cpu_data[cpu].page) + return -1; + + if (update_page_info(handle, cpu)) + return -1; + + return 0; +} + +static int get_next_page(struct tracecmd_input *handle, int cpu) +{ + off64_t offset; + + if (!handle->cpu_data[cpu].page && !handle->use_pipe) + return 0; + + free_page(handle, cpu); + + if (handle->cpu_data[cpu].size <= handle->page_size) { + handle->cpu_data[cpu].offset = 0; + return 0; + } + + offset = handle->cpu_data[cpu].offset + handle->page_size; + + return get_page(handle, cpu, offset); +} + +static struct tep_record * +peek_event(struct tracecmd_input *handle, unsigned long long offset, + int cpu) +{ + struct tep_record *record = NULL; + + /* + * Since the timestamp is calculated from the beginning + * of the page and through each event, we reset the + * page to the beginning. This is just used by + * tracecmd_read_at. + */ + update_page_info(handle, cpu); + + do { + free_next(handle, cpu); + record = tracecmd_peek_data(handle, cpu); + if (record && (record->offset + record->record_size) > offset) + break; + } while (record); + + return record; +} + +static struct tep_record * +read_event(struct tracecmd_input *handle, unsigned long long offset, + int cpu) +{ + struct tep_record *record; + + record = peek_event(handle, offset, cpu); + if (record) + record = tracecmd_read_data(handle, cpu); + return record; +} + +static struct tep_record * +find_and_peek_event(struct tracecmd_input *handle, unsigned long long offset, + int *pcpu) +{ + unsigned long long page_offset; + int cpu; + + /* find the cpu that this offset exists in */ + for (cpu = 0; cpu < handle->cpus; cpu++) { + if (offset >= handle->cpu_data[cpu].file_offset && + offset < handle->cpu_data[cpu].file_offset + + handle->cpu_data[cpu].file_size) + break; + } + + /* Not found? */ + if (cpu == handle->cpus) + return NULL; + + /* Move this cpu index to point to this offest */ + page_offset = calc_page_offset(handle, offset); + + if (get_page(handle, cpu, page_offset) < 0) + return NULL; + + if (pcpu) + *pcpu = cpu; + + return peek_event(handle, offset, cpu); +} + + +static struct tep_record * +find_and_read_event(struct tracecmd_input *handle, unsigned long long offset, + int *pcpu) +{ + struct tep_record *record; + int cpu; + + record = find_and_peek_event(handle, offset, &cpu); + if (record) { + record = tracecmd_read_data(handle, cpu); + if (pcpu) + *pcpu = cpu; + } + return record; +} + +/** + * tracecmd_read_at - read a record from a specific offset + * @handle: input handle for the trace.dat file + * @offset: the offset into the file to find the record + * @pcpu: pointer to a variable to store the CPU id the record was found in + * + * This function is useful when looking for a previous record. + * You can store the offset of the record "record->offset" and use that + * offset to retreive the record again without needing to store any + * other information about the record. + * + * The record returned must be freed. + */ +struct tep_record * +tracecmd_read_at(struct tracecmd_input *handle, unsigned long long offset, + int *pcpu) +{ + unsigned long long page_offset; + int cpu; + + page_offset = calc_page_offset(handle, offset); + + /* check to see if we have this page already */ + for (cpu = 0; cpu < handle->cpus; cpu++) { + if (handle->cpu_data[cpu].offset == page_offset && + handle->cpu_data[cpu].file_size) + break; + } + + if (cpu < handle->cpus && handle->cpu_data[cpu].page) { + if (pcpu) + *pcpu = cpu; + return read_event(handle, offset, cpu); + } else + return find_and_read_event(handle, offset, pcpu); +} + +/** + * tracecmd_refresh_record - remaps the records data + * @handle: input handle for the trace.dat file + * @record: the record to be refreshed + * + * A record data points to a mmap section of memory. + * by reading new records the mmap section may be unmapped. + * This will refresh the record's data mapping. + * + * ===== OBSOLETED BY PAGE REFERENCES ===== + * + * Returns 1 if page is still mapped (does not modify CPU iterator) + * 0 on successful mapping (was not mapped before, + * This will update CPU iterator to point to + * the next record) + * -1 on error. + */ +int tracecmd_refresh_record(struct tracecmd_input *handle, + struct tep_record *record) +{ + unsigned long long page_offset; + int cpu = record->cpu; + struct cpu_data *cpu_data = &handle->cpu_data[cpu]; + int index; + int ret; + + page_offset = calc_page_offset(handle, record->offset); + index = record->offset & (handle->page_size - 1); + + ret = get_page(handle, record->cpu, page_offset); + if (ret < 0) + return -1; + + /* If the page is still mapped, there's nothing to do */ + if (ret) + return 1; + + record->data = kbuffer_read_at_offset(cpu_data->kbuf, index, &record->ts); + cpu_data->timestamp = record->ts; + + return 0; +} + +/** + * tracecmd_read_cpu_first - get the first record in a CPU + * @handle: input handle for the trace.dat file + * @cpu: the CPU to search + * + * This returns the first (by time) record entry in a given CPU. + * + * The record returned must be freed. + */ +struct tep_record * +tracecmd_read_cpu_first(struct tracecmd_input *handle, int cpu) +{ + unsigned long long page_offset; + int ret; + + if (cpu >= handle->cpus) + return NULL; + + page_offset = calc_page_offset(handle, handle->cpu_data[cpu].file_offset); + + ret = get_page(handle, cpu, page_offset); + if (ret < 0) + return NULL; + + /* If the page was already mapped, we need to reset it */ + if (ret) + update_page_info(handle, cpu); + + free_next(handle, cpu); + + return tracecmd_read_data(handle, cpu); +} + +/** + * tracecmd_read_cpu_last - get the last record in a CPU + * @handle: input handle for the trace.dat file + * @cpu: the CPU to search + * + * This returns the last (by time) record entry in a given CPU. + * + * The record returned must be freed. + */ +struct tep_record * +tracecmd_read_cpu_last(struct tracecmd_input *handle, int cpu) +{ + struct tep_record *record = NULL; + off64_t offset, page_offset; + + offset = handle->cpu_data[cpu].file_offset + + handle->cpu_data[cpu].file_size; + + if (offset & (handle->page_size - 1)) + offset &= ~(handle->page_size - 1); + else + offset -= handle->page_size; + + page_offset = offset; + + again: + if (get_page(handle, cpu, page_offset) < 0) + return NULL; + + offset = page_offset; + + do { + tracecmd_free_record(record); + record = tracecmd_read_data(handle, cpu); + if (record) + offset = record->offset; + } while (record); + + record = tracecmd_read_at(handle, offset, NULL); + + /* + * It is possible that a page has just a timestamp + * or just padding on it. + */ + if (!record) { + if (page_offset == handle->cpu_data[cpu].file_offset) + return NULL; + page_offset -= handle->page_size; + goto again; + } + + return record; +} + +/** + * tracecmd_set_cpu_to_timestamp - set the CPU iterator to a given time + * @handle: input handle for the trace.dat file + * @cpu: the CPU pointer to set + * @ts: the timestamp to set the CPU at. + * + * This sets the CPU iterator used by tracecmd_read_data and + * tracecmd_peek_data to a location in the CPU storage near + * a given timestamp. It will try to set the iterator to a time before + * the time stamp and not actually at a given time. + * + * To use this to find a record in a time field, call this function + * first, than iterate with tracecmd_read_data to find the records + * you need. + */ +int +tracecmd_set_cpu_to_timestamp(struct tracecmd_input *handle, int cpu, + unsigned long long ts) +{ + struct cpu_data *cpu_data = &handle->cpu_data[cpu]; + off64_t start, end, next; + + if (cpu < 0 || cpu >= handle->cpus) { + errno = -EINVAL; + return -1; + } + + if (!cpu_data->size) + return -1; + + if (!cpu_data->page) { + if (init_cpu(handle, cpu)) + return -1; + } + + if (cpu_data->timestamp == ts) { + /* + * If a record is cached, then that record is most + * likely the matching timestamp. Otherwise we need + * to start from the beginning of the index; + */ + if (!cpu_data->next || + cpu_data->next->ts != ts) + update_page_info(handle, cpu); + return 0; + } + + /* Set to the first record on current page */ + update_page_info(handle, cpu); + + if (cpu_data->timestamp < ts) { + start = cpu_data->offset; + end = cpu_data->file_offset + cpu_data->file_size; + if (end & (handle->page_size - 1)) + end &= ~(handle->page_size - 1); + else + end -= handle->page_size; + next = end; + } else { + end = cpu_data->offset; + start = cpu_data->file_offset; + next = start; + } + + while (start < end) { + if (get_page(handle, cpu, next) < 0) + return -1; + + if (cpu_data->timestamp == ts) + break; + + if (cpu_data->timestamp < ts) + start = next; + else + end = next; + + next = start + (end - start) / 2; + next = calc_page_offset(handle, next); + + /* Prevent an infinite loop if start and end are a page off */ + if (next == start) + start = next += handle->page_size; + } + + /* + * We need to end up on a page before the time stamp. + * We go back even if the timestamp is the same. This is because + * we want the event with the timestamp, not the page. The page + * can start with the timestamp we are looking for, but the event + * may be on the previous page. + */ + if (cpu_data->timestamp >= ts && + cpu_data->offset > cpu_data->file_offset) + get_page(handle, cpu, cpu_data->offset - handle->page_size); + + return 0; +} + +/** + * tracecmd_set_all_cpus_to_timestamp - set all CPUs iterator to a given time + * @handle: input handle for the trace.dat file + * @cpu: the CPU pointer to set + * @ts: the timestamp to set the CPU at. + * + * This sets the CPU iterator used by tracecmd_read_data and + * tracecmd_peek_data to a location in the CPU storage near + * a given timestamp. It will try to set the iterator to a time before + * the time stamp and not actually at a given time. + * + * To use this to find a record in a time field, call this function + * first, than iterate with tracecmd_read_next_data to find the records + * you need. + */ +void +tracecmd_set_all_cpus_to_timestamp(struct tracecmd_input *handle, + unsigned long long time) +{ + int cpu; + + for (cpu = 0; cpu < handle->cpus; cpu++) + tracecmd_set_cpu_to_timestamp(handle, cpu, time); +} + +/** + * tracecmd_set_cursor - set the offset for the next tracecmd_read_data + * @handle: input handle for the trace.dat file + * @cpu: the CPU pointer to set + * @offset: the offset to place the cursor + * + * Set the pointer to the next read or peek. This is useful when + * needing to read sequentially and then look at another record + * out of sequence without breaking the iteration. This is done with: + * + * record = tracecmd_peek_data() + * offset = record->offset; + * record = tracecmd_read_at(); + * - do what ever with record - + * tracecmd_set_cursor(handle, cpu, offset); + * + * Now the next tracecmd_peek_data or tracecmd_read_data will return + * the original record. + */ +int tracecmd_set_cursor(struct tracecmd_input *handle, + int cpu, unsigned long long offset) +{ + struct cpu_data *cpu_data = &handle->cpu_data[cpu]; + unsigned long long page_offset; + + if (cpu < 0 || cpu >= handle->cpus) + return -1; + + if (offset < cpu_data->file_offset || + offset > cpu_data->file_offset + cpu_data->file_size) + return -1; /* cpu does not have this offset. */ + + /* Move this cpu index to point to this offest */ + page_offset = calc_page_offset(handle, offset); + + if (get_page(handle, cpu, page_offset) < 0) + return -1; + + peek_event(handle, offset, cpu); + + return 0; +} + +/** + * tracecmd_get_cursor - get the offset for the next tracecmd_read_data + * @handle: input handle for the trace.dat file + * @cpu: the CPU pointer to get the cursor from + * + * Returns the offset of the next record that would be read. + */ +unsigned long long +tracecmd_get_cursor(struct tracecmd_input *handle, int cpu) +{ + struct cpu_data *cpu_data = &handle->cpu_data[cpu]; + struct kbuffer *kbuf = cpu_data->kbuf; + + if (cpu < 0 || cpu >= handle->cpus) + return 0; + + /* + * Use the next pointer if it exists and matches the + * current timestamp. + */ + if (cpu_data->next && + cpu_data->next->ts == cpu_data->timestamp) + return cpu_data->next->offset; + + /* + * Either the next point does not exist, or it does + * not match the timestamp. The next read will use the + * current page. + * + * If the offset is at the end, then return that. + */ + if (cpu_data->offset >= cpu_data->file_offset + + cpu_data->file_size) + return cpu_data->offset; + + return cpu_data->offset + kbuffer_curr_offset(kbuf); +} + +/** + * tracecmd_translate_data - create a record from raw data + * @handle: input handle for the trace.dat file + * @ptr: raw data to read + * @size: the size of the data + * + * This function tries to create a record from some given + * raw data. The data does not need to be from the trace.dat file. + * It can be stored from another location. + * + * Note, since the timestamp is calculated from within the trace + * buffer, the timestamp for the record will be zero, since it + * can't calculate it. + * + * The record returned must be freed. + */ +struct tep_record * +tracecmd_translate_data(struct tracecmd_input *handle, + void *ptr, int size) +{ + struct tep_handle *pevent = handle->pevent; + struct tep_record *record; + unsigned int length; + int swap = 1; + + /* minimum record read is 8, (warn?) (TODO: make 8 into macro) */ + if (size < 8) + return NULL; + + record = malloc(sizeof(*record)); + if (!record) + return NULL; + memset(record, 0, sizeof(*record)); + + record->ref_count = 1; + if (tep_is_local_bigendian(pevent) == tep_is_file_bigendian(pevent)) + swap = 0; + record->data = kbuffer_translate_data(swap, ptr, &length); + record->size = length; + if (record->data) + record->record_size = record->size + (record->data - ptr); + + return record; +} + + +/** + * tracecmd_peek_data - return the record at the current location. + * @handle: input handle for the trace.dat file + * @cpu: the CPU to pull from + * + * This returns the record at the current location of the CPU + * iterator. It does not increment the CPU iterator. + */ +struct tep_record * +tracecmd_peek_data(struct tracecmd_input *handle, int cpu) +{ + struct tep_record *record; + unsigned long long ts; + struct kbuffer *kbuf; + struct page *page; + int index; + void *data; + + if (cpu >= handle->cpus) + return NULL; + + page = handle->cpu_data[cpu].page; + kbuf = handle->cpu_data[cpu].kbuf; + + /* Hack to work around function graph read ahead */ + tracecmd_curr_thread_handle = handle; + + if (handle->cpu_data[cpu].next) { + + record = handle->cpu_data[cpu].next; + if (!record->data) { + tracecmd_critical("Something freed the record"); + return NULL; + } + + if (handle->cpu_data[cpu].timestamp == record->ts) + return record; + + /* + * The timestamp changed, which means the cached + * record is no longer valid. Reread a new record. + */ + free_next(handle, cpu); + } + +read_again: + if (!page) { + if (handle->use_pipe) { + get_next_page(handle, cpu); + page = handle->cpu_data[cpu].page; + } + if (!page) + return NULL; + } + + data = kbuffer_read_event(kbuf, &ts); + if (!data) { + if (get_next_page(handle, cpu)) + return NULL; + page = handle->cpu_data[cpu].page; + goto read_again; + } + + handle->cpu_data[cpu].timestamp = timestamp_calc(ts, cpu, handle); + + index = kbuffer_curr_offset(kbuf); + + record = malloc(sizeof(*record)); + if (!record) + return NULL; + memset(record, 0, sizeof(*record)); + + record->ts = handle->cpu_data[cpu].timestamp; + record->size = kbuffer_event_size(kbuf); + record->cpu = handle->cpu_data[cpu].cpu; + record->data = data; + record->offset = handle->cpu_data[cpu].offset + index; + record->missed_events = kbuffer_missed_events(kbuf); + record->ref_count = 1; + record->locked = 1; + + handle->cpu_data[cpu].next = record; + + record->record_size = kbuffer_curr_size(kbuf); + record->priv = page; + add_record(page, record); + page->ref_count++; + + kbuffer_next_event(kbuf, NULL); + + return record; +} + +/** + * tracecmd_read_data - read the next record and increment + * @handle: input handle for the trace.dat file + * @cpu: the CPU to pull from + * + * This returns the record at the current location of the CPU + * iterator and increments the CPU iterator. + * + * The record returned must be freed. + */ +struct tep_record * +tracecmd_read_data(struct tracecmd_input *handle, int cpu) +{ + struct tep_record *record; + + if (cpu >= handle->cpus) + return NULL; + + record = tracecmd_peek_data(handle, cpu); + handle->cpu_data[cpu].next = NULL; + if (record) { + record->locked = 0; +#if DEBUG_RECORD + record->alloc_addr = (unsigned long)__builtin_return_address(0); +#endif + } + return record; +} + +/** + * tracecmd_read_next_data - read the next record + * @handle: input handle to the trace.dat file + * @rec_cpu: return pointer to the CPU that the record belongs to + * + * This returns the next record by time. This is different than + * tracecmd_read_data in that it looks at all CPUs. It does a peek + * at each CPU and the record with the earliest time stame is + * returned. If @rec_cpu is not NULL it gets the CPU id the record was + * on. The CPU cursor of the returned record is moved to the + * next record. + * + * Multiple reads of this function will return a serialized list + * of all records for all CPUs in order of time stamp. + * + * The record returned must be freed. + */ +struct tep_record * +tracecmd_read_next_data(struct tracecmd_input *handle, int *rec_cpu) +{ + struct tep_record *record; + int next_cpu; + + record = tracecmd_peek_next_data(handle, &next_cpu); + if (!record) + return NULL; + + if (rec_cpu) + *rec_cpu = next_cpu; + + return tracecmd_read_data(handle, next_cpu); +} + +/** + * tracecmd_peek_next_data - return the next record + * @handle: input handle to the trace.dat file + * @rec_cpu: return pointer to the CPU that the record belongs to + * + * This returns the next record by time. This is different than + * tracecmd_peek_data in that it looks at all CPUs. It does a peek + * at each CPU and the record with the earliest time stame is + * returned. If @rec_cpu is not NULL it gets the CPU id the record was + * on. It does not increment the CPU iterator. + */ +struct tep_record * +tracecmd_peek_next_data(struct tracecmd_input *handle, int *rec_cpu) +{ + unsigned long long ts; + struct tep_record *record, *next_record = NULL; + int next_cpu; + int cpu; + + if (rec_cpu) + *rec_cpu = -1; + + next_cpu = -1; + ts = 0; + + for (cpu = 0; cpu < handle->cpus; cpu++) { + record = tracecmd_peek_data(handle, cpu); + if (record && (!next_record || record->ts < ts)) { + ts = record->ts; + next_cpu = cpu; + next_record = record; + } + } + + if (next_record) { + if (rec_cpu) + *rec_cpu = next_cpu; + return next_record; + } + + return NULL; +} + +/** + * tracecmd_read_prev - read the record before the given record + * @handle: input handle to the trace.dat file + * @record: the record to use to find the previous record. + * + * This returns the record before the @record on its CPU. If + * @record is the first record, NULL is returned. The cursor is set + * as if the previous record was read by tracecmd_read_data(). + * + * @record can not be NULL, otherwise NULL is returned; the + * record ownership goes to this function. + * + * Note, this is not that fast of an algorithm, since it needs + * to build the timestamp for the record. + * + * The record returned must be freed with tracecmd_free_record(). + */ +struct tep_record * +tracecmd_read_prev(struct tracecmd_input *handle, struct tep_record *record) +{ + unsigned long long offset, page_offset;; + struct cpu_data *cpu_data; + int index; + int cpu; + + if (!record) + return NULL; + + cpu = record->cpu; + offset = record->offset; + cpu_data = &handle->cpu_data[cpu]; + + page_offset = calc_page_offset(handle, offset); + index = offset - page_offset; + + /* Note, the record passed in could have been a peek */ + free_next(handle, cpu); + + /* Reset the cursor */ + /* Should not happen */ + if (get_page(handle, cpu, page_offset) < 0) + return NULL; + + update_page_info(handle, cpu); + + /* Find the record before this record */ + index = 0; + for (;;) { + record = tracecmd_read_data(handle, cpu); + /* Should not happen! */ + if (!record) + return NULL; + if (record->offset == offset) + break; + index = record->offset - page_offset; + tracecmd_free_record(record); + } + tracecmd_free_record(record); + + if (index) + /* we found our record */ + return tracecmd_read_at(handle, page_offset + index, NULL); + + /* reset the index to start at the beginning of the page */ + update_page_info(handle, cpu); + + /* The previous record is on the previous page */ + for (;;) { + /* check if this is the first page */ + if (page_offset == cpu_data->file_offset) + return NULL; + page_offset -= handle->page_size; + + /* Updating page to a new page will reset index to 0 */ + get_page(handle, cpu, page_offset); + + record = NULL; + index = 0; + do { + if (record) { + index = record->offset - page_offset; + tracecmd_free_record(record); + } + record = tracecmd_read_data(handle, cpu); + /* Should not happen */ + if (!record) + return NULL; + } while (record->offset != offset); + tracecmd_free_record(record); + + if (index) + /* we found our record */ + return tracecmd_read_at(handle, page_offset + index, NULL); + } + + /* Not reached */ +} + +static int init_cpu_zfile(struct tracecmd_input *handle, int cpu) +{ + struct cpu_data *cpu_data; + unsigned long long size; + off64_t offset; + + cpu_data = &handle->cpu_data[cpu]; + offset = lseek64(handle->fd, 0, SEEK_CUR); + if (lseek64(handle->fd, cpu_data->file_offset, SEEK_SET) == (off_t)-1) + return -1; + + strcpy(cpu_data->compress.file, COMPR_TEMP_FILE); + cpu_data->compress.fd = mkstemp(cpu_data->compress.file); + if (cpu_data->compress.fd < 0) + return -1; + + if (tracecmd_uncompress_copy_to(handle->compress, cpu_data->compress.fd, NULL, &size)) + return -1; + + if (lseek64(handle->fd, offset, SEEK_SET) == (off_t)-1) + return -1; + + cpu_data->file_offset = handle->next_offset; + handle->next_offset = (handle->next_offset + size + handle->page_size - 1) & + ~(handle->page_size - 1); + cpu_data->offset = cpu_data->file_offset; + + cpu_data->file_size = size; + cpu_data->size = size; + return 0; +} + +static int init_cpu_zpage(struct tracecmd_input *handle, int cpu) +{ + struct cpu_data *cpu_data = &handle->cpu_data[cpu]; + int count; + int i; + + if (lseek64(handle->fd, cpu_data->file_offset, SEEK_SET) == (off_t)-1) + return -1; + + count = tracecmd_load_chunks_info(handle->compress, &cpu_data->compress.chunks); + if (count < 0) + return -1; + + cpu_data->compress.count = count; + cpu_data->compress.last_chunk = 0; + + cpu_data->file_offset = handle->next_offset; + + for (i = 0; i < count; i++) + cpu_data->file_size += cpu_data->compress.chunks[i].size; + + cpu_data->offset = cpu_data->file_offset; + cpu_data->size = cpu_data->file_size; + handle->next_offset = (handle->next_offset + cpu_data->size + handle->page_size - 1) & + ~(handle->page_size - 1); + return 0; +} + +static int init_cpu(struct tracecmd_input *handle, int cpu) +{ + struct cpu_data *cpu_data = &handle->cpu_data[cpu]; + int ret; + int i; + + if (handle->cpu_compressed && cpu_data->file_size > 0) { + if (handle->read_zpage) + ret = init_cpu_zpage(handle, cpu); + else + ret = init_cpu_zfile(handle, cpu); + if (ret) + return ret; + } else { + cpu_data->offset = cpu_data->file_offset; + cpu_data->size = cpu_data->file_size; + } + cpu_data->timestamp = 0; + + list_head_init(&cpu_data->page_maps); + list_head_init(&cpu_data->compress.cache); + + if (!cpu_data->size) { + tracecmd_info("CPU %d is empty", cpu); + return 0; + } + + cpu_data->nr_pages = (cpu_data->size + handle->page_size - 1) / handle->page_size; + if (!cpu_data->nr_pages) + cpu_data->nr_pages = 1; + cpu_data->pages = calloc(cpu_data->nr_pages, sizeof(*cpu_data->pages)); + if (!cpu_data->pages) + return -1; + + if (handle->use_pipe) { + /* Just make a page, it will be nuked later */ + cpu_data->page = malloc(sizeof(*cpu_data->page)); + if (!cpu_data->page) + goto fail; + + memset(cpu_data->page, 0, sizeof(*cpu_data->page)); + cpu_data->pages[0] = cpu_data->page; + cpu_data->page_cnt = 1; + cpu_data->page->ref_count = 1; + return 0; + } + + cpu_data->page = allocate_page(handle, cpu, cpu_data->offset); + if (!cpu_data->page && !handle->read_page) { + perror("mmap"); + fprintf(stderr, "Can not mmap file, will read instead\n"); + + if (cpu) { + /* + * If the other CPUs had size and was able to mmap + * then bail. + */ + for (i = 0; i < cpu; i++) { + if (handle->cpu_data[i].size) + goto fail; + } + } + + /* try again without mmapping, just read it directly */ + handle->read_page = true; + cpu_data->page = allocate_page(handle, cpu, cpu_data->offset); + if (!cpu_data->page) + /* Still no luck, bail! */ + goto fail; + } + + if (update_page_info(handle, cpu)) + goto fail; + cpu_data->first_ts = cpu_data->timestamp; + + return 0; + fail: + free(cpu_data->pages); + cpu_data->pages = NULL; + free(cpu_data->page); + cpu_data->page = NULL; + return -1; +} + +void tracecmd_set_ts_offset(struct tracecmd_input *handle, + long long offset) +{ + handle->ts_offset = offset; +} + +/** + * tracecmd_add_ts_offset - Add value to the offset which will be applied to the timestamps of all + * events from given trace file + * @handle: input handle to the trace.dat file + * @offset: value, that will be added to the offset + */ +void tracecmd_add_ts_offset(struct tracecmd_input *handle, + long long offset) +{ + handle->ts_offset += offset; +} + +void tracecmd_set_ts2secs(struct tracecmd_input *handle, + unsigned long long hz) +{ + double ts2secs; + + ts2secs = (double)NSEC_PER_SEC / (double)hz; + handle->ts2secs = ts2secs; + handle->use_trace_clock = false; +} + +static int tsync_offset_cmp(const void *a, const void *b) +{ + struct ts_offset_sample *ts_a = (struct ts_offset_sample *)a; + struct ts_offset_sample *ts_b = (struct ts_offset_sample *)b; + + if (ts_a->time > ts_b->time) + return 1; + if (ts_a->time < ts_b->time) + return -1; + return 0; +} + +#define safe_read(R, C) \ + do { \ + if ((C) > size) \ + return -EFAULT; \ + (R) = tep_read_number(tep, buf, (C)); \ + buf += (C); \ + size -= (C); \ + } while (0) + +#define safe_read_loop(type) \ + do { \ + int ii; \ + for (ii = 0; ii < ts_offsets->ts_samples_count; ii++) \ + safe_read(ts_offsets->ts_samples[ii].type, 8); \ + } while (0) + +static int tsync_cpu_offsets_load(struct tracecmd_input *handle, char *buf, int size) +{ + struct tep_handle *tep = handle->pevent; + struct timesync_offsets *ts_offsets; + int i, j, k; + + safe_read(handle->host.cpu_count, 4); + handle->host.ts_offsets = calloc(handle->host.cpu_count, + sizeof(struct timesync_offsets)); + if (!handle->host.ts_offsets) + return -ENOMEM; + for (i = 0; i < handle->host.cpu_count; i++) { + ts_offsets = &handle->host.ts_offsets[i]; + safe_read(ts_offsets->ts_samples_count, 4); + ts_offsets->ts_samples = calloc(ts_offsets->ts_samples_count, + sizeof(struct ts_offset_sample)); + if (!ts_offsets->ts_samples) + return -ENOMEM; + safe_read_loop(time); + safe_read_loop(offset); + safe_read_loop(scaling); + } + + if (size > 0) { + for (i = 0; i < handle->host.cpu_count; i++) { + ts_offsets = &handle->host.ts_offsets[i]; + safe_read_loop(fraction); + } + } + + for (i = 0; i < handle->host.cpu_count; i++) { + ts_offsets = &handle->host.ts_offsets[i]; + qsort(ts_offsets->ts_samples, ts_offsets->ts_samples_count, + sizeof(struct ts_offset_sample), tsync_offset_cmp); + /* Filter possible samples with equal time */ + for (k = 0, j = 0; k < ts_offsets->ts_samples_count; k++) { + if (k == 0 || ts_offsets->ts_samples[k].time != ts_offsets->ts_samples[k-1].time) + ts_offsets->ts_samples[j++] = ts_offsets->ts_samples[k]; + } + ts_offsets->ts_samples_count = j; + } + + return 0; +} + +static void trace_tsync_offset_free(struct host_trace_info *host) +{ + int i; + + if (host->ts_offsets) { + for (i = 0; i < host->cpu_count; i++) + free(host->ts_offsets[i].ts_samples); + free(host->ts_offsets); + host->ts_offsets = NULL; + } +} + +static int trace_pid_map_cmp(const void *a, const void *b) +{ + struct tracecmd_proc_addr_map *m_a = (struct tracecmd_proc_addr_map *)a; + struct tracecmd_proc_addr_map *m_b = (struct tracecmd_proc_addr_map *)b; + + if (m_a->start > m_b->start) + if (m_a->start < m_b->start) + return -1; + return 0; +} + +static void procmap_free(struct pid_addr_maps *maps) +{ + int i; + + if (!maps) + return; + if (maps->lib_maps) { + for (i = 0; i < maps->nr_lib_maps; i++) + free(maps->lib_maps[i].lib_name); + free(maps->lib_maps); + } + free(maps->proc_name); + free(maps); +} + +static void trace_guests_free(struct tracecmd_input *handle) +{ + struct guest_trace_info *guest; + + while (handle->guest) { + guest = handle->guest; + handle->guest = handle->guest->next; + free(guest->name); + free(guest->cpu_pid); + free(guest); + } +} + +static int trace_guest_load(struct tracecmd_input *handle, char *buf, int size) +{ + struct guest_trace_info *guest = NULL; + int cpu; + int i; + + guest = calloc(1, sizeof(struct guest_trace_info)); + if (!guest) + goto error; + + /* + * Guest name, null terminated string + * long long (8 bytes) trace-id + * int (4 bytes) number of guest CPUs + * array of size number of guest CPUs: + * int (4 bytes) Guest CPU id + * int (4 bytes) Host PID, running the guest CPU + */ + + guest->name = strndup(buf, size); + if (!guest->name) + goto error; + buf += strlen(guest->name) + 1; + size -= strlen(guest->name) + 1; + + if (size < sizeof(long long)) + goto error; + guest->trace_id = tep_read_number(handle->pevent, buf, sizeof(long long)); + buf += sizeof(long long); + size -= sizeof(long long); + + if (size < sizeof(int)) + goto error; + guest->vcpu_count = tep_read_number(handle->pevent, buf, sizeof(int)); + buf += sizeof(int); + size -= sizeof(int); + + guest->cpu_pid = calloc(guest->vcpu_count, sizeof(int)); + if (!guest->cpu_pid) + goto error; + + for (i = 0; i < guest->vcpu_count; i++) { + if (size < 2 * sizeof(int)) + goto error; + cpu = tep_read_number(handle->pevent, buf, sizeof(int)); + buf += sizeof(int); + if (cpu >= guest->vcpu_count) + goto error; + guest->cpu_pid[cpu] = tep_read_number(handle->pevent, + buf, sizeof(int)); + buf += sizeof(int); + size -= 2 * sizeof(int); + } + + guest->next = handle->guest; + handle->guest = guest; + return 0; + +error: + if (guest) { + free(guest->cpu_pid); + free(guest->name); + free(guest); + } + return -1; +} + +/* Needs to be a constant, and 4K should be good enough */ +#define STR_PROCMAP_LINE_MAX 4096 +static int trace_pid_map_load(struct tracecmd_input *handle, char *buf) +{ + struct pid_addr_maps *maps = NULL; + char mapname[STR_PROCMAP_LINE_MAX+1]; + char *line; + int res; + int ret; + int i; + + maps = calloc(1, sizeof(*maps)); + if (!maps) + return -ENOMEM; + + ret = -EINVAL; + line = strchr(buf, '\n'); + if (!line) + goto out_fail; + + *line = '\0'; + if (strlen(buf) > STR_PROCMAP_LINE_MAX) + goto out_fail; + + res = sscanf(buf, "%x %x %"STRINGIFY(STR_PROCMAP_LINE_MAX)"s", &maps->pid, &maps->nr_lib_maps, mapname); + if (res != 3) + goto out_fail; + + ret = -ENOMEM; + maps->proc_name = strdup(mapname); + if (!maps->proc_name) + goto out_fail; + + maps->lib_maps = calloc(maps->nr_lib_maps, sizeof(struct tracecmd_proc_addr_map)); + if (!maps->lib_maps) + goto out_fail; + + buf = line + 1; + line = strchr(buf, '\n'); + for (i = 0; i < maps->nr_lib_maps; i++) { + if (!line) + break; + *line = '\0'; + if (strlen(buf) > STR_PROCMAP_LINE_MAX) + break; + res = sscanf(buf, "%llx %llx %s", &maps->lib_maps[i].start, + &maps->lib_maps[i].end, mapname); + if (res != 3) + break; + maps->lib_maps[i].lib_name = strdup(mapname); + if (!maps->lib_maps[i].lib_name) + goto out_fail; + buf = line + 1; + line = strchr(buf, '\n'); + } + + ret = -EINVAL; + if (i != maps->nr_lib_maps) + goto out_fail; + + qsort(maps->lib_maps, maps->nr_lib_maps, + sizeof(*maps->lib_maps), trace_pid_map_cmp); + + maps->next = handle->pid_maps; + handle->pid_maps = maps; + + return 0; + +out_fail: + procmap_free(maps); + return ret; +} + +static void trace_pid_map_free(struct pid_addr_maps *maps) +{ + struct pid_addr_maps *del; + + while (maps) { + del = maps; + maps = maps->next; + procmap_free(del); + } +} + +static int trace_pid_map_search(const void *a, const void *b) +{ + struct tracecmd_proc_addr_map *key = (struct tracecmd_proc_addr_map *)a; + struct tracecmd_proc_addr_map *map = (struct tracecmd_proc_addr_map *)b; + + if (key->start >= map->end) + return 1; + if (key->start < map->start) + return -1; + return 0; +} + +/** + * tracecmd_search_task_map - Search task memory address map + * @handle: input handle to the trace.dat file + * @pid: pid of the task + * @addr: address from the task memory space. + * + * Map of the task memory can be saved in the trace.dat file, using the option + * "--proc-map". If there is such information, this API can be used to look up + * into this memory map to find what library is loaded at the given @addr. + * + * A pointer to struct tracecmd_proc_addr_map is returned, containing the name + * of the library at given task @addr and the library start and end addresses. + */ +struct tracecmd_proc_addr_map * +tracecmd_search_task_map(struct tracecmd_input *handle, + int pid, unsigned long long addr) +{ + struct tracecmd_proc_addr_map *lib; + struct tracecmd_proc_addr_map key; + struct pid_addr_maps *maps; + + if (!handle || !handle->pid_maps) + return NULL; + + maps = handle->pid_maps; + while (maps) { + if (maps->pid == pid) + break; + maps = maps->next; + } + if (!maps || !maps->nr_lib_maps || !maps->lib_maps) + return NULL; + key.start = addr; + lib = bsearch(&key, maps->lib_maps, maps->nr_lib_maps, + sizeof(*maps->lib_maps), trace_pid_map_search); + + return lib; +} + +__hidden unsigned int get_meta_strings_size(struct tracecmd_input *handle) +{ + return handle->strings_size; +} + +__hidden unsigned long long get_last_option_offset(struct tracecmd_input *handle) +{ + return handle->options_last_offset; +} + +static int handle_option_done(struct tracecmd_input *handle, char *buf, int size) +{ + unsigned long long offset; + + if (size < 8) + return -1; + + offset = lseek64(handle->fd, 0, SEEK_CUR); + if (offset >= size) + handle->options_last_offset = offset - size; + + offset = tep_read_number(handle->pevent, buf, 8); + if (!offset) + return 0; + + if (lseek64(handle->fd, offset, SEEK_SET) == (off_t)-1) + return -1; + + return handle_options(handle); +} + +static inline int save_read_number(struct tep_handle *tep, char *data, int *data_size, + int *read_pos, int bytes, unsigned long long *num) +{ + if (bytes > *data_size) + return -1; + + *num = tep_read_number(tep, (data + *read_pos), bytes); + *read_pos += bytes; + *data_size -= bytes; + return 0; +} + +static inline char *save_read_string(char *data, int *data_size, int *read_pos) +{ + char *str; + + if (*data_size < 1) + return NULL; + + str = strdup(data + *read_pos); + if (!str) + return NULL; + *data_size -= (strlen(str) + 1); + if (*data_size < 0) { + free(str); + return NULL; + } + *read_pos += (strlen(str) + 1); + + return str; +} + +static int handle_buffer_option(struct tracecmd_input *handle, + unsigned short id, char *data, int size) +{ + struct input_buffer_instance *buff; + struct cpu_file_data *cpu_data; + unsigned long long tmp; + long long max_cpu = -1; + int rsize = 0; + char *name; + int i; + + if (save_read_number(handle->pevent, data, &size, &rsize, 8, &tmp)) + return -1; + + name = save_read_string(data, &size, &rsize); + if (!name) + return -1; + + if (*name == '\0') { + /* top buffer */ + buff = &handle->top_buffer; + } else { + buff = realloc(handle->buffers, sizeof(*handle->buffers) * (handle->nr_buffers + 1)); + if (!buff) { + free(name); + return -1; + } + handle->buffers = buff; + handle->nr_buffers++; + + buff = &handle->buffers[handle->nr_buffers - 1]; + } + memset(buff, 0, sizeof(struct input_buffer_instance)); + buff->name = name; + buff->offset = tmp; + + if (!HAS_SECTIONS(handle)) + return 0; + + /* file sections specific data */ + buff->clock = save_read_string(data, &size, &rsize); + if (!buff->clock) + return -1; + + if (*name == '\0' && !handle->trace_clock) + handle->trace_clock = strdup(buff->clock); + + if (id == TRACECMD_OPTION_BUFFER) { + if (save_read_number(handle->pevent, data, &size, &rsize, 4, &tmp)) + return -1; + buff->page_size = tmp; + + if (save_read_number(handle->pevent, data, &size, &rsize, 4, &tmp)) + return -1; + buff->cpus = tmp; + if (!buff->cpus) + return 0; + cpu_data = calloc(buff->cpus, sizeof(*cpu_data)); + if (!cpu_data) + return -1; + for (i = 0; i < buff->cpus; i++) { + if (save_read_number(handle->pevent, data, &size, &rsize, 4, &tmp)) + goto fail; + if ((long long)tmp > max_cpu) + max_cpu = tmp; + cpu_data[i].cpu = tmp; + if (save_read_number(handle->pevent, data, + &size, &rsize, 8, &cpu_data[i].offset)) + goto fail; + if (save_read_number(handle->pevent, data, + &size, &rsize, 8, &cpu_data[i].size)) + goto fail; + } + if (buff->cpus == max_cpu + 1) { + /* Check to make sure cpus match the index */ + for (i = 0; i < buff->cpus; i++) { + if (cpu_data[i].cpu != i) + goto copy_buffer; + } + buff->cpu_data = cpu_data; + } else { + copy_buffer: + buff->cpu_data = calloc(max_cpu + 1, sizeof(*cpu_data)); + if (!buff->cpu_data) + goto fail; + for (i = 0; i < buff->cpus; i++) { + if (buff->cpu_data[cpu_data[i].cpu].size) { + tracecmd_warning("More than one buffer defined for CPU %d (buffer %d)\n", + cpu_data[i].cpu, i); + goto fail; + } + buff->cpu_data[cpu_data[i].cpu] = cpu_data[i]; + } + buff->cpus = max_cpu + 1; + free(cpu_data); + } + } else { + buff->latency = true; + } + return 0; +fail: + free(cpu_data); + return -1; +} + +static int handle_options(struct tracecmd_input *handle) +{ + long long offset; + unsigned short option; + unsigned int size; + unsigned short id, flags; + char *cpustats = NULL; + struct hook_list *hook; + bool compress = false; + char *buf; + int cpus; + int ret; + + if (!HAS_SECTIONS(handle)) { + handle->options_start = lseek64(handle->fd, 0, SEEK_CUR); + } else { + if (read_section_header(handle, &id, &flags, NULL, NULL)) + return -1; + if (id != TRACECMD_OPTION_DONE) + return -1; + if (flags & TRACECMD_SEC_FL_COMPRESS) + compress = true; + } + + if (compress && in_uncompress_block(handle)) + return -1; + + for (;;) { + ret = read2(handle, &option); + if (ret) + goto out; + + if (!HAS_SECTIONS(handle) && option == TRACECMD_OPTION_DONE) + break; + + /* next 4 bytes is the size of the option */ + ret = read4(handle, &size); + if (ret) + goto out; + buf = malloc(size); + if (!buf) { + ret = -ENOMEM; + goto out; + } + ret = do_read_check(handle, buf, size); + if (ret) + goto out; + + switch (option) { + case TRACECMD_OPTION_DATE: + /* + * A time has been mapped that is the + * difference between the timestamps and + * gtod. It is stored as ASCII with '0x' + * appended. + */ + if (handle->flags & + (TRACECMD_FL_IGNORE_DATE | TRACECMD_FL_RAW_TS)) + break; + offset = strtoll(buf, NULL, 0); + /* Convert from micro to nano */ + offset *= 1000; + handle->ts_offset += offset; + break; + case TRACECMD_OPTION_OFFSET: + /* + * Similar to date option, but just adds an + * offset to the timestamp. + */ + if (handle->flags & TRACECMD_FL_RAW_TS) + break; + offset = strtoll(buf, NULL, 0); + handle->ts_offset += offset; + break; + case TRACECMD_OPTION_TIME_SHIFT: + /* + * long long int (8 bytes) trace session ID + * int (4 bytes) protocol flags. + * int (4 bytes) CPU count. + * array of size [CPU count]: + * [ + * int (4 bytes) count of timestamp offsets. + * long long array of size [count] of times, + * when the offsets were calculated. + * long long array of size [count] of timestamp offsets. + * long long array of size [count] of timestamp scaling ratios.* + * ] + * array of size [CPU count]: + * [ + * long long array of size [count] of timestamp scaling fraction bits.* + * ]* + */ + if (size < 16 || (handle->flags & TRACECMD_FL_RAW_TS)) + break; + handle->host.peer_trace_id = tep_read_number(handle->pevent, + buf, 8); + handle->host.flags = tep_read_number(handle->pevent, + buf + 8, 4); + ret = tsync_cpu_offsets_load(handle, buf + 12, size - 12); + if (ret < 0) + goto out; + tracecmd_enable_tsync(handle, true); + break; + case TRACECMD_OPTION_CPUSTAT: + buf[size-1] = '\n'; + cpustats = realloc(handle->cpustats, + handle->cpustats_size + size + 1); + if (!cpustats) { + ret = -ENOMEM; + goto out; + } + memcpy(cpustats + handle->cpustats_size, buf, size); + handle->cpustats_size += size; + cpustats[handle->cpustats_size] = 0; + handle->cpustats = cpustats; + break; + case TRACECMD_OPTION_BUFFER: + case TRACECMD_OPTION_BUFFER_TEXT: + ret = handle_buffer_option(handle, option, buf, size); + if (ret < 0) + goto out; + break; + case TRACECMD_OPTION_TRACECLOCK: + tracecmd_parse_trace_clock(handle, buf, size); + if (!handle->ts2secs) + handle->use_trace_clock = true; + break; + case TRACECMD_OPTION_UNAME: + handle->uname = strdup(buf); + break; + case TRACECMD_OPTION_VERSION: + handle->version = strdup(buf); + break; + case TRACECMD_OPTION_HOOK: + hook = tracecmd_create_event_hook(buf); + hook->next = handle->hooks; + handle->hooks = hook; + break; + case TRACECMD_OPTION_CPUCOUNT: + cpus = *(int *)buf; + handle->cpus = tep_read_number(handle->pevent, &cpus, 4); + if (handle->cpus > handle->max_cpu) + handle->max_cpu = handle->cpus; + tep_set_cpus(handle->pevent, handle->cpus); + break; + case TRACECMD_OPTION_PROCMAPS: + if (buf[size-1] == '\0') + trace_pid_map_load(handle, buf); + break; + case TRACECMD_OPTION_TRACEID: + if (size < 8) + break; + handle->trace_id = tep_read_number(handle->pevent, + buf, 8); + break; + case TRACECMD_OPTION_GUEST: + trace_guest_load(handle, buf, size); + break; + case TRACECMD_OPTION_TSC2NSEC: + if (size < 16 || (handle->flags & TRACECMD_FL_RAW_TS)) + break; + handle->tsc_calc.mult = tep_read_number(handle->pevent, + buf, 4); + handle->tsc_calc.shift = tep_read_number(handle->pevent, + buf + 4, 4); + handle->tsc_calc.offset = tep_read_number(handle->pevent, + buf + 8, 8); + break; + case TRACECMD_OPTION_HEADER_INFO: + case TRACECMD_OPTION_FTRACE_EVENTS: + case TRACECMD_OPTION_EVENT_FORMATS: + case TRACECMD_OPTION_KALLSYMS: + case TRACECMD_OPTION_PRINTK: + case TRACECMD_OPTION_CMDLINES: + if (size < 8) + break; + section_add_or_update(handle, option, -1, + tep_read_number(handle->pevent, buf, 8), 0); + break; + case TRACECMD_OPTION_DONE: + if (compress) + in_uncompress_reset(handle); + ret = handle_option_done(handle, buf, size); + free(buf); + return ret; + + default: + tracecmd_warning("unknown option %d", option); + break; + } + + free(buf); + + } + + ret = 0; + +out: + if (compress) + in_uncompress_reset(handle); + return ret; +} + +static int read_options_type(struct tracecmd_input *handle) +{ + char buf[10]; + + if (CHECK_READ_STATE(handle, TRACECMD_FILE_CPU_LATENCY)) + return 0; + + if (do_read_check(handle, buf, 10)) + return -1; + + /* check if this handles options */ + if (strncmp(buf, "options", 7) == 0) { + if (handle_options(handle) < 0) + return -1; + handle->file_state = TRACECMD_FILE_OPTIONS; + if (do_read_check(handle, buf, 10)) + return -1; + } + + /* + * Check if this is a latency report or flyrecord. + */ + if (strncmp(buf, "latency", 7) == 0) + handle->file_state = TRACECMD_FILE_CPU_LATENCY; + else if (strncmp(buf, "flyrecord", 9) == 0) + handle->file_state = TRACECMD_FILE_CPU_FLYRECORD; + else + return -1; + + return 0; +} + +int tracecmd_latency_data_read(struct tracecmd_input *handle, char **buf, size_t *size) +{ + struct cpu_zdata *zdata = &handle->latz; + void *data; + int rsize; + int fd = -1; + int id; + + if (!handle || !buf || !size) + return -1; + if (handle->file_state != TRACECMD_FILE_CPU_LATENCY) + return -1; + + if (!handle->cpu_compressed) { + fd = handle->fd; + } else if (!handle->read_zpage) { + if (zdata->fd < 0) + return -1; + fd = zdata->fd; + } + + /* Read data from a file */ + if (fd >= 0) { + if (!(*buf)) { + *size = BUFSIZ; + *buf = malloc(*size); + if (!(*buf)) + return -1; + } + return do_read_fd(fd, *buf, *size); + } + + /* Uncompress data in memory */ + if (zdata->last_chunk >= zdata->count) + return 0; + + id = zdata->last_chunk; + if (!*buf || *size < zdata->chunks[id].size) { + data = realloc(*buf, zdata->chunks[id].size); + if (!data) + return -1; + *buf = data; + *size = zdata->chunks[id].size; + } + + if (tracecmd_uncompress_chunk(handle->compress, &zdata->chunks[id], *buf)) + return -1; + + rsize = zdata->chunks[id].size; + zdata->last_chunk++; + return rsize; +} + +static int init_cpu_data(struct tracecmd_input *handle) +{ + enum kbuffer_long_size long_size; + enum kbuffer_endian endian; + unsigned long long max_size = 0; + unsigned long long pages; + int cpu; + + /* We expect this to be flyrecord */ + if (handle->file_state != TRACECMD_FILE_CPU_FLYRECORD) + return -1; + + if (force_read) + handle->read_page = true; + + if (handle->long_size == 8) + long_size = KBUFFER_LSIZE_8; + else + long_size = KBUFFER_LSIZE_4; + + if (tep_is_file_bigendian(handle->pevent)) + endian = KBUFFER_ENDIAN_BIG; + else + endian = KBUFFER_ENDIAN_LITTLE; + + for (cpu = 0; cpu < handle->cpus; cpu++) { + handle->cpu_data[cpu].compress.fd = -1; + handle->cpu_data[cpu].kbuf = kbuffer_alloc(long_size, endian); + if (!handle->cpu_data[cpu].kbuf) + goto out_free; + if (tep_is_old_format(handle->pevent)) + kbuffer_set_old_format(handle->cpu_data[cpu].kbuf); + + if (handle->cpu_data[cpu].file_size > max_size) + max_size = handle->cpu_data[cpu].file_size; + } + + /* Calculate about a meg of pages for buffering */ + pages = handle->page_size ? max_size / handle->page_size : 0; + if (!pages) + pages = 1; + pages = normalize_size(pages); + handle->page_map_size = handle->page_size * pages; + if (handle->page_map_size < handle->page_size) + handle->page_map_size = handle->page_size; + + + for (cpu = 0; cpu < handle->cpus; cpu++) { + if (init_cpu(handle, cpu)) + goto out_free; + } + + return 0; + + out_free: + for ( ; cpu >= 0; cpu--) { + free_page(handle, cpu); + kbuffer_free(handle->cpu_data[cpu].kbuf); + handle->cpu_data[cpu].kbuf = NULL; + } + return -1; +} + +int init_latency_data(struct tracecmd_input *handle) +{ + unsigned long long wsize; + int ret; + + if (!handle->cpu_compressed) + return 0; + + if (handle->read_zpage) { + handle->latz.count = tracecmd_load_chunks_info(handle->compress, &handle->latz.chunks); + if (handle->latz.count < 0) + return -1; + } else { + strcpy(handle->latz.file, COMPR_TEMP_FILE); + handle->latz.fd = mkstemp(handle->latz.file); + if (handle->latz.fd < 0) + return -1; + + ret = tracecmd_uncompress_copy_to(handle->compress, handle->latz.fd, NULL, &wsize); + if (ret) + return -1; + + lseek64(handle->latz.fd, 0, SEEK_SET); + } + + return 0; +} + +static int init_buffer_cpu_data(struct tracecmd_input *handle, struct input_buffer_instance *buffer) +{ + unsigned long long offset; + unsigned long long size; + unsigned short id, flags; + int cpu; + + if (handle->cpu_data) + return -1; + + if (lseek64(handle->fd, buffer->offset, SEEK_SET) == (off_t)-1) + return -1; + if (read_section_header(handle, &id, &flags, NULL, NULL)) + return -1; + if (flags & TRACECMD_SEC_FL_COMPRESS) + handle->cpu_compressed = true; + if (buffer->latency) { + handle->file_state = TRACECMD_FILE_CPU_LATENCY; + return init_latency_data(handle) == 0 ? 1 : -1; + } + handle->file_state = TRACECMD_FILE_CPU_FLYRECORD; + handle->cpus = buffer->cpus; + if (handle->max_cpu < handle->cpus) + handle->max_cpu = handle->cpus; + + handle->cpu_data = calloc(handle->cpus, sizeof(*handle->cpu_data)); + if (!handle->cpu_data) + return -1; + + for (cpu = 0; cpu < handle->cpus; cpu++) { + handle->cpu_data[cpu].cpu = buffer->cpu_data[cpu].cpu; + offset = buffer->cpu_data[cpu].offset; + size = buffer->cpu_data[cpu].size; + handle->cpu_data[cpu].file_offset = offset; + handle->cpu_data[cpu].file_size = size; + if (size && (offset + size > handle->total_file_size)) { + /* this happens if the file got truncated */ + printf("File possibly truncated. " + "Need at least %llu, but file size is %zu.\n", + offset + size, handle->total_file_size); + errno = EINVAL; + return -1; + } + } + + return init_cpu_data(handle); +} + +static int read_cpu_data(struct tracecmd_input *handle) +{ + unsigned long long size; + int cpus; + int cpu; + + /* + * Check if this is a latency report or not. + */ + if (handle->file_state == TRACECMD_FILE_CPU_LATENCY) + return 1; + + /* We expect this to be flyrecord */ + if (handle->file_state != TRACECMD_FILE_CPU_FLYRECORD) + return -1; + + cpus = handle->cpus; + + handle->cpu_data = malloc(sizeof(*handle->cpu_data) * handle->cpus); + if (!handle->cpu_data) + return -1; + memset(handle->cpu_data, 0, sizeof(*handle->cpu_data) * handle->cpus); + + for (cpu = 0; cpu < handle->cpus; cpu++) { + unsigned long long offset; + + handle->cpu_data[cpu].cpu = cpu; + read8(handle, &offset); + read8(handle, &size); + handle->cpu_data[cpu].file_offset = offset; + handle->cpu_data[cpu].file_size = size; + if (size && (offset + size > handle->total_file_size)) { + /* this happens if the file got truncated */ + printf("File possibly truncated. " + "Need at least %llu, but file size is %zu.\n", + offset + size, handle->total_file_size); + errno = EINVAL; + return -1; + } + } + + /* + * It is possible that an option changed the number of CPUs. + * If that happened, then there's "empty" cpu data saved for + * backward compatibility. + */ + if (cpus < handle->cpus) { + unsigned long long ignore; + int once = 0; + + read8(handle, &ignore); /* offset */ + read8(handle, &ignore); /* size */ + if (ignore != 0) { + if (!once) { + tracecmd_warning("ignored CPU data not zero size"); + once++; + } + } + } + + return init_cpu_data(handle); +} + +static int read_data_and_size(struct tracecmd_input *handle, + char **data, unsigned long long *size) +{ + if (read8(handle, size) < 0) + return -1; + *data = malloc(*size + 1); + if (!*data) + return -1; + if (do_read_check(handle, *data, *size)) { + free(*data); + return -1; + } + + return 0; +} + +static int read_and_parse_cmdlines(struct tracecmd_input *handle) +{ + struct tep_handle *pevent = handle->pevent; + unsigned long long size; + char *cmdlines; + + if (CHECK_READ_STATE(handle, TRACECMD_FILE_CMD_LINES)) + return 0; + + if (!HAS_SECTIONS(handle)) + section_add_or_update(handle, TRACECMD_OPTION_CMDLINES, 0, 0, + lseek64(handle->fd, 0, SEEK_CUR)); + + + if (read_data_and_size(handle, &cmdlines, &size) < 0) + return -1; + cmdlines[size] = 0; + tep_parse_saved_cmdlines(pevent, cmdlines); + free(cmdlines); + + handle->file_state = TRACECMD_FILE_CMD_LINES; + + return 0; +} + +static void extract_trace_clock(struct tracecmd_input *handle, char *line) +{ + char *clock = NULL; + char *next = NULL; + char *data; + + data = strtok_r(line, "[]", &next); + sscanf(data, "%ms", &clock); + /* TODO: report if it fails to allocate */ + handle->trace_clock = clock; + + if (!clock) + return; + + /* Clear usecs if raw timestamps are requested */ + if (handle->flags & TRACECMD_FL_RAW_TS) + handle->flags &= ~TRACECMD_FL_IN_USECS; + + /* Clear usecs if not one of the specified clocks */ + if (strcmp(clock, "local") && strcmp(clock, "global") && + strcmp(clock, "uptime") && strcmp(clock, "perf") && + strncmp(clock, "mono", 4) && strcmp(clock, TSCNSEC_CLOCK) && + strcmp(clock, "tai")) + handle->flags &= ~TRACECMD_FL_IN_USECS; +} + +void tracecmd_parse_trace_clock(struct tracecmd_input *handle, + char *file, int size __maybe_unused) +{ + char *line; + char *next = NULL; + + line = strtok_r(file, " ", &next); + while (line) { + /* current trace_clock is shown as "[local]". */ + if (*line == '[') + return extract_trace_clock(handle, line); + line = strtok_r(NULL, " ", &next); + } +} + +static int read_and_parse_trace_clock(struct tracecmd_input *handle, + struct tep_handle *pevent) +{ + unsigned long long size; + char *trace_clock; + + if (read_data_and_size(handle, &trace_clock, &size) < 0) + return -1; + trace_clock[size] = 0; + tracecmd_parse_trace_clock(handle, trace_clock, size); + free(trace_clock); + return 0; +} + +static int init_data_v6(struct tracecmd_input *handle) +{ + struct tep_handle *pevent = handle->pevent; + int ret; + + ret = read_cpu_data(handle); + if (ret < 0) + return ret; + + if (handle->use_trace_clock) { + /* + * There was a bug in the original setting of + * the trace_clock file which let it get + * corrupted. If it fails to read, force local + * clock. + */ + if (read_and_parse_trace_clock(handle, pevent) < 0) { + char clock[] = "[local]"; + tracecmd_warning("File has trace_clock bug, using local clock"); + tracecmd_parse_trace_clock(handle, clock, 8); + } + } + return ret; +} + +static int init_data(struct tracecmd_input *handle) +{ + return init_buffer_cpu_data(handle, &handle->top_buffer); +} + +/** + * tracecmd_init_data - prepare reading the data from trace.dat + * @handle: input handle for the trace.dat file + * + * This prepares reading the data from trace.dat. This is called + * after tracecmd_read_headers() and before tracecmd_read_data(). + */ +int tracecmd_init_data(struct tracecmd_input *handle) +{ + int ret; + + if (!HAS_SECTIONS(handle)) + ret = init_data_v6(handle); + else + ret = init_data(handle); + tracecmd_blk_hack(handle); + + return ret; +} + +/** + * tracecmd_make_pipe - Have the handle read a pipe instead of a file + * @handle: input handle to read from a pipe + * @cpu: the cpu that the pipe represents + * @fd: the read end of the pipe + * @cpus: the total number of cpus for this handle + * + * In order to stream data from the binary trace files and produce + * output or analyze the data, a tracecmd_input descriptor needs to + * be created, and then converted into a form that can act on a + * pipe. + * + * Note, there are limitations to what this descriptor can do. + * Most notibly, it can not read backwards. Once a page is read + * it can not be read at a later time (except if a record is attached + * to it and is holding the page ref). + * + * It is expected that the handle has already been created and + * tracecmd_read_headers() has run on it. + */ +int tracecmd_make_pipe(struct tracecmd_input *handle, int cpu, int fd, int cpus) +{ + enum kbuffer_long_size long_size; + enum kbuffer_endian endian; + + handle->read_page = true; + handle->use_pipe = true; + + if (!handle->cpus) { + handle->cpus = cpus; + handle->cpu_data = malloc(sizeof(*handle->cpu_data) * handle->cpus); + if (!handle->cpu_data) + return -1; + } + + if (cpu >= handle->cpus) + return -1; + + + if (handle->long_size == 8) + long_size = KBUFFER_LSIZE_8; + else + long_size = KBUFFER_LSIZE_4; + + if (tep_is_file_bigendian(handle->pevent)) + endian = KBUFFER_ENDIAN_BIG; + else + endian = KBUFFER_ENDIAN_LITTLE; + + memset(&handle->cpu_data[cpu], 0, sizeof(handle->cpu_data[cpu])); + handle->cpu_data[cpu].pipe_fd = fd; + handle->cpu_data[cpu].cpu = cpu; + + handle->cpu_data[cpu].kbuf = kbuffer_alloc(long_size, endian); + if (!handle->cpu_data[cpu].kbuf) + return -1; + if (tep_is_old_format(handle->pevent)) + kbuffer_set_old_format(handle->cpu_data[cpu].kbuf); + + handle->cpu_data[cpu].file_offset = 0; + handle->cpu_data[cpu].file_size = -1; + + init_cpu(handle, cpu); + + return 0; +} + +/** + * tracecmd_print_events - print the events that are stored in trace.dat + * @handle: input handle for the trace.dat file + * @regex: regex of events to print (NULL is all events) + * + * This is a debugging routine to print out the events that + * are stored in a given trace.dat file. + */ +void tracecmd_print_events(struct tracecmd_input *handle, const char *regex) +{ + if (!regex) + regex = ".*"; + + if (!HAS_SECTIONS(handle)) + read_headers_v6(handle, TRACECMD_FILE_ALL_EVENTS, regex); + + read_headers(handle, regex); +} + +/* Show the cpu data stats */ +static void show_cpu_stats(struct tracecmd_input *handle) +{ + struct cpu_data *cpu_data; + int i; + + for (i = 0; i < handle->cpus; i++) { + cpu_data = &handle->cpu_data[i]; + printf("CPU%d data recorded at offset=0x%llx\n", + i, cpu_data->file_offset); + printf(" %lld bytes in size\n", cpu_data->file_size); + } +} + +/** + * tracecmd_print_stats - prints the stats recorded in the options. + * @handle: input handle for the trace.dat file + * + * Looks for the option TRACECMD_OPTION_CPUSTAT and prints out what's + * stored there, if it is found. Otherwise it prints that none were found. + */ +void tracecmd_print_stats(struct tracecmd_input *handle) +{ + if (handle->cpustats) + printf("%s\n", handle->cpustats); + else + printf(" No stats in this file\n"); + + show_cpu_stats(handle); +} + +/** + * tracecmd_print_uname - prints the recorded uname if it was recorded + * @handle: input handle for the trace.dat file + * + * Looks for the option TRACECMD_OPTION_UNAME and prints out what's + * stored there, if it is found. Otherwise it prints that none were found. + */ +void tracecmd_print_uname(struct tracecmd_input *handle) +{ + if (handle->uname) + printf("%s\n", handle->uname); + else + printf(" uname was not recorded in this file\n"); +} + +/** + * tracecmd_print_uname - prints the recorded uname if it was recorded + * @handle: input handle for the trace.dat file + * + * Looks for the option TRACECMD_OPTION_VERSION and prints out what's + * stored there, if it is found. Otherwise it prints that none were found. + */ +void tracecmd_print_version(struct tracecmd_input *handle) +{ + if (handle->version) + printf("%s\n", handle->version); + else + printf(" version was not recorded in this file\n"); +} + +/** + * tracecmd_hooks - return the event hooks that were used in record + * @handle: input handle for the trace.dat file + * + * If trace-cmd record used -H to save hooks, they are parsed and + * presented as hooks here. + * + * Returns the hook list (do not free it, they are freed on close) + */ +struct hook_list *tracecmd_hooks(struct tracecmd_input *handle) +{ + return handle->hooks; +} + +static int init_metadata_strings(struct tracecmd_input *handle, int size) +{ + char *tmp; + + tmp = realloc(handle->strings, handle->strings_size + size); + if (!tmp) + return -1; + + handle->strings = tmp; + if (do_read_check(handle, handle->strings + handle->strings_size, size)) + return -1; + + handle->strings_size += size; + + return 0; +} + +static int read_metadata_strings(struct tracecmd_input *handle) +{ + unsigned short flags; + int found = 0; + unsigned short id; + unsigned int csize, rsize; + unsigned long long size; + off64_t offset; + + offset = lseek64(handle->fd, 0, SEEK_CUR); + do { + if (read_section_header(handle, &id, &flags, &size, NULL)) + break; + if (id == TRACECMD_OPTION_STRINGS) { + found++; + if ((flags & TRACECMD_SEC_FL_COMPRESS)) { + read4(handle, &csize); + read4(handle, &rsize); + do_lseek(handle, -8, SEEK_CUR); + if (in_uncompress_block(handle)) + break; + } else { + rsize = size; + } + init_metadata_strings(handle, rsize); + if (flags & TRACECMD_SEC_FL_COMPRESS) + in_uncompress_reset(handle); + } else { + if (lseek64(handle->fd, size, SEEK_CUR) == (off_t)-1) + break; + } + } while (1); + + if (lseek64(handle->fd, offset, SEEK_SET) == (off_t)-1) + return -1; + + return found ? 0 : -1; +} + +/** + * tracecmd_alloc_fd - create a tracecmd_input handle from a file descriptor + * @fd: the file descriptor for the trace.dat file + * @flags: bitmask of enum tracecmd_open_flags + * + * Allocate a tracecmd_input handle from a file descriptor and open the + * file. This tests if the file is of trace-cmd format and allocates + * a parse event descriptor. + * + * The returned pointer is not ready to be read yet. A tracecmd_read_headers() + * and tracecmd_init_data() still need to be called on the descriptor. + * + * Unless you know what you are doing with this, you want to use + * tracecmd_open_fd() instead. + */ +struct tracecmd_input *tracecmd_alloc_fd(int fd, int flags) +{ + struct tracecmd_input *handle; + char test[] = TRACECMD_MAGIC; + unsigned int page_size; + size_t offset; + char *version = NULL; + char *zver = NULL; + char *zname = NULL; + char buf[BUFSIZ]; + unsigned long ver; + + handle = malloc(sizeof(*handle)); + if (!handle) + return NULL; + memset(handle, 0, sizeof(*handle)); + + handle->fd = fd; + handle->ref = 1; + handle->latz.fd = -1; + /* By default, use usecs, unless told otherwise */ + handle->flags |= TRACECMD_FL_IN_USECS; + +#ifdef INMEMORY_DECOMPRESS + handle->read_zpage = 1; +#endif + if (do_read_check(handle, buf, 3)) + goto failed_read; + + if (memcmp(buf, test, 3) != 0) + goto failed_read; + + if (do_read_check(handle, buf, 7)) + goto failed_read; + if (memcmp(buf, "tracing", 7) != 0) + goto failed_read; + + version = read_string(handle); + if (!version) + goto failed_read; + tracecmd_info("version = %s", version); + ver = strtol(version, NULL, 10); + if (!ver && errno) + goto failed_read; + if (!tracecmd_is_version_supported(ver)) { + tracecmd_warning("Unsupported file version %lu", ver); + goto failed_read; + } + handle->file_version = ver; + free(version); + version = NULL; + + if (handle->file_version >= FILE_VERSION_SECTIONS) + handle->flags |= TRACECMD_FL_SECTIONED; + if (handle->file_version >= FILE_VERSION_COMPRESSION) + handle->flags |= TRACECMD_FL_COMPRESSION; + + if (do_read_check(handle, buf, 1)) + goto failed_read; + + handle->pevent = tep_alloc(); + if (!handle->pevent) + goto failed_read; + + /* register default ftrace functions first */ + if (!(flags & TRACECMD_FL_LOAD_NO_PLUGINS) && + !(flags & TRACECMD_FL_LOAD_NO_SYSTEM_PLUGINS)) + tracecmd_ftrace_overrides(handle, &handle->finfo); + + handle->plugin_list = trace_load_plugins(handle->pevent, flags); + + tep_set_file_bigendian(handle->pevent, buf[0]); + tep_set_local_bigendian(handle->pevent, tracecmd_host_bigendian()); + + do_read_check(handle, buf, 1); + handle->long_size = buf[0]; + tep_set_long_size(handle->pevent, handle->long_size); + + read4(handle, &page_size); + handle->page_size = page_size; + handle->next_offset = page_size; + + offset = lseek64(handle->fd, 0, SEEK_CUR); + handle->total_file_size = lseek64(handle->fd, 0, SEEK_END); + lseek64(handle->fd, offset, SEEK_SET); + + if (HAS_COMPRESSION(handle)) { + zname = read_string(handle); + if (!zname) + goto failed_read; + + zver = read_string(handle); + if (!zver) + goto failed_read; + + if (strcmp(zname, "none") == 0) { + handle->read_zpage = false; + handle->flags &= ~TRACECMD_FL_COMPRESSION; + } else { + handle->compress = tracecmd_compress_alloc(zname, zver, + handle->fd, + handle->pevent, NULL); + if (!handle->compress) { + tracecmd_warning("Unsupported file compression %s %s", zname, zver); + goto failed_read; + } + } + + free(zname); + free(zver); + } + + if (HAS_SECTIONS(handle)) { + if (read8(handle, &(handle->options_start))) { + tracecmd_warning("Filed to read the offset of the first option section"); + goto failed_read; + } + read_metadata_strings(handle); + } + + handle->file_state = TRACECMD_FILE_INIT; + + return handle; + + failed_read: + free(version); + free(zname); + free(zver); + free(handle); + + return NULL; +} + +/** + * tracecmd_alloc_fd - create a tracecmd_input handle from a file name + * @file: the file name of the file that is of tracecmd data type. + * @flags: bitmask of enum tracecmd_open_flags + * + * Allocate a tracecmd_input handle from a given file name and open the + * file. This tests if the file is of trace-cmd format and allocates + * a parse event descriptor. + * + * The returned pointer is not ready to be read yet. A tracecmd_read_headers() + * and tracecmd_init_data() still need to be called on the descriptor. + * + * Unless you know what you are doing with this, you want to use + * tracecmd_open() instead. + */ +struct tracecmd_input *tracecmd_alloc(const char *file, int flags) +{ + int fd; + + fd = open(file, O_RDONLY); + if (fd < 0) + return NULL; + + return tracecmd_alloc_fd(fd, flags); +} + +/** + * tracecmd_open_fd - create a tracecmd_handle from the trace.dat file descriptor + * @fd: the file descriptor for the trace.dat file + * @flags: bitmask of enum tracecmd_open_flags + */ +struct tracecmd_input *tracecmd_open_fd(int fd, int flags) +{ + struct tracecmd_input *handle; + int ret; + + handle = tracecmd_alloc_fd(fd, flags); + if (!handle) + return NULL; + + if (tracecmd_read_headers(handle, 0) < 0) + goto fail; + + if ((ret = tracecmd_init_data(handle)) < 0) + goto fail; + + return handle; + +fail: + tracecmd_close(handle); + return NULL; +} + +/** + * tracecmd_open - create a tracecmd_handle from a given file + * @file: the file name of the file that is of tracecmd data type. + * @flags: bitmask of enum tracecmd_open_flags + */ +struct tracecmd_input *tracecmd_open(const char *file, int flags) +{ + int fd; + + fd = open(file, O_RDONLY); + if (fd < 0) + return NULL; + + return tracecmd_open_fd(fd, flags); +} + +/** + * tracecmd_open_head - create a tracecmd_handle from a given file, read + * and parse only the trace headers from the file + * @file: the file name of the file that is of tracecmd data type. + * @flags: bitmask of enum tracecmd_open_flags + */ +struct tracecmd_input *tracecmd_open_head(const char *file, int flags) +{ + struct tracecmd_input *handle; + int fd; + + fd = open(file, O_RDONLY); + if (fd < 0) + return NULL; + + handle = tracecmd_alloc_fd(fd, flags); + if (!handle) + return NULL; + + if (tracecmd_read_headers(handle, 0) < 0) + goto fail; + + return handle; + +fail: + tracecmd_close(handle); + return NULL; +} + +/** + * tracecmd_ref - add a reference to the handle + * @handle: input handle for the trace.dat file + * + * Some applications may share a handle between parts of + * the application. Let those parts add reference counters + * to the handle, and the last one to close it will free it. + */ +void tracecmd_ref(struct tracecmd_input *handle) +{ + if (!handle) + return; + + handle->ref++; +} + +static inline void free_buffer(struct input_buffer_instance *buf) +{ + free(buf->name); + free(buf->clock); + free(buf->cpu_data); +} + +/** + * tracecmd_close - close and free the trace.dat handle + * @handle: input handle for the trace.dat file + * + * Close the file descriptor of the handle and frees + * the resources allocated by the handle. + */ +void tracecmd_close(struct tracecmd_input *handle) +{ + struct zchunk_cache *cache; + struct file_section *del_sec; + struct cpu_data *cpu_data; + struct page_map *page_map, *n; + int cpu; + int i; + + if (!handle) + return; + + if (handle->ref <= 0) { + tracecmd_warning("tracecmd: bad ref count on handle"); + return; + } + + if (--handle->ref) + return; + + for (cpu = 0; cpu < handle->cpus; cpu++) { + /* The tracecmd_peek_data may have cached a record */ + free_next(handle, cpu); + free_page(handle, cpu); + if (handle->cpu_data) { + cpu_data = &handle->cpu_data[cpu]; + if (cpu_data->kbuf) { + kbuffer_free(cpu_data->kbuf); + if (cpu_data->page_map) + free_page_map(cpu_data->page_map); + + if (cpu_data->page_cnt) + tracecmd_warning("%d pages still allocated on cpu %d%s", + cpu_data->page_cnt, cpu, + show_records(cpu_data->pages, + cpu_data->nr_pages)); + free(cpu_data->pages); + } + if (cpu_data->compress.fd >= 0) { + close(cpu_data->compress.fd); + unlink(cpu_data->compress.file); + } + while (!list_empty(&cpu_data->compress.cache)) { + cache = container_of(cpu_data->compress.cache.next, + struct zchunk_cache, list); + list_del(&cache->list); + free(cache->map); + free(cache); + } + free(cpu_data->compress.chunks); + list_for_each_entry_safe(page_map, n, &cpu_data->page_maps, list) { + list_del(&page_map->list); + free(page_map); + } + } + } + + free(handle->cpustats); + free(handle->cpu_data); + free(handle->uname); + free(handle->trace_clock); + free(handle->strings); + free(handle->version); + close(handle->fd); + free(handle->latz.chunks); + if (handle->latz.fd >= 0) { + close(handle->latz.fd); + unlink(handle->latz.file); + } + while (handle->sections) { + del_sec = handle->sections; + handle->sections = handle->sections->next; + free(del_sec); + } + + free_buffer(&handle->top_buffer); + for (i = 0; i < handle->nr_buffers; i++) + free_buffer(&handle->buffers[i]); + free(handle->buffers); + + tracecmd_free_hooks(handle->hooks); + handle->hooks = NULL; + + trace_pid_map_free(handle->pid_maps); + handle->pid_maps = NULL; + + trace_tsync_offset_free(&handle->host); + trace_guests_free(handle); + + if (handle->flags & TRACECMD_FL_BUFFER_INSTANCE) + tracecmd_close(handle->parent); + else { + /* Only main handle frees plugins, pevent and compression context */ + tracecmd_compress_destroy(handle->compress); + tep_unload_plugins(handle->plugin_list, handle->pevent); + tep_free(handle->pevent); + } + free(handle); +} + +static int read_copy_size8(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle, unsigned long long *size) +{ + /* read size */ + if (do_read_check(in_handle, size, 8)) + return -1; + + if (do_write_check(out_handle, size, 8)) + return -1; + + *size = tep_read_number(in_handle->pevent, size, 8); + return 0; +} + +static int read_copy_size4(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle, + unsigned int *size) +{ + /* read size */ + if (do_read_check(in_handle, size, 4)) + return -1; + + if (do_write_check(out_handle, size, 4)) + return -1; + + *size = tep_read_number(in_handle->pevent, size, 4); + return 0; +} + +static int read_copy_data(struct tracecmd_input *in_handle, + unsigned long long size, + struct tracecmd_output *out_handle) +{ + char *buf; + + buf = malloc(size); + if (!buf) + return -1; + if (do_read_check(in_handle, buf, size)) + goto failed_read; + + if (do_write_check(out_handle, buf, size)) + goto failed_read; + + free(buf); + + return 0; + + failed_read: + free(buf); + return -1; +} + + +static bool check_in_state(struct tracecmd_input *handle, int new_state) +{ + return check_file_state(handle->file_version, handle->file_state, new_state); +} + +static int copy_header_files(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle) +{ + bool compress = out_check_compression(out_handle); + struct file_section *sec; + unsigned long long offset; + unsigned long long size; + + if (!check_in_state(in_handle, TRACECMD_FILE_HEADERS) || + !check_out_state(out_handle, TRACECMD_FILE_HEADERS)) + return -1; + + sec = section_open(in_handle, TRACECMD_OPTION_HEADER_INFO); + if (!sec) + return -1; + + offset = out_write_section_header(out_handle, TRACECMD_OPTION_HEADER_INFO, + "headers", TRACECMD_SEC_FL_COMPRESS, true); + out_compression_start(out_handle, compress); + + /* "header_page" */ + if (read_copy_data(in_handle, 12, out_handle) < 0) + goto error; + + if (read_copy_size8(in_handle, out_handle, &size) < 0) + goto error; + + if (read_copy_data(in_handle, size, out_handle) < 0) + goto error; + + /* "header_event" */ + if (read_copy_data(in_handle, 13, out_handle) < 0) + goto error; + + if (read_copy_size8(in_handle, out_handle, &size) < 0) + goto error; + + if (read_copy_data(in_handle, size, out_handle) < 0) + goto error; + + in_handle->file_state = TRACECMD_FILE_HEADERS; + if (out_compression_end(out_handle, compress)) + goto error; + + out_set_file_state(out_handle, in_handle->file_state); + section_close(in_handle, sec); + + if (out_update_section_header(out_handle, offset)) + goto error; + + return 0; +error: + out_compression_reset(out_handle, compress); + section_close(in_handle, sec); + return -1; +} + +static int copy_ftrace_files(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) +{ + bool compress = out_check_compression(out_handle); + struct file_section *sec; + unsigned long long offset; + unsigned long long size; + unsigned int count; + unsigned int i; + + if (!check_in_state(in_handle, TRACECMD_FILE_FTRACE_EVENTS) || + !check_out_state(out_handle, TRACECMD_FILE_FTRACE_EVENTS)) + return -1; + + sec = section_open(in_handle, TRACECMD_OPTION_FTRACE_EVENTS); + if (!sec) + return -1; + offset = out_write_section_header(out_handle, TRACECMD_OPTION_FTRACE_EVENTS, + "ftrace events", TRACECMD_SEC_FL_COMPRESS, true); + + out_compression_start(out_handle, compress); + + if (read_copy_size4(in_handle, out_handle, &count) < 0) + goto error; + + for (i = 0; i < count; i++) { + + if (read_copy_size8(in_handle, out_handle, &size) < 0) + goto error; + + if (read_copy_data(in_handle, size, out_handle) < 0) + goto error; + } + + in_handle->file_state = TRACECMD_FILE_FTRACE_EVENTS; + if (out_compression_end(out_handle, compress)) + goto error; + + out_set_file_state(out_handle, in_handle->file_state); + + section_close(in_handle, sec); + + if (out_update_section_header(out_handle, offset)) + goto error; + + return 0; +error: + out_compression_reset(out_handle, compress); + section_close(in_handle, sec); + return -1; +} + +static int copy_event_files(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) +{ + bool compress = out_check_compression(out_handle); + struct file_section *sec; + unsigned long long offset; + unsigned long long size; + char *system; + unsigned int systems; + unsigned int count; + unsigned int i,x; + + if (!check_in_state(in_handle, TRACECMD_FILE_ALL_EVENTS) || + !check_out_state(out_handle, TRACECMD_FILE_ALL_EVENTS)) + return -1; + + sec = section_open(in_handle, TRACECMD_OPTION_EVENT_FORMATS); + if (!sec) + return -1; + offset = out_write_section_header(out_handle, TRACECMD_OPTION_EVENT_FORMATS, + "events format", TRACECMD_SEC_FL_COMPRESS, true); + + out_compression_start(out_handle, compress); + + if (read_copy_size4(in_handle, out_handle, &systems) < 0) + goto error; + + for (i = 0; i < systems; i++) { + system = read_string(in_handle); + if (!system) + goto error; + if (do_write_check(out_handle, system, strlen(system) + 1)) { + free(system); + goto error; + } + free(system); + + if (read_copy_size4(in_handle, out_handle, &count) < 0) + goto error; + + for (x=0; x < count; x++) { + if (read_copy_size8(in_handle, out_handle, &size) < 0) + goto error; + + if (read_copy_data(in_handle, size, out_handle) < 0) + goto error; + } + } + + in_handle->file_state = TRACECMD_FILE_ALL_EVENTS; + if (out_compression_end(out_handle, compress)) + goto error; + + out_set_file_state(out_handle, in_handle->file_state); + + section_close(in_handle, sec); + + if (out_update_section_header(out_handle, offset)) + goto error; + + return 0; +error: + out_compression_reset(out_handle, compress); + section_close(in_handle, sec); + return -1; +} + +static int copy_proc_kallsyms(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) +{ + bool compress = out_check_compression(out_handle); + struct file_section *sec; + unsigned long long offset; + unsigned int size; + + if (!check_in_state(in_handle, TRACECMD_FILE_KALLSYMS) || + !check_out_state(out_handle, TRACECMD_FILE_KALLSYMS)) + return -1; + + sec = section_open(in_handle, TRACECMD_OPTION_KALLSYMS); + if (!sec) + return -1; + offset = out_write_section_header(out_handle, TRACECMD_OPTION_KALLSYMS, + "kallsyms", TRACECMD_SEC_FL_COMPRESS, true); + + out_compression_start(out_handle, compress); + if (read_copy_size4(in_handle, out_handle, &size) < 0) + goto error; + + if (!size) + goto out; /* OK? */ + + if (read_copy_data(in_handle, size, out_handle) < 0) + goto error; +out: + in_handle->file_state = TRACECMD_FILE_KALLSYMS; + if (out_compression_end(out_handle, compress)) + goto error; + + out_set_file_state(out_handle, in_handle->file_state); + + section_close(in_handle, sec); + + if (out_update_section_header(out_handle, offset)) + goto error; + + return 0; +error: + out_compression_reset(out_handle, compress); + section_close(in_handle, sec); + return -1; +} + +static int copy_ftrace_printk(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) +{ + bool compress = out_check_compression(out_handle); + struct file_section *sec; + unsigned long long offset; + unsigned int size; + + if (!check_in_state(in_handle, TRACECMD_FILE_PRINTK) || + !check_out_state(out_handle, TRACECMD_FILE_PRINTK)) + return -1; + + sec = section_open(in_handle, TRACECMD_OPTION_PRINTK); + if (!sec) + return -1; + + offset = out_write_section_header(out_handle, TRACECMD_OPTION_PRINTK, + "printk", TRACECMD_SEC_FL_COMPRESS, true); + + out_compression_start(out_handle, compress); + + if (read_copy_size4(in_handle, out_handle, &size) < 0) + goto error; + + if (!size) + goto out; /* OK? */ + + if (read_copy_data(in_handle, size, out_handle) < 0) + goto error; + +out: + in_handle->file_state = TRACECMD_FILE_PRINTK; + if (out_compression_end(out_handle, compress)) + goto error; + + out_set_file_state(out_handle, in_handle->file_state); + + section_close(in_handle, sec); + + if (out_update_section_header(out_handle, offset)) + goto error; + + return 0; +error: + out_compression_reset(out_handle, compress); + section_close(in_handle, sec); + return -1; +} + +static int copy_command_lines(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) +{ + bool compress = out_check_compression(out_handle); + struct file_section *sec; + unsigned long long offset; + unsigned long long size; + + if (!check_in_state(in_handle, TRACECMD_FILE_CMD_LINES) || + !check_out_state(out_handle, TRACECMD_FILE_CMD_LINES)) + return -1; + + sec = section_open(in_handle, TRACECMD_OPTION_CMDLINES); + if (!sec) + return -1; + offset = out_write_section_header(out_handle, TRACECMD_OPTION_CMDLINES, + "command lines", TRACECMD_SEC_FL_COMPRESS, true); + + out_compression_start(out_handle, compress); + + if (read_copy_size8(in_handle, out_handle, &size) < 0) + goto error; + + if (!size) + goto out; /* OK? */ + + if (read_copy_data(in_handle, size, out_handle) < 0) + goto error; + +out: + in_handle->file_state = TRACECMD_FILE_CMD_LINES; + if (out_compression_end(out_handle, compress)) + goto error; + + out_set_file_state(out_handle, in_handle->file_state); + + section_close(in_handle, sec); + + if (out_update_section_header(out_handle, offset)) + goto error; + + return 0; +error: + out_compression_reset(out_handle, compress); + section_close(in_handle, sec); + return -1; +} + +static int copy_cpu_count(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) +{ + unsigned int cpus; + + if (!check_in_state(in_handle, TRACECMD_FILE_CPU_COUNT) || + !check_out_state(out_handle, TRACECMD_FILE_CPU_COUNT)) + return -1; + + if (!HAS_SECTIONS(in_handle)) { + if (read4(in_handle, &cpus)) + return -1; + } else { + cpus = in_handle->max_cpu; + } + + if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) { + cpus = tep_read_number(in_handle->pevent, &cpus, 4); + if (do_write_check(out_handle, &cpus, 4)) + return -1; + } else { + tracecmd_add_option(out_handle, TRACECMD_OPTION_CPUCOUNT, sizeof(int), &cpus); + } + + in_handle->file_state = TRACECMD_FILE_CPU_COUNT; + out_set_file_state(out_handle, in_handle->file_state); + + return 0; +} + +/** + * tracecmd_copy_headers - Copy headers from a tracecmd_input handle to a file descriptor + * @in_handle: input handle for the trace.dat file to copy from. + * @out_handle: output handle to the trace.dat file to copy to. + * @start_state: The file state to start copying from (zero for the beginnig) + * @end_state: The file state to stop at (zero for up to cmdlines) + * + * This is used to copy trace header data of a trace.dat file to a + * file descriptor. Using @start_state and @end_state it may be used + * multiple times against the input handle. + * + * NOTE: The input handle is also modified, and ends at the end + * state as well. + */ +int tracecmd_copy_headers(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle, + enum tracecmd_file_states start_state, + enum tracecmd_file_states end_state) +{ + struct file_section *sec = NULL; + int ret; + + if (!start_state) + start_state = TRACECMD_FILE_HEADERS; + if (!end_state) + end_state = TRACECMD_FILE_CMD_LINES; + + if (start_state > end_state) + return -1; + + if (end_state < TRACECMD_FILE_HEADERS) + return 0; + + if (in_handle->file_state >= start_state) { + /* Set the handle to just before the start state */ + sec = section_open(in_handle, TRACECMD_OPTION_HEADER_INFO); + if (!sec) + return -1; + /* Now that the file handle has moved, change its state */ + in_handle->file_state = TRACECMD_FILE_INIT; + } + + /* Try to bring the input up to the start state - 1 */ + ret = tracecmd_read_headers(in_handle, start_state - 1); + if (sec) + section_close(in_handle, sec); + if (ret < 0) + goto out; + + switch (start_state) { + case TRACECMD_FILE_HEADERS: + ret = copy_header_files(in_handle, out_handle); + if (ret < 0) + goto out; + + /* fallthrough */ + case TRACECMD_FILE_FTRACE_EVENTS: + /* handle's state is now updating with the copies */ + if (end_state <= in_handle->file_state) + return 0; + + ret = copy_ftrace_files(in_handle, out_handle); + if (ret < 0) + goto out; + + /* fallthrough */ + case TRACECMD_FILE_ALL_EVENTS: + if (end_state <= in_handle->file_state) + return 0; + + ret = copy_event_files(in_handle, out_handle); + if (ret < 0) + goto out; + + /* fallthrough */ + case TRACECMD_FILE_KALLSYMS: + if (end_state <= in_handle->file_state) + return 0; + + ret = copy_proc_kallsyms(in_handle, out_handle); + if (ret < 0) + goto out; + + /* fallthrough */ + case TRACECMD_FILE_PRINTK: + if (end_state <= in_handle->file_state) + return 0; + + ret = copy_ftrace_printk(in_handle, out_handle); + if (ret < 0) + goto out; + + /* fallthrough */ + case TRACECMD_FILE_CMD_LINES: + if (end_state <= in_handle->file_state) + return 0; + + ret = copy_command_lines(in_handle, out_handle); + if (ret < 0) + goto out; + + /* fallthrough */ + case TRACECMD_FILE_CPU_COUNT: + if (end_state <= in_handle->file_state) + return 0; + + ret = copy_cpu_count(in_handle, out_handle); + if (ret < 0) + goto out; + + /* fallthrough */ + default: + break; + } + + out: + return ret < 0 ? -1 : 0; +} + +int tracecmd_copy_buffer_descr(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle) +{ + int i; + + if (tracecmd_get_out_file_version(out_handle) >= FILE_VERSION_SECTIONS) + return 0; + + for (i = 0; i < in_handle->nr_buffers; i++) + tracecmd_add_buffer_info(out_handle, in_handle->buffers[i].name, 0); + + return tracecmd_write_buffer_info(out_handle); +} + +static int copy_options_recursive(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle) +{ + unsigned short id, flags = 0; + unsigned short option, en2; + unsigned long long next; + unsigned int size, en4; + bool skip; + + for (;;) { + if (do_read_check(in_handle, &option, 2)) + return -1; + + en2 = tep_read_number(in_handle->pevent, &option, 2); + + if (en2 == TRACECMD_OPTION_DONE && !HAS_SECTIONS(in_handle)) + return 0; + + /* next 4 bytes is the size of the option */ + if (do_read_check(in_handle, &size, 4)) + return -1; + + en4 = tep_read_number(in_handle->pevent, &size, 4); + if (en2 == TRACECMD_OPTION_DONE) { + /* option done v7 */ + if (en4 < 8) + return -1; + + if (read8(in_handle, &next)) + return -1; + + if (!next) + break; + + if (do_lseek(in_handle, next, SEEK_SET) == (off64_t)-1) + return -1; + + if (read_section_header(in_handle, &id, &flags, NULL, NULL)) + return -1; + + if (id != TRACECMD_OPTION_DONE) + return -1; + + if (flags & TRACECMD_SEC_FL_COMPRESS && in_uncompress_block(in_handle)) + return -1; + + return copy_options_recursive(in_handle, out_handle); + } + /* Do not copy these, as they have file specific offsets */ + switch (en2) { + case TRACECMD_OPTION_BUFFER: + case TRACECMD_OPTION_BUFFER_TEXT: + case TRACECMD_OPTION_HEADER_INFO: + case TRACECMD_OPTION_FTRACE_EVENTS: + case TRACECMD_OPTION_EVENT_FORMATS: + case TRACECMD_OPTION_KALLSYMS: + case TRACECMD_OPTION_PRINTK: + case TRACECMD_OPTION_CMDLINES: + skip = true; + break; + default: + skip = false; + break; + } + if (skip) { + do_lseek(in_handle, en4, SEEK_CUR); + continue; + } + if (do_write_check(out_handle, &option, 2)) + return -1; + + if (do_write_check(out_handle, &size, 4)) + return -1; + + if (read_copy_data(in_handle, en4, out_handle)) + return -1; + } + + return 0; +} + +static int copy_options(struct tracecmd_input *in_handle, struct tracecmd_output *out_handle) +{ + unsigned long long offset, start; + unsigned short id, en2, flags = 0; + int tmp; + + if (HAS_SECTIONS(in_handle)) { + if (read_section_header(in_handle, &id, &flags, NULL, NULL)) + return -1; + + if (id != TRACECMD_OPTION_DONE) + return -1; + + if (flags & TRACECMD_SEC_FL_COMPRESS && in_uncompress_block(in_handle)) + return -1; + } + start = tracecmd_get_out_file_offset(out_handle); + if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) { + if (do_write_check(out_handle, "options ", 10)) + return -1; + } + + offset = out_write_section_header(out_handle, TRACECMD_OPTION_DONE, "options", 0, false); + + if (copy_options_recursive(in_handle, out_handle)) + goto error; + + id = TRACECMD_OPTION_DONE; + en2 = tep_read_number(in_handle->pevent, &id, 2); + if (do_write_check(out_handle, &en2, 2)) + goto error; + + if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) { + out_save_options_offset(out_handle, start); + } else { + tmp = 8; + if (do_write_check(out_handle, &tmp, 4)) + goto error; + + out_save_options_offset(out_handle, start); + start = 0; + if (do_write_check(out_handle, &start, 8)) + goto error; + } + out_update_section_header(out_handle, offset); + if (flags & TRACECMD_SEC_FL_COMPRESS) + in_uncompress_reset(in_handle); + in_handle->file_state = TRACECMD_FILE_OPTIONS; + out_set_file_state(out_handle, in_handle->file_state); + /* Append local options */ + return tracecmd_append_options(out_handle); + +error: + if (flags & TRACECMD_SEC_FL_COMPRESS) + in_uncompress_reset(in_handle); + return 0; +} + +int tracecmd_copy_options(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle) +{ + if (!check_in_state(in_handle, TRACECMD_FILE_OPTIONS) || + !check_out_state(out_handle, TRACECMD_FILE_OPTIONS)) + return -1; + + if (!in_handle->options_start) + return 0; + + if (lseek64(in_handle->fd, in_handle->options_start, SEEK_SET) == (off64_t)-1) + return -1; + + if (copy_options(in_handle, out_handle) < 0) + return -1; + + return 0; +} + +static int copy_trace_latency(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle, const char *buf_name) +{ + int page_size = getpagesize(); + unsigned long long wsize; + unsigned long long offset; + int fd; + + if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS && + do_write_check(out_handle, "latency ", 10)) + return -1; + + offset = tracecmd_get_out_file_offset(out_handle); + + if (tracecmd_get_out_file_version(out_handle) >= FILE_VERSION_SECTIONS && + !out_add_buffer_option(out_handle, buf_name, TRACECMD_OPTION_BUFFER_TEXT, + offset, 0, NULL, page_size)) + return -1; + + offset = out_write_section_header(out_handle, TRACECMD_OPTION_BUFFER_TEXT, + "buffer latency", TRACECMD_SEC_FL_COMPRESS, false); + + if (in_handle->latz.fd >= 0) + fd = in_handle->latz.fd; + else + fd = in_handle->fd; + + if (!out_copy_fd_compress(out_handle, fd, 0, &wsize, page_size)) + return -1; + + if (out_update_section_header(out_handle, offset)) + return -1; + + out_set_file_state(out_handle, TRACECMD_FILE_CPU_LATENCY); + return 0; +} + +static int copy_trace_flyrecord_data(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle, const char *buff_name) +{ + struct cpu_data_source *data; + int total_size = 0; + int cpus; + int ret; + int i, j; + + if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) + cpus = in_handle->max_cpu; + else + cpus = in_handle->cpus; + + data = calloc(cpus, sizeof(struct cpu_data_source)); + if (!data) + return -1; + + for (i = 0; i < in_handle->cpus; i++) { + j = in_handle->cpu_data[i].cpu; + data[j].size = in_handle->cpu_data[i].file_size; + total_size += data[j].size; + if (in_handle->cpu_data[i].compress.fd >= 0) { + data[j].fd = in_handle->cpu_data[i].compress.fd; + data[j].offset = 0; + } else { + data[j].fd = in_handle->fd; + data[j].offset = in_handle->cpu_data[i].file_offset; + } + } + if (total_size || tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) + ret = out_write_cpu_data(out_handle, cpus, data, buff_name); + else + ret = 0; + free(data); + + return ret; +} + +static int copy_flyrecord_buffer(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle, int index) +{ + struct tracecmd_input *instance; + const char *name; + int ret; + + name = tracecmd_buffer_instance_name(in_handle, index); + if (!name) + return -1; + + instance = tracecmd_buffer_instance_handle(in_handle, index); + if (!instance) + return -1; + + if (!tracecmd_get_quiet(out_handle) && *name) + fprintf(stderr, "\nBuffer: %s\n\n", name); + + if (in_handle->buffers[index].latency) + ret = copy_trace_latency(in_handle, out_handle, name); + else + ret = copy_trace_flyrecord_data(instance, out_handle, name); + tracecmd_close(instance); + + return ret; +} + +static int copy_trace_data_from_v6(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle) +{ + char buf[10]; + int ret; + int i; + + if (do_read_check(in_handle, buf, 10)) + return -1; + + if (strncmp(buf, "latency", 7) == 0) + in_handle->file_state = TRACECMD_FILE_CPU_LATENCY; + else if (strncmp(buf, "flyrecord", 9) == 0) + in_handle->file_state = TRACECMD_FILE_CPU_FLYRECORD; + + tracecmd_init_data(in_handle); + tracecmd_set_out_clock(out_handle, in_handle->trace_clock); + + if (in_handle->file_state == TRACECMD_FILE_CPU_LATENCY) + return copy_trace_latency(in_handle, out_handle, ""); + + /* top instance */ + ret = copy_trace_flyrecord_data(in_handle, out_handle, ""); + if (ret) + return ret; + + for (i = 0; i < in_handle->nr_buffers; i++) + copy_flyrecord_buffer(in_handle, out_handle, i); + + return 0; +} + +static int copy_trace_data_from_v7(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle) +{ + int ret = 0; + int i; + + /* Force using temporary files for trace data decompression */ + in_handle->read_zpage = false; + tracecmd_init_data(in_handle); + tracecmd_set_out_clock(out_handle, in_handle->trace_clock); + + /* copy top buffer */ + if (in_handle->top_buffer.latency) + ret = copy_trace_latency(in_handle, out_handle, in_handle->top_buffer.name); + else if (in_handle->top_buffer.cpus) + ret = copy_trace_flyrecord_data(in_handle, out_handle, + in_handle->top_buffer.name); + else if (tracecmd_get_out_file_version(out_handle) < FILE_VERSION_SECTIONS) + ret = out_write_emty_cpu_data(out_handle, in_handle->max_cpu); + if (ret) + return ret; + + for (i = 0; i < in_handle->nr_buffers; i++) + copy_flyrecord_buffer(in_handle, out_handle, i); + + return 0; +} + +__hidden int tracecmd_copy_trace_data(struct tracecmd_input *in_handle, + struct tracecmd_output *out_handle) +{ + int ret; + + if (!check_in_state(in_handle, TRACECMD_FILE_CPU_FLYRECORD) || + !check_out_state(out_handle, TRACECMD_FILE_CPU_FLYRECORD)) + return -1; + + if (in_handle->file_version < FILE_VERSION_SECTIONS) + ret = copy_trace_data_from_v6(in_handle, out_handle); + else + ret = copy_trace_data_from_v7(in_handle, out_handle); + + return ret; +} + +/** + * tracecmd_record_at_buffer_start - return true if record is first on subbuffer + * @handle: input handle for the trace.dat file + * @record: The record to test if it is the first record on page + * + * Returns true if the record is the first record on the page. + */ +int tracecmd_record_at_buffer_start(struct tracecmd_input *handle, + struct tep_record *record) +{ + struct page *page = record->priv; + struct kbuffer *kbuf = handle->cpu_data[record->cpu].kbuf; + int offset; + + if (!page || !kbuf) + return 0; + + offset = record->offset - page->offset; + return offset == kbuffer_start_of_data(kbuf); +} + +unsigned long long tracecmd_page_ts(struct tracecmd_input *handle, + struct tep_record *record) +{ + struct page *page = record->priv; + struct kbuffer *kbuf = handle->cpu_data[record->cpu].kbuf; + + if (!page || !kbuf) + return 0; + + return kbuffer_subbuf_timestamp(kbuf, page->map); +} + +unsigned int tracecmd_record_ts_delta(struct tracecmd_input *handle, + struct tep_record *record) +{ + struct kbuffer *kbuf = handle->cpu_data[record->cpu].kbuf; + struct page *page = record->priv; + int offset; + + if (!page || !kbuf) + return 0; + + offset = record->offset - page->offset; + + return kbuffer_ptr_delta(kbuf, page->map + offset); +} + +struct kbuffer *tracecmd_record_kbuf(struct tracecmd_input *handle, + struct tep_record *record) +{ + return handle->cpu_data[record->cpu].kbuf; +} + +void *tracecmd_record_page(struct tracecmd_input *handle, + struct tep_record *record) +{ + struct page *page = record->priv; + + return page ? page->map : NULL; +} + +void *tracecmd_record_offset(struct tracecmd_input *handle, + struct tep_record *record) +{ + struct page *page = record->priv; + int offset; + + if (!page) + return NULL; + + offset = record->offset - page->offset; + + return page->map + offset; +} + +int tracecmd_buffer_instances(struct tracecmd_input *handle) +{ + return handle->nr_buffers; +} + +const char *tracecmd_buffer_instance_name(struct tracecmd_input *handle, int indx) +{ + if (indx >= handle->nr_buffers) + return NULL; + + return handle->buffers[indx].name; +} + +struct tracecmd_input * +tracecmd_buffer_instance_handle(struct tracecmd_input *handle, int indx) +{ + struct tracecmd_input *new_handle; + struct input_buffer_instance *buffer = &handle->buffers[indx]; + size_t offset; + ssize_t ret; + + if (indx >= handle->nr_buffers) + return NULL; + + /* + * We make a copy of the current handle, but we substitute + * the cpu data with the cpu data for this buffer. + */ + new_handle = malloc(sizeof(*handle)); + if (!new_handle) + return NULL; + + *new_handle = *handle; + memset(&new_handle->top_buffer, 0, sizeof(new_handle->top_buffer)); + new_handle->cpu_data = NULL; + new_handle->nr_buffers = 0; + new_handle->buffers = NULL; + new_handle->version = NULL; + new_handle->sections = NULL; + new_handle->strings = NULL; + new_handle->guest = NULL; + new_handle->ref = 1; + if (handle->trace_clock) { + new_handle->trace_clock = strdup(handle->trace_clock); + if (!new_handle->trace_clock) { + free(new_handle); + return NULL; + } + } + memset(&new_handle->host, 0, sizeof(new_handle->host)); + new_handle->parent = handle; + new_handle->cpustats = NULL; + new_handle->hooks = NULL; + if (handle->uname) + /* Ignore if fails to malloc, no biggy */ + new_handle->uname = strdup(handle->uname); + tracecmd_ref(handle); + + new_handle->fd = dup(handle->fd); + + new_handle->flags |= TRACECMD_FL_BUFFER_INSTANCE; + + new_handle->pid_maps = NULL; + if (!HAS_SECTIONS(handle)) { + /* Save where we currently are */ + offset = lseek64(handle->fd, 0, SEEK_CUR); + + ret = lseek64(handle->fd, buffer->offset, SEEK_SET); + if (ret == (off64_t)-1) { + tracecmd_warning("could not seek to buffer %s offset %ld", + buffer->name, buffer->offset); + goto error; + } + /* + * read_options_type() is called right after the CPU count so update + * file state accordingly. + */ + new_handle->file_state = TRACECMD_FILE_CPU_COUNT; + ret = read_options_type(new_handle); + if (!ret) + ret = read_cpu_data(new_handle); + + if (ret < 0) { + tracecmd_warning("failed to read sub buffer %s", buffer->name); + goto error; + } + ret = lseek64(handle->fd, offset, SEEK_SET); + if (ret < 0) { + tracecmd_warning("could not seek to back to offset %ld", offset); + goto error; + } + } else { + new_handle->page_size = handle->buffers[indx].page_size; + if (init_buffer_cpu_data(new_handle, buffer) < 0) + goto error; + } + + return new_handle; + +error: + tracecmd_close(new_handle); + return NULL; +} + +int tracecmd_is_buffer_instance(struct tracecmd_input *handle) +{ + return handle->flags & TRACECMD_FL_BUFFER_INSTANCE; +} + +/** + * tracecmd_long_size - return the size of "long" for the arch + * @handle: input handle for the trace.dat file + */ +int tracecmd_long_size(struct tracecmd_input *handle) +{ + return handle->long_size; +} + +/** + * tracecmd_page_size - return the PAGE_SIZE for the arch + * @handle: input handle for the trace.dat file + */ +int tracecmd_page_size(struct tracecmd_input *handle) +{ + return handle->page_size; +} + +/** + * tracecmd_page_size - return the number of CPUs recorded + * @handle: input handle for the trace.dat file + */ +int tracecmd_cpus(struct tracecmd_input *handle) +{ + return handle->max_cpu; +} + +/** + * tracecmd_get_tep - return the tep handle + * @handle: input handle for the trace.dat file + */ +struct tep_handle *tracecmd_get_tep(struct tracecmd_input *handle) +{ + return handle->pevent; +} + +/** + * tracecmd_get_in_file_version - return the trace.dat file version + * @handle: input handle for the trace.dat file + */ +unsigned long tracecmd_get_in_file_version(struct tracecmd_input *handle) +{ + return handle->file_version; +} + +/** + * tracecmd_get_file_compress_proto - get name and version of compression algorithm + * @handle: input handle for the trace.dat file + * @name: return, name of the compression algorithm. + * @version: return, version of the compression algorithm. + * + * Get the name and the version of the compression algorithm, used to + * compress the file associated with @handle. + * Returns 0 on success, or -1 in case of an error. If 0 is returned, + * the name and version of the algorithm are stored in @name and @version. + * The returned strings must *not* be freed. + */ +int tracecmd_get_file_compress_proto(struct tracecmd_input *handle, + const char **name, const char **version) +{ + return tracecmd_compress_proto_get_name(handle->compress, name, version); +} + +/** + * tracecmd_get_use_trace_clock - return use_trace_clock + * @handle: input handle for the trace.dat file + */ +bool tracecmd_get_use_trace_clock(struct tracecmd_input *handle) +{ + return handle->use_trace_clock; +} + +/** + * tracecmd_get_options_offset - get offset of the options sections in the file + * @handle: input handle for the trace.dat file + */ +size_t tracecmd_get_options_offset(struct tracecmd_input *handle) +{ + return handle->options_start; +} + +/** + * tracecmd_get_trace_clock - return the saved trace clock + * @handle: input handle for the trace.dat file + * + * Returns a string of the clock that was saved in the trace.dat file. + * The string should not be freed, as it points to the internal + * structure data. + */ +const char *tracecmd_get_trace_clock(struct tracecmd_input *handle) +{ + return handle->trace_clock; +} + +/** + * tracecmd_get_cpustats - return the saved cpu stats + * @handle: input handle for the trace.dat file + * + * Provides a method to extract the cpu stats saved in @handle. + * + * Returns a string of the cpu stats that was saved in the trace.dat file. + * The string should not be freed, as it points to the internal + * structure data. + */ +const char *tracecmd_get_cpustats(struct tracecmd_input *handle) +{ + return handle->cpustats; +} + +/** + * tracecmd_get_uname - return the saved name and kernel information + * @handle: input handle for the trace.dat file + * + * Provides a method to extract the system information saved in @handle. + * + * Returns a string of the system information that was saved in the + * trace.dat file. + * The string should not be freed, as it points to the internal + * structure data. + */ +const char *tracecmd_get_uname(struct tracecmd_input *handle) +{ + return handle->uname; +} + +/** + * tracecmd_get_version - return the saved version information + * @handle: input handle for the trace.dat file + * + * Provides a method to extract the version string saved in @handle. + * + * Returns a string of the version that was saved in the trace.dat file. + * The string should not be freed, as it points to the internal + * structure data. + */ +const char *tracecmd_get_version(struct tracecmd_input *handle) +{ + return handle->version; +} + +/** + * tracecmd_get_cpu_file_size - return the saved cpu file size + * @handle: input handle for the trace.dat file + * @cpu: cpu index + * + * Provides a method to extract the cpu file size saved in @handle. + * + * Returns the cpu file size saved in trace.dat file or (off64_t)-1 for + * invalid cpu index. + */ +off64_t tracecmd_get_cpu_file_size(struct tracecmd_input *handle, int cpu) +{ + if (cpu < 0 || cpu >= handle->cpus) + return (off64_t)-1; + return handle->cpu_data[cpu].file_size; +} + +/** + * tracecmd_get_show_data_func - return the show data func + * @handle: input handle for the trace.dat file + */ +tracecmd_show_data_func +tracecmd_get_show_data_func(struct tracecmd_input *handle) +{ + return handle->show_data_func; +} + +/** + * tracecmd_set_show_data_func - set the show data func + * @handle: input handle for the trace.dat file + */ +void tracecmd_set_show_data_func(struct tracecmd_input *handle, + tracecmd_show_data_func func) +{ + handle->show_data_func = func; +} + +/** + * tracecmd_get_traceid - get the trace id of the session + * @handle: input handle for the trace.dat file + * + * Returns the trace id, written in the trace file + */ +unsigned long long tracecmd_get_traceid(struct tracecmd_input *handle) +{ + return handle->trace_id; +} + +/** + * tracecmd_get_first_ts - get the timestamp of the first recorded event + * @handle: input handle for the trace.dat file + * + * Returns the timestamp of the first recorded event + */ +unsigned long long tracecmd_get_first_ts(struct tracecmd_input *handle) +{ + unsigned long long ts = 0; + bool first = true; + int i; + + for (i = 0; i < handle->cpus; i++) { + /* Ignore empty buffers */ + if (!handle->cpu_data[i].size) + continue; + if (first || ts > handle->cpu_data[i].first_ts) + ts = handle->cpu_data[i].first_ts; + first = false; + } + + return ts; +} + +/** + * tracecmd_get_guest_cpumap - get the mapping of guest VCPU to host process + * @handle: input handle for the trace.dat file + * @trace_id: ID of the guest tracing session + * @name: return, name of the guest + * @vcpu_count: return, number of VPUs + * @cpu_pid: return, array with guest VCPU to host process mapping + * + * Returns @name of the guest, number of VPUs (@vcpu_count) + * and array @cpu_pid with size @vcpu_count. Array index is VCPU id, array + * content is PID of the host process, running this VCPU. + * + * This information is stored in host trace.dat file + */ +int tracecmd_get_guest_cpumap(struct tracecmd_input *handle, + unsigned long long trace_id, + const char **name, + int *vcpu_count, const int **cpu_pid) +{ + struct guest_trace_info *guest = handle->guest; + + while (guest) { + if (guest->trace_id == trace_id) + break; + guest = guest->next; + } + if (!guest) + return -1; + + if (name) + *name = guest->name; + if (vcpu_count) + *vcpu_count = guest->vcpu_count; + if (cpu_pid) + *cpu_pid = guest->cpu_pid; + return 0; +} + +/** + * tracecmd_enable_tsync - enable / disable the timestamps correction + * @handle: input handle for the trace.dat file + * @enable: enable / disable the timestamps correction + * + * Enables or disables timestamps correction on file load, using the array of + * recorded time offsets. If "enable" is true, but there are no time offsets, + * function fails and -1 is returned. + * + * Returns -1 in case of an error, or 0 otherwise + */ +int tracecmd_enable_tsync(struct tracecmd_input *handle, bool enable) +{ + if (enable && + (!handle->host.ts_offsets || !handle->host.cpu_count)) + return -1; + + handle->host.sync_enable = enable; + + return 0; +} + |