diff options
Diffstat (limited to 'lib/trace-cmd/trace-output.c')
-rw-r--r-- | lib/trace-cmd/trace-output.c | 2819 |
1 files changed, 2819 insertions, 0 deletions
diff --git a/lib/trace-cmd/trace-output.c b/lib/trace-cmd/trace-output.c new file mode 100644 index 00000000..ca7132e1 --- /dev/null +++ b/lib/trace-cmd/trace-output.c @@ -0,0 +1,2819 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ +#define _LARGEFILE64_SOURCE +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <glob.h> + +#include "tracefs.h" +#include "trace-cmd.h" +#include "trace-cmd-local.h" +#include "trace-write-local.h" +#include "list.h" +#include "trace-msg.h" + +/* We can't depend on the host size for size_t, all must be 64 bit */ +typedef unsigned long long tsize_t; +typedef long long stsize_t; + +struct tracecmd_option { + unsigned short id; + int size; + void *data; + tsize_t offset; + struct list_head list; +}; + +struct tracecmd_buffer { + int cpus; + void *name; + tsize_t offset; + struct tracecmd_option *option; + struct list_head list; +}; + +enum { + OUTPUT_FL_SEND_META = (1 << 0), +}; + +struct tracecmd_output { + int fd; + int page_size; + int cpus; + struct tep_handle *pevent; + char *tracing_dir; + char *kallsyms; + int nr_options; + bool quiet; + unsigned long file_state; + unsigned long file_version; + + /* size of meta-data strings, not yet stored in the file */ + unsigned long strings_p; + /* current virtual offset of meta-data string */ + unsigned long strings_offs; + + unsigned long long options_start; + bool big_endian; + bool do_compress; + struct tracecmd_compression *compress; + + struct list_head options; + struct list_head buffers; + struct tracecmd_msg_handle *msg_handle; + char *trace_clock; + + /* meta-data strings, not yet stored in the file */ + char *strings; +}; + +struct list_event { + struct list_event *next; + char *name; + char *file; +}; + +struct list_event_system { + struct list_event_system *next; + struct list_event *events; + char *name; +}; + +#define HAS_SECTIONS(H) ((H)->file_version >= FILE_VERSION_SECTIONS) + +static int write_options(struct tracecmd_output *handle); +static int save_string_section(struct tracecmd_output *handle, bool compress); + +__hidden long long +do_write_check(struct tracecmd_output *handle, const void *data, long long size) +{ + if (handle->do_compress) + return tracecmd_compress_buffer_write(handle->compress, data, size); + + if (handle->msg_handle) + return tracecmd_msg_data_send(handle->msg_handle, data, size); + + return __do_write_check(handle->fd, data, size); +} + +static inline off64_t do_lseek(struct tracecmd_output *handle, off_t offset, int whence) +{ + if (handle->do_compress) + return tracecmd_compress_lseek(handle->compress, offset, whence); + + if (handle->msg_handle) + return msg_lseek(handle->msg_handle, offset, whence); + + return lseek64(handle->fd, offset, whence); +} + +static inline int do_preed(struct tracecmd_output *handle, void *dst, int len, off_t offset) +{ + if (handle->do_compress) + return tracecmd_compress_pread(handle->compress, dst, len, offset); + + return pread(handle->fd, dst, len, offset); +} + +static short convert_endian_2(struct tracecmd_output *handle, short val) +{ + if (!handle->pevent) + return val; + + return tep_read_number(handle->pevent, &val, 2); +} + +static int convert_endian_4(struct tracecmd_output *handle, int val) +{ + if (!handle->pevent) + return val; + + return tep_read_number(handle->pevent, &val, 4); +} + +static unsigned long long convert_endian_8(struct tracecmd_output *handle, + unsigned long long val) +{ + if (!handle->pevent) + return val; + + return tep_read_number(handle->pevent, &val, 8); +} + +__hidden void out_compression_reset(struct tracecmd_output *handle, bool compress) +{ + if (!compress || !handle->compress) + return; + + tracecmd_compress_reset(handle->compress); + handle->do_compress = false; +} + +__hidden int out_uncompress_block(struct tracecmd_output *handle) +{ + int ret = 0; + + if (!handle->compress) + return 0; + + ret = tracecmd_uncompress_block(handle->compress); + if (!ret) + handle->do_compress = true; + + return ret; +} + +__hidden int out_compression_start(struct tracecmd_output *handle, bool compress) +{ + if (!compress || !handle->compress) + return 0; + + tracecmd_compress_reset(handle->compress); + handle->do_compress = true; + + return 0; +} + +__hidden int out_compression_end(struct tracecmd_output *handle, bool compress) +{ + if (!compress || !handle->compress) + return 0; + + handle->do_compress = false; + return tracecmd_compress_block(handle->compress); +} + +static long add_string(struct tracecmd_output *handle, const char *string) +{ + int size = strlen(string) + 1; + int pos = handle->strings_p; + char *strings; + + strings = realloc(handle->strings, pos + size); + if (!strings) + return -1; + handle->strings = strings; + memcpy(handle->strings + pos, string, size); + handle->strings_p += size; + + return handle->strings_offs + pos; +} + +/** + * tracecmd_set_quiet - Set if to print output to the screen + * @quiet: If non zero, print no output to the screen + * + */ +void tracecmd_set_quiet(struct tracecmd_output *handle, bool set_quiet) +{ + if (handle) + handle->quiet = set_quiet; +} + +void tracecmd_set_out_clock(struct tracecmd_output *handle, const char *clock) +{ + if (handle && clock) { + free(handle->trace_clock); + handle->trace_clock = strdup(clock); + } +} + +/** + * tracecmd_get_quiet - Get if to print output to the screen + * Returns non zero, if no output to the screen should be printed + * + */ +bool tracecmd_get_quiet(struct tracecmd_output *handle) +{ + if (handle) + return handle->quiet; + return false; +} + +void tracecmd_output_free(struct tracecmd_output *handle) +{ + struct tracecmd_option *option; + struct tracecmd_buffer *buffer; + + if (!handle) + return; + + if (handle->tracing_dir) + free(handle->tracing_dir); + + if (handle->pevent) + tep_unref(handle->pevent); + + while (!list_empty(&handle->buffers)) { + buffer = container_of(handle->buffers.next, + struct tracecmd_buffer, list); + list_del(&buffer->list); + free(buffer->name); + free(buffer); + } + while (!list_empty(&handle->options)) { + option = container_of(handle->options.next, + struct tracecmd_option, list); + list_del(&option->list); + free(option->data); + free(option); + } + + free(handle->strings); + free(handle->trace_clock); + tracecmd_compress_destroy(handle->compress); + free(handle); +} + +void tracecmd_output_close(struct tracecmd_output *handle) +{ + if (!handle) + return; + + if (HAS_SECTIONS(handle)) { + /* write any unsaved options at the end of trace files with sections */ + write_options(handle); + + /* write strings section */ + save_string_section(handle, true); + } + + if (handle->fd >= 0) { + close(handle->fd); + handle->fd = -1; + } + + tracecmd_output_free(handle); +} +static unsigned long get_size_fd(int fd) +{ + unsigned long long size = 0; + char buf[BUFSIZ]; + int r; + + do { + r = read(fd, buf, BUFSIZ); + if (r > 0) + size += r; + } while (r > 0); + + lseek(fd, 0, SEEK_SET); + + return size; +} + +static unsigned long get_size(const char *file) +{ + unsigned long long size = 0; + int fd; + + fd = open(file, O_RDONLY); + if (fd < 0) { + tracecmd_warning("Can't read '%s'", file); + return 0; /* Caller will fail with zero */ + } + size = get_size_fd(fd); + close(fd); + + return size; +} + +static tsize_t copy_file_fd(struct tracecmd_output *handle, int fd, unsigned long long max) +{ + tsize_t rsize = BUFSIZ; + tsize_t size = 0; + char buf[BUFSIZ]; + stsize_t r; + + do { + if (max && rsize > max) + rsize = max; + + r = read(fd, buf, rsize); + if (r > 0) { + size += r; + if (do_write_check(handle, buf, r)) + return 0; + if (max) { + max -= r; + if (!max) + break; + } + } + } while (r > 0); + + return size; +} + +static tsize_t copy_file(struct tracecmd_output *handle, + const char *file) +{ + tsize_t size = 0; + int fd; + + fd = open(file, O_RDONLY); + if (fd < 0) { + tracecmd_warning("Can't read '%s'", file); + return 0; + } + size = copy_file_fd(handle, fd, 0); + close(fd); + + return size; +} + +#define PAGES_IN_CHUNK 10 +__hidden unsigned long long out_copy_fd_compress(struct tracecmd_output *handle, + int fd, unsigned long long max, + unsigned long long *write_size, + int page) +{ + unsigned long long rsize = 0; + unsigned long long wsize = 0; + unsigned long long size; + int ret; + + if (handle->compress) { + rsize = max; + ret = tracecmd_compress_copy_from(handle->compress, fd, + PAGES_IN_CHUNK * page, + &rsize, &wsize); + if (ret < 0) + return 0; + + size = rsize; + if (write_size) + *write_size = wsize; + } else { + size = copy_file_fd(handle, fd, max); + if (write_size) + *write_size = size; + } + + return size; +} + +static tsize_t copy_file_compress(struct tracecmd_output *handle, + const char *file, unsigned long long *write_size) +{ + int ret; + int fd; + + fd = open(file, O_RDONLY); + if (fd < 0) { + tracecmd_warning("Can't read '%s'", file); + return 0; + } + + ret = out_copy_fd_compress(handle, fd, 0, write_size, getpagesize()); + if (!ret) + tracecmd_warning("Can't compress '%s'", file); + + close(fd); + return ret; +} + +/* + * Finds the path to the debugfs/tracing + * Allocates the string and stores it. + */ +static const char *find_tracing_dir(struct tracecmd_output *handle) +{ + if (!handle->tracing_dir) { + const char *dir = tracefs_tracing_dir(); + + if (dir) + handle->tracing_dir = strdup(dir); + } + return handle->tracing_dir; +} + +static char *get_tracing_file(struct tracecmd_output *handle, const char *name) +{ + const char *tracing; + char *file; + int ret; + + tracing = find_tracing_dir(handle); + if (!tracing) + return NULL; + + ret = asprintf(&file, "%s/%s", tracing, name); + if (ret < 0) + return NULL; + + return file; +} + +static void put_tracing_file(char *file) +{ + free(file); +} + +int tracecmd_ftrace_enable(int set) +{ + struct stat buf; + char *path = "/proc/sys/kernel/ftrace_enabled"; + int fd; + char *val = set ? "1" : "0"; + int ret = 0; + + /* if ftace_enable does not exist, simply ignore it */ + fd = stat(path, &buf); + if (fd < 0) + return ENODEV; + + fd = open(path, O_WRONLY); + if (fd < 0) { + tracecmd_warning("Can't %s ftrace", set ? "enable" : "disable"); + return EIO; + } + + if (write(fd, val, 1) < 0) + ret = -1; + close(fd); + + return ret; +} + +__hidden unsigned long long +out_write_section_header(struct tracecmd_output *handle, unsigned short header_id, + char *description, int flags, bool option) +{ + tsize_t endian8; + tsize_t offset; + long long size; + short endian2; + int endian4; + int desc; + + if (header_id >= TRACECMD_OPTION_MAX) + return -1; + if (!HAS_SECTIONS(handle)) + return 0; + if (!handle->compress) + flags &= ~TRACECMD_SEC_FL_COMPRESS; + offset = do_lseek(handle, 0, SEEK_CUR); + if (option) { + endian8 = convert_endian_8(handle, offset); + if (!tracecmd_add_option(handle, header_id, 8, &endian8)) + return -1; + } + /* Section ID */ + endian2 = convert_endian_2(handle, header_id); + if (do_write_check(handle, &endian2, 2)) + return (off64_t)-1; + + /* Section flags */ + endian2 = convert_endian_2(handle, flags); + if (do_write_check(handle, &endian2, 2)) + return (off64_t)-1; + + /* Section description */ + if (description) + desc = add_string(handle, description); + else + desc = -1; + endian4 = convert_endian_4(handle, desc); + if (do_write_check(handle, &endian4, 4)) + return (off64_t)-1; + + offset = do_lseek(handle, 0, SEEK_CUR); + size = 0; + /* Reserve for section size */ + if (do_write_check(handle, &size, 8)) + return (off64_t)-1; + return offset; +} + +__hidden int out_update_section_header(struct tracecmd_output *handle, tsize_t offset) +{ + tsize_t current; + tsize_t endian8; + tsize_t size; + + if (!HAS_SECTIONS(handle) || offset == 0) + return 0; + + current = do_lseek(handle, 0, SEEK_CUR); + /* The real size is the difference between the saved offset and + * the current offset - 8 bytes, the reserved space for the section size. + */ + size = current - offset; + if (size < 8) + return -1; + size -= 8; + if (do_lseek(handle, offset, SEEK_SET) == (off64_t)-1) + return -1; + + endian8 = convert_endian_8(handle, size); + if (do_write_check(handle, &endian8, 8)) + return -1; + if (do_lseek(handle, current, SEEK_SET) == (off64_t)-1) + return -1; + return 0; +} + +static int save_string_section(struct tracecmd_output *handle, bool compress) +{ + enum tracecmd_section_flags flags = 0; + tsize_t offset; + + if (!handle->strings || !handle->strings_p) + return 0; + + if (!check_out_state(handle, TRACECMD_OPTION_STRINGS)) { + tracecmd_warning("Cannot write strings, unexpected state 0x%X", + handle->file_state); + return -1; + } + + if (compress) + flags |= TRACECMD_SEC_FL_COMPRESS; + offset = out_write_section_header(handle, TRACECMD_OPTION_STRINGS, "strings", flags, false); + if (offset == (off64_t)-1) + return -1; + + out_compression_start(handle, compress); + + if (do_write_check(handle, handle->strings, handle->strings_p)) + goto error; + + if (out_compression_end(handle, compress)) + goto error; + + if (out_update_section_header(handle, offset)) + return -1; + + handle->strings_offs += handle->strings_p; + free(handle->strings); + handle->strings = NULL; + handle->strings_p = 0; + handle->file_state = TRACECMD_OPTION_STRINGS; + return 0; + +error: + out_compression_reset(handle, compress); + return -1; +} + +static int read_header_files(struct tracecmd_output *handle, bool compress) +{ + enum tracecmd_section_flags flags = 0; + tsize_t size, check_size, endian8; + struct stat st; + tsize_t offset; + char *path; + int fd = -1; + int ret; + + if (!check_out_state(handle, TRACECMD_FILE_HEADERS)) { + tracecmd_warning("Cannot read header files, unexpected state 0x%X", + handle->file_state); + return -1; + } + + path = get_tracing_file(handle, "events/header_page"); + if (!path) + return -1; + + if (compress) + flags |= TRACECMD_SEC_FL_COMPRESS; + offset = out_write_section_header(handle, TRACECMD_OPTION_HEADER_INFO, + "headers", flags, true); + if (offset == (off64_t)-1) + return -1; + + out_compression_start(handle, compress); + ret = stat(path, &st); + if (ret < 0) { + /* old style did not show this info, just add zero */ + put_tracing_file(path); + if (do_write_check(handle, "header_page", 12)) + goto out_close; + size = 0; + if (do_write_check(handle, &size, 8)) + goto out_close; + if (do_write_check(handle, "header_event", 13)) + goto out_close; + if (do_write_check(handle, &size, 8)) + goto out_close; + if (out_compression_end(handle, compress)) + goto out_close; + if (out_update_section_header(handle, offset)) + goto out_close; + return 0; + } + + fd = open(path, O_RDONLY); + if (fd < 0) { + tracecmd_warning("can't read '%s'", path); + goto out_close; + } + + /* unfortunately, you can not stat debugfs files for size */ + size = get_size_fd(fd); + + if (do_write_check(handle, "header_page", 12)) + goto out_close; + endian8 = convert_endian_8(handle, size); + if (do_write_check(handle, &endian8, 8)) + goto out_close; + check_size = copy_file_fd(handle, fd, 0); + close(fd); + if (size != check_size) { + tracecmd_warning("wrong size for '%s' size=%lld read=%lld", path, size, check_size); + errno = EINVAL; + goto out_close; + } + put_tracing_file(path); + + path = get_tracing_file(handle, "events/header_event"); + if (!path) + goto out_close; + + fd = open(path, O_RDONLY); + if (fd < 0) { + tracecmd_warning("can't read '%s'", path); + goto out_close; + } + + size = get_size_fd(fd); + + if (do_write_check(handle, "header_event", 13)) + goto out_close; + endian8 = convert_endian_8(handle, size); + if (do_write_check(handle, &endian8, 8)) + goto out_close; + check_size = copy_file_fd(handle, fd, 0); + close(fd); + if (size != check_size) { + tracecmd_warning("wrong size for '%s'", path); + goto out_close; + } + put_tracing_file(path); + if (out_compression_end(handle, compress)) + goto out_close; + + if (out_update_section_header(handle, offset)) + goto out_close; + handle->file_state = TRACECMD_FILE_HEADERS; + + return 0; + + out_close: + out_compression_reset(handle, compress); + if (fd >= 0) + close(fd); + return -1; +} + +static int copy_event_system(struct tracecmd_output *handle, + struct list_event_system *slist) +{ + struct list_event *elist; + unsigned long long size, check_size, endian8; + struct stat st; + char *format; + int endian4; + int count = 0; + int ret; + + for (elist = slist->events; elist; elist = elist->next) + count++; + + endian4 = convert_endian_4(handle, count); + if (do_write_check(handle, &endian4, 4)) + return -1; + + for (elist = slist->events; elist; elist = elist->next) { + format = elist->file; + ret = stat(format, &st); + + if (ret >= 0) { + /* unfortunately, you can not stat debugfs files for size */ + size = get_size(format); + endian8 = convert_endian_8(handle, size); + if (do_write_check(handle, &endian8, 8)) + return -1; + check_size = copy_file(handle, format); + if (size != check_size) { + tracecmd_warning("error in size of file '%s'", format); + return -1; + } + } + } + + return 0; +} + +static void add_list_event_system(struct list_event_system **systems, + const char *system, + const char *event, + const char *path) +{ + struct list_event_system *slist; + struct list_event *elist; + + for (slist = *systems; slist; slist = slist->next) + if (strcmp(slist->name, system) == 0) + break; + + if (!slist) { + slist = malloc(sizeof(*slist)); + if (!slist) + goto err_mem; + slist->name = strdup(system); + if (!slist->name) { + free(slist); + goto err_mem; + } + slist->next = *systems; + slist->events = NULL; + *systems = slist; + } + + for (elist = slist->events; elist; elist = elist->next) + if (strcmp(elist->name, event) == 0) + break; + + if (!elist) { + elist = malloc(sizeof(*elist)); + if (!elist) + goto err_mem; + elist->name = strdup(event); + elist->file = strdup(path); + if (!elist->name || !elist->file) { + free(elist->name); + free(elist->file); + free(elist); + goto err_mem; + } + elist->next = slist->events; + slist->events = elist; + } + return; + err_mem: + tracecmd_warning("Insufficient memory"); +} + +static void free_list_events(struct list_event_system *list) +{ + struct list_event_system *slist; + struct list_event *elist; + + while (list) { + slist = list; + list = list->next; + while (slist->events) { + elist = slist->events; + slist->events = elist->next; + free(elist->name); + free(elist->file); + free(elist); + } + free(slist->name); + free(slist); + } +} + +static void glob_events(struct tracecmd_output *handle, + struct list_event_system **systems, + const char *str) +{ + glob_t globbuf; + char *events_path; + char *system; + char *event; + char *path; + char *file; + char *ptr; + int do_ftrace = 0; + int events_len; + int ret; + int i; + + if (strncmp(str, "ftrace/", 7) == 0) + do_ftrace = 1; + + events_path = get_tracing_file(handle, "events"); + events_len = strlen(events_path); + + path = malloc(events_len + strlen(str) + + strlen("/format") + 2); + if (!path) + return; + path[0] = '\0'; + strcat(path, events_path); + strcat(path, "/"); + strcat(path, str); + strcat(path, "/format"); + put_tracing_file(events_path); + + globbuf.gl_offs = 0; + ret = glob(path, 0, NULL, &globbuf); + free(path); + if (ret < 0) + return; + + for (i = 0; i < globbuf.gl_pathc; i++) { + file = globbuf.gl_pathv[i]; + system = strdup(file + events_len + 1); + system = strtok_r(system, "/", &ptr); + if (!ptr) { + /* ?? should we warn? */ + free(system); + continue; + } + + if (!do_ftrace && strcmp(system, "ftrace") == 0) { + free(system); + continue; + } + + event = strtok_r(NULL, "/", &ptr); + if (!ptr) { + /* ?? should we warn? */ + free(system); + continue; + } + + add_list_event_system(systems, system, event, file); + free(system); + } + globfree(&globbuf); +} + +static void +create_event_list_item(struct tracecmd_output *handle, + struct list_event_system **systems, + struct tracecmd_event_list *list) +{ + char *ptr; + char *str; + + str = strdup(list->glob); + if (!str) + goto err_mem; + + /* system and event names are separated by a ':' */ + ptr = strchr(str, ':'); + if (ptr) + *ptr = '/'; + else + /* system and event may also be separated by a '/' */ + ptr = strchr(str, '/'); + + if (ptr) { + glob_events(handle, systems, str); + free(str); + return; + } + + ptr = str; + str = malloc(strlen(ptr) + 3); + if (!str) + goto err_mem; + str[0] = '\0'; + strcat(str, ptr); + strcat(str, "/*"); + glob_events(handle, systems, str); + + str[0] = '\0'; + strcat(str, "*/"); + strcat(str, ptr); + glob_events(handle, systems, str); + + free(ptr); + free(str); + return; + err_mem: + tracecmd_warning("Insufficient memory"); +} + +static int read_ftrace_files(struct tracecmd_output *handle, bool compress) +{ + enum tracecmd_section_flags flags = 0; + struct list_event_system *systems = NULL; + struct tracecmd_event_list list = { .glob = "ftrace/*" }; + tsize_t offset; + int ret; + + if (!check_out_state(handle, TRACECMD_FILE_FTRACE_EVENTS)) { + tracecmd_warning("Cannot read ftrace files, unexpected state 0x%X", + handle->file_state); + return -1; + } + + if (compress) + flags |= TRACECMD_SEC_FL_COMPRESS; + offset = out_write_section_header(handle, TRACECMD_OPTION_FTRACE_EVENTS, + "ftrace events", flags, true); + if (offset == (off64_t)-1) + return -1; + + create_event_list_item(handle, &systems, &list); + out_compression_start(handle, compress); + + ret = copy_event_system(handle, systems); + if (!ret) + ret = out_compression_end(handle, compress); + else + out_compression_reset(handle, compress); + + free_list_events(systems); + if (ret) + return ret; + if (out_update_section_header(handle, offset)) + return -1; + + handle->file_state = TRACECMD_FILE_FTRACE_EVENTS; + + return ret; +} + +static struct list_event_system * +create_event_list(struct tracecmd_output *handle, + struct tracecmd_event_list *event_list) +{ + struct list_event_system *systems = NULL; + struct tracecmd_event_list *list; + + for (list = event_list; list; list = list->next) + create_event_list_item(handle, &systems, list); + + return systems; +} + +static int read_event_files(struct tracecmd_output *handle, + struct tracecmd_event_list *event_list, bool compress) +{ + enum tracecmd_section_flags flags = 0; + struct list_event_system *systems; + struct list_event_system *slist; + struct tracecmd_event_list *list; + struct tracecmd_event_list all_events = { .glob = "*/*" }; + int count = 0; + tsize_t offset; + int endian4; + int ret; + + if (!check_out_state(handle, TRACECMD_FILE_ALL_EVENTS)) { + tracecmd_warning("Cannot read event files, unexpected state 0x%X", + handle->file_state); + return -1; + } + + if (compress) + flags |= TRACECMD_SEC_FL_COMPRESS; + offset = out_write_section_header(handle, TRACECMD_OPTION_EVENT_FORMATS, + "events format", flags, true); + if (offset == (off64_t)-1) + return -1; + /* + * If any of the list is the special keyword "all" then + * just do all files. + */ + for (list = event_list; list; list = list->next) { + if (strcmp(list->glob, "all") == 0) + break; + } + /* all events are listed, use a global glob */ + if (!event_list || list) + event_list = &all_events; + + systems = create_event_list(handle, event_list); + + for (slist = systems; slist; slist = slist->next) + count++; + out_compression_start(handle, compress); + ret = -1; + endian4 = convert_endian_4(handle, count); + if (do_write_check(handle, &endian4, 4)) + goto out_free; + + ret = 0; + for (slist = systems; !ret && slist; slist = slist->next) { + if (do_write_check(handle, slist->name, + strlen(slist->name) + 1)) { + ret = -1; + continue; + } + ret = copy_event_system(handle, slist); + } + if (ret) + goto out_free; + + ret = out_compression_end(handle, compress); + if (ret) + goto out_free; + ret = out_update_section_header(handle, offset); + + out_free: + if (!ret) + handle->file_state = TRACECMD_FILE_ALL_EVENTS; + else + out_compression_reset(handle, compress); + + free_list_events(systems); + + return ret; +} + +#define KPTR_UNINITIALIZED 'X' + +static void set_proc_kptr_restrict(int reset) +{ + char *path = "/proc/sys/kernel/kptr_restrict"; + static char saved = KPTR_UNINITIALIZED; + int fd, ret = -1; + struct stat st; + char buf; + + if ((reset && saved == KPTR_UNINITIALIZED) || + (stat(path, &st) < 0)) + return; + + fd = open(path, O_RDONLY); + if (fd < 0) + goto err; + + if (reset) { + buf = saved; + } else { + if (read(fd, &buf, 1) < 0) + goto err; + saved = buf; + buf = '0'; + } + close(fd); + + fd = open(path, O_WRONLY); + if (fd < 0) + goto err; + if (write(fd, &buf, 1) > 0) + ret = 0; +err: + if (fd > 0) + close(fd); + if (ret) + tracecmd_warning("can't set kptr_restrict"); +} + +static int read_proc_kallsyms(struct tracecmd_output *handle, bool compress) +{ + enum tracecmd_section_flags flags = 0; + unsigned int size, check_size, endian4; + const char *path = "/proc/kallsyms"; + tsize_t offset; + struct stat st; + int ret; + + if (!check_out_state(handle, TRACECMD_FILE_KALLSYMS)) { + tracecmd_warning("Cannot read kallsyms, unexpected state 0x%X", + handle->file_state); + return -1; + } + + if (handle->kallsyms) + path = handle->kallsyms; + + if (compress) + flags |= TRACECMD_SEC_FL_COMPRESS; + offset = out_write_section_header(handle, TRACECMD_OPTION_KALLSYMS, + "kallsyms", flags, true); + if (offset == (off64_t)-1) + return -1; + + out_compression_start(handle, compress); + ret = stat(path, &st); + if (ret < 0) { + /* not found */ + size = 0; + endian4 = convert_endian_4(handle, size); + ret = do_write_check(handle, &endian4, 4); + goto out; + } + size = get_size(path); + endian4 = convert_endian_4(handle, size); + ret = do_write_check(handle, &endian4, 4); + if (ret) + goto out; + + set_proc_kptr_restrict(0); + check_size = copy_file(handle, path); + if (size != check_size) { + errno = EINVAL; + tracecmd_warning("error in size of file '%s'", path); + set_proc_kptr_restrict(1); + ret = -1; + goto out; + } + set_proc_kptr_restrict(1); + + ret = out_compression_end(handle, compress); + if (ret) + goto out; + + ret = out_update_section_header(handle, offset); +out: + if (!ret) + handle->file_state = TRACECMD_FILE_KALLSYMS; + else + out_compression_reset(handle, compress); + return ret; +} + +static int read_ftrace_printk(struct tracecmd_output *handle, bool compress) +{ + enum tracecmd_section_flags flags = 0; + unsigned int size, check_size, endian4; + tsize_t offset; + struct stat st; + char *path; + int ret; + + if (!check_out_state(handle, TRACECMD_FILE_PRINTK)) { + tracecmd_warning("Cannot read printk, unexpected state 0x%X", + handle->file_state); + return -1; + } + + path = get_tracing_file(handle, "printk_formats"); + if (!path) + return -1; + + if (compress) + flags |= TRACECMD_SEC_FL_COMPRESS; + offset = out_write_section_header(handle, TRACECMD_OPTION_PRINTK, "printk", flags, true); + if (offset == (off64_t)-1) + return -1; + + out_compression_start(handle, compress); + ret = stat(path, &st); + if (ret < 0) { + /* not found */ + size = 0; + endian4 = convert_endian_4(handle, size); + if (do_write_check(handle, &endian4, 4)) + goto fail; + goto out; + } + size = get_size(path); + endian4 = convert_endian_4(handle, size); + if (do_write_check(handle, &endian4, 4)) + goto fail; + check_size = copy_file(handle, path); + if (size != check_size) { + errno = EINVAL; + tracecmd_warning("error in size of file '%s'", path); + goto fail; + } + + out: + put_tracing_file(path); + if (out_compression_end(handle, compress)) + return -1; + + if (out_update_section_header(handle, offset)) + return -1; + handle->file_state = TRACECMD_FILE_PRINTK; + return 0; + fail: + put_tracing_file(path); + out_compression_reset(handle, compress); + return -1; +} + +static int save_tracing_file_data(struct tracecmd_output *handle, + const char *filename) +{ + unsigned long long endian8; + char *file = NULL; + struct stat st; + off64_t check_size; + off64_t size; + int ret = -1; + + file = get_tracing_file(handle, filename); + if (!file) + return -1; + + ret = stat(file, &st); + if (ret >= 0) { + size = get_size(file); + endian8 = convert_endian_8(handle, size); + if (do_write_check(handle, &endian8, 8)) + goto out_free; + check_size = copy_file(handle, file); + if (size != check_size) { + errno = EINVAL; + tracecmd_warning("error in size of file '%s'", file); + goto out_free; + } + } else { + size = 0; + endian8 = convert_endian_8(handle, size); + if (do_write_check(handle, &endian8, 8)) + goto out_free; + } + ret = 0; + +out_free: + put_tracing_file(file); + return ret; +} + +static int write_compression_header(struct tracecmd_output *handle) +{ + const char *name = NULL; + const char *ver = NULL; + int ret; + + ret = tracecmd_compress_proto_get_name(handle->compress, &name, &ver); + if (ret < 0 || !name || !ver) { + name = "none"; + ver = ""; + } + + if (do_write_check(handle, name, strlen(name) + 1)) + return -1; + + if (do_write_check(handle, ver, strlen(ver) + 1)) + return -1; + + return 0; +} + +static int get_trace_page_size(struct tracecmd_output *handle, const char *name) +{ + struct tracefs_instance *instance; + struct tep_handle *tep = NULL; + int psize, size; + char *buff = NULL; + + /* In case of an error, return user space page size */ + psize = getpagesize(); + + instance = tracefs_instance_alloc(find_tracing_dir(handle), name); + if (!instance) + goto out; + + buff = tracefs_instance_file_read(instance, "events/header_page", &size); + if (!buff) + goto out; + + tep = tep_alloc(); + if (!tep) + goto out; + + if (tep_parse_header_page(tep, buff, size, sizeof(long long))) + goto out; + + psize = tep_get_sub_buffer_size(tep); + +out: + tracefs_instance_free(instance); + tep_free(tep); + free(buff); + + return psize; +} + +/** + * tracecmd_output_create_fd - allocate new output handle to a trace file + * @fd: File descriptor for the handle to write to. + * + * Allocate a tracecmd_output descriptor and perform minimal initialization. + * @fd will be set as the file descriptor for the handle. Nothing is + * written in the file yet, and if @fd is -1, then all writes will be ignored. + * + * Returns a pointer to a newly allocated file descriptor for the use of creating + * a tracecmd data file. In case of an error, NULL is returned. The returned + * handle must be freed with tracecmd_output_close() or tracecmd_output_free() + */ +struct tracecmd_output *tracecmd_output_create_fd(int fd) +{ + struct tracecmd_output *handle; + + handle = calloc(1, sizeof(*handle)); + if (!handle) + return NULL; + + handle->fd = fd; + + handle->file_version = FILE_VERSION_DEFAULT; + + handle->page_size = get_trace_page_size(handle, NULL); + handle->big_endian = tracecmd_host_bigendian(); + + list_head_init(&handle->options); + list_head_init(&handle->buffers); + + handle->file_state = TRACECMD_FILE_ALLOCATED; + + return handle; +} + +/** + * tracecmd_output_set_msg - associated an output file handle with network message handle + * @handle: output handle to a trace file. + * @msg_handle: network handle, allocated by tracecmd_msg_handle_alloc() + * + * Associate an output file handle (@handle) to a network stream (@msg_handle). + * All subsequent calls to @handle will send data over the network using @msg_handle + * instead of writing to a file. + * + * This must be called after the handle file version is set and before calling + * tracecmd_output_write_headers(). + * + * Returns 0 on success, or -1 if the output file handle is not allocated or not + * in the expected state. + */ +int tracecmd_output_set_msg(struct tracecmd_output *handle, struct tracecmd_msg_handle *msg_handle) +{ + if (!handle || handle->file_state != TRACECMD_FILE_ALLOCATED) + return -1; + + handle->msg_handle = msg_handle; + /* Force messages to be cached in a temp file before sending through the socket */ + if (handle->msg_handle && HAS_SECTIONS(handle)) + tracecmd_msg_handle_cache(handle->msg_handle); + + return 0; +} + +/** + * tracecmd_output_set_trace_dir - Set a custom tracing dir, instead of system default + * @handle: output handle to a trace file. + * @tracing_dir: full path to a directory with tracing files + * + * Associate the output file handle (@handle) with a custom tracing directory + * (@tracing_dir), to be used when creating the trace file instead of using the + * system default tracig directory. + * + * Must be called before tracecmd_output_write_headers(). + * + * Returns 0 on success, or -1 if the output file handle is not allocated or not + * in the expected state. + */ +int tracecmd_output_set_trace_dir(struct tracecmd_output *handle, const char *tracing_dir) +{ + if (!handle || handle->file_state != TRACECMD_FILE_ALLOCATED) + return -1; + + free(handle->tracing_dir); + if (tracing_dir) { + handle->tracing_dir = strdup(tracing_dir); + if (!handle->tracing_dir) + return -1; + } else + handle->tracing_dir = NULL; + + return 0; +} + +/** + * tracecmd_output_set_kallsyms - Set a custom kernel symbols file + * @handle: output handle to a trace file. + * @tracing_dir: full path to a file with kernel symbols + * + * Have the output file handle (@handle) use a custom kernel symbols file instead + * of the default /proc/kallsyms. + * + * Must be called before tracecmd_output_write_headers(). + * + * Returns 0 on success, or -1 if the output file handle is not allocated or + * not in the expected state. + */ +int tracecmd_output_set_kallsyms(struct tracecmd_output *handle, const char *kallsyms) +{ + if (!handle || handle->file_state != TRACECMD_FILE_ALLOCATED) + return -1; + + free(handle->kallsyms); + if (kallsyms) { + handle->kallsyms = strdup(kallsyms); + if (!handle->kallsyms) + return -1; + } else + handle->kallsyms = NULL; + + return 0; +} + +/** + * tracecmd_output_set_from_input - Inherit parameters from an existing trace file + * @handle: output handle to a trace file. + * @ihandle: input handle to an existing trace file. + * + * Have the output file handle (@handle) inherit the properties of a given + * input file handle (@ihandle). + * + * The parameters that are copied are: + * - tep handle + * - page size + * - file endian + * - file version + * - file compression protocol + * + * Must be called before tracecmd_output_write_headers(). + * + * Returns 0 on success, or -1 if the output file handle is not allocated or + * not in expected state. + */ +int tracecmd_output_set_from_input(struct tracecmd_output *handle, struct tracecmd_input *ihandle) +{ + const char *cname = NULL; + const char *cver = NULL; + + if (!handle || !ihandle || handle->file_state != TRACECMD_FILE_ALLOCATED) + return -1; + + /* get endian, page size, file version and compression */ + /* Use the pevent of the ihandle for later writes */ + handle->pevent = tracecmd_get_tep(ihandle); + tep_ref(handle->pevent); + handle->page_size = tracecmd_page_size(ihandle); + handle->file_version = tracecmd_get_in_file_version(ihandle); + handle->big_endian = tep_is_file_bigendian(handle->pevent); + + if (!tracecmd_get_file_compress_proto(ihandle, &cname, &cver)) { + handle->compress = tracecmd_compress_alloc(cname, cver, handle->fd, + handle->pevent, handle->msg_handle); + if (!handle->compress) + return -1; + + if (handle->file_version < FILE_VERSION_COMPRESSION) + handle->file_version = FILE_VERSION_COMPRESSION; + } + + return 0; +} + +/** + * tracecmd_output_set_version - Set file version of the output handle + * @handle: output handle to a trace file. + * @file_version: desired file version + * + * This API must be called before tracecmd_output_write_headers(). + * + * Returns 0 on success, or -1 if the output file handle is not allocated or not in expected state. + */ +int tracecmd_output_set_version(struct tracecmd_output *handle, int file_version) +{ + if (!handle || handle->file_state != TRACECMD_FILE_ALLOCATED) + return -1; + if (file_version < FILE_VERSION_MIN || file_version > FILE_VERSION_MAX) + return -1; + handle->file_version = file_version; + if (handle->file_version < FILE_VERSION_COMPRESSION) + handle->compress = NULL; + return 0; +} + +/** + * tracecmd_output_set_compression - Set file compression algorithm of the output handle + * @handle: output handle to a trace file. + * @compression: name of the desired compression algorithm. Can be one of: + * - "none" - do not use compression + * - "all" - use the best available compression algorithm + * - or specific name of the desired compression algorithm + * + * This API must be called before tracecmd_output_write_headers(). + * + * Returns 0 on success, or -1 in case of an error: + * - the output file handle is not allocated or not in expected state. + * - the specified compression algorithm is not available + */ +int tracecmd_output_set_compression(struct tracecmd_output *handle, const char *compression) +{ + if (!handle || handle->file_state != TRACECMD_FILE_ALLOCATED) + return -1; + + handle->compress = NULL; + if (compression && strcmp(compression, "none")) { + if (!strcmp(compression, "any")) { + handle->compress = tracecmd_compress_alloc(NULL, NULL, handle->fd, + handle->pevent, + handle->msg_handle); + if (!handle->compress) + tracecmd_warning("No compression algorithms are supported"); + } else { + handle->compress = tracecmd_compress_alloc(compression, NULL, handle->fd, + handle->pevent, + handle->msg_handle); + if (!handle->compress) { + tracecmd_warning("Compression algorithm %s is not supported", + compression); + return -1; + } + } + } + if (handle->compress && handle->file_version < FILE_VERSION_COMPRESSION) { + handle->file_version = FILE_VERSION_COMPRESSION; + if (handle->msg_handle) + tracecmd_msg_handle_cache(handle->msg_handle); + } + + return 0; +} + +/** + * output_write_init - Write the initial data into the trace file + * @handle: output handle to a trace file. + * + * Must be called after tracecmd_output_set_*() functions and before writing + * anything else. + * + * The initial information to be written into the file: + * - initial file magic bytes + * - file version + * - data endian + * - long size + * - page size + * - compression header + * + * Returns 0 on success, or -1 if the output file handle is not allocated or + * not in the expected state. + */ +static int output_write_init(struct tracecmd_output *handle) +{ + unsigned long long offset; + char buf[BUFSIZ]; + int endian4; + + if (!handle || handle->file_state != TRACECMD_FILE_ALLOCATED) + return -1; + + buf[0] = 23; + buf[1] = 8; + buf[2] = 68; + memcpy(buf + 3, "tracing", 7); + + if (do_write_check(handle, buf, 10)) + return -1; + + sprintf(buf, "%lu", handle->file_version); + if (do_write_check(handle, buf, strlen(buf) + 1)) + return -1; + + if (handle->big_endian) + buf[0] = 1; + else + buf[0] = 0; + if (do_write_check(handle, buf, 1)) + return -1; + + /* save size of long (this may not be what the kernel is) */ + buf[0] = sizeof(long); + if (do_write_check(handle, buf, 1)) + return -1; + + endian4 = convert_endian_4(handle, handle->page_size); + if (do_write_check(handle, &endian4, 4)) + return -1; + + if (handle->file_version >= FILE_VERSION_COMPRESSION) { + if (write_compression_header(handle)) + return -1; + } + + if (HAS_SECTIONS(handle)) { + /* Write 0 as options offset and save its location */ + offset = 0; + handle->options_start = do_lseek(handle, 0, SEEK_CUR); + if (do_write_check(handle, &offset, 8)) + return -1; + } + + handle->file_state = TRACECMD_FILE_INIT; + return 0; +} + +/** + * tracecmd_output_write_headers - Write the trace file headers + * @handle: output handle to a trace file. + * @list: desired events that will be included in the trace file. + * It can be NULL for all available events + * + * These headers are written in the file: + * - header files from the tracing directory + * - ftrace events from the tracing directory + * - event file from the tracing directory - all or only the one from @list + * - kernel symbols from the tracing directory + * - kernel printk strings from the tracing directory + * + * Returns 0 on success, or -1 in case of an error. + */ +int tracecmd_output_write_headers(struct tracecmd_output *handle, + struct tracecmd_event_list *list) +{ + bool compress = false; + + if (!handle || handle->file_state < TRACECMD_FILE_ALLOCATED) + return -1; + + /* Write init data, if not written yet */ + if (handle->file_state < TRACECMD_FILE_INIT && output_write_init(handle)) + return -1; + if (handle->compress) + compress = true; + if (read_header_files(handle, compress)) + return -1; + if (read_ftrace_files(handle, compress)) + return -1; + if (read_event_files(handle, list, compress)) + return -1; + if (read_proc_kallsyms(handle, compress)) + return -1; + if (read_ftrace_printk(handle, compress)) + return -1; + return 0; +} + +/** + * tracecmd_add_option_v - add options to the file + * @handle: the output file handle name + * @id: the id of the option + * @size: the size of the option data + * @data: the data to write to the file + * @vector: array of vectors, pointing to the data to write in the file + * @count: number of items in the vector array + * + * + * Returns handle to update option if needed. + * Just the content can be updated, with smaller or equal to + * content than the specified size. + */ +struct tracecmd_option * +tracecmd_add_option_v(struct tracecmd_output *handle, + unsigned short id, const struct iovec *vector, int count) + +{ + struct tracecmd_option *option; + char *data = NULL; + int i, size = 0; + + /* + * We can only add options before tracing data were written. + * This may change in the future. + */ + if (!HAS_SECTIONS(handle) && handle->file_state > TRACECMD_FILE_OPTIONS) + return NULL; + + for (i = 0; i < count; i++) + size += vector[i].iov_len; + /* Some IDs (like TRACECMD_OPTION_TRACECLOCK) pass vector with 0 / NULL data */ + if (size) { + data = malloc(size); + if (!data) { + tracecmd_warning("Insufficient memory"); + return NULL; + } + } + option = calloc(1, sizeof(*option)); + if (!option) { + tracecmd_warning("Could not allocate space for option"); + free(data); + return NULL; + } + + handle->nr_options++; + option->data = data; + for (i = 0; i < count; i++) { + if (vector[i].iov_base && vector[i].iov_len) { + memcpy(data, vector[i].iov_base, vector[i].iov_len); + data += vector[i].iov_len; + } + } + + option->size = size; + option->id = id; + + list_add_tail(&option->list, &handle->options); + + return option; +} + +/** + * tracecmd_add_option - add options to the file + * @handle: the output file handle name + * @id: the id of the option + * @size: the size of the option data + * @data: the data to write to the file + * + * Returns handle to update option if needed + * Just the content can be updated, with smaller or equal to + * content than the specified size + */ +struct tracecmd_option * +tracecmd_add_option(struct tracecmd_output *handle, + unsigned short id, int size, const void *data) +{ + struct iovec vect; + + vect.iov_base = (void *) data; + vect.iov_len = size; + return tracecmd_add_option_v(handle, id, &vect, 1); +} + +int tracecmd_write_cpus(struct tracecmd_output *handle, int cpus) +{ + int ret; + + if (!check_out_state(handle, TRACECMD_FILE_CPU_COUNT)) { + tracecmd_warning("Cannot write CPU count into the file, unexpected state 0x%X", + handle->file_state); + return -1; + } + + if (!HAS_SECTIONS(handle)) { + cpus = convert_endian_4(handle, cpus); + ret = do_write_check(handle, &cpus, 4); + if (ret < 0) + return ret; + } else { + tracecmd_add_option(handle, TRACECMD_OPTION_CPUCOUNT, sizeof(int), &cpus); + } + + handle->file_state = TRACECMD_FILE_CPU_COUNT; + return 0; +} + +static int write_options_v6(struct tracecmd_output *handle) +{ + struct tracecmd_option *options; + unsigned short option; + unsigned short endian2; + unsigned int endian4; + + /* If already written, ignore */ + if (handle->file_state == TRACECMD_FILE_OPTIONS) + return 0; + if (!check_out_state(handle, TRACECMD_FILE_OPTIONS)) { + tracecmd_warning("Cannot write options into the file, unexpected state 0x%X", + handle->file_state); + return -1; + } + + if (do_write_check(handle, "options ", 10)) + return -1; + handle->options_start = do_lseek(handle, 0, SEEK_CUR); + list_for_each_entry(options, &handle->options, list) { + endian2 = convert_endian_2(handle, options->id); + if (do_write_check(handle, &endian2, 2)) + return -1; + + endian4 = convert_endian_4(handle, options->size); + if (do_write_check(handle, &endian4, 4)) + return -1; + + /* Save the data location in case it needs to be updated */ + options->offset = do_lseek(handle, 0, SEEK_CUR); + + if (do_write_check(handle, options->data, + options->size)) + return -1; + } + + option = TRACECMD_OPTION_DONE; + + if (do_write_check(handle, &option, 2)) + return -1; + + handle->file_state = TRACECMD_FILE_OPTIONS; + return 0; +} + +static int write_options(struct tracecmd_output *handle) +{ + struct tracecmd_option *options; + unsigned long long endian8; + unsigned short endian2; + unsigned int endian4; + bool new = false; + tsize_t offset; + + /* Check if there are unsaved options */ + list_for_each_entry(options, &handle->options, list) { + if (!options->offset) { + new = true; + break; + } + } + if (!new) + return 0; + offset = do_lseek(handle, 0, SEEK_CUR); + + /* Append to the previous options section, if any */ + if (handle->options_start) { + if (do_lseek(handle, handle->options_start, SEEK_SET) == (off64_t)-1) + return -1; + endian8 = convert_endian_8(handle, offset); + if (do_write_check(handle, &endian8, 8)) + return -1; + if (do_lseek(handle, offset, SEEK_SET) == (off_t)-1) + return -1; + } + + offset = out_write_section_header(handle, TRACECMD_OPTION_DONE, "options", 0, false); + if (offset == (off_t)-1) + return -1; + + list_for_each_entry(options, &handle->options, list) { + /* Option is already saved, skip it */ + if (options->offset) + continue; + endian2 = convert_endian_2(handle, options->id); + if (do_write_check(handle, &endian2, 2)) + return -1; + endian4 = convert_endian_4(handle, options->size); + if (do_write_check(handle, &endian4, 4)) + return -1; + /* Save the data location */ + options->offset = do_lseek(handle, 0, SEEK_CUR); + if (do_write_check(handle, options->data, options->size)) + return -1; + } + + endian2 = convert_endian_2(handle, TRACECMD_OPTION_DONE); + if (do_write_check(handle, &endian2, 2)) + return -1; + endian4 = convert_endian_4(handle, 8); + if (do_write_check(handle, &endian4, 4)) + return -1; + endian8 = 0; + handle->options_start = do_lseek(handle, 0, SEEK_CUR); + if (do_write_check(handle, &endian8, 8)) + return -1; + if (out_update_section_header(handle, offset)) + return -1; + + return 0; +} + +int tracecmd_write_meta_strings(struct tracecmd_output *handle) +{ + if (!HAS_SECTIONS(handle)) + return 0; + + return save_string_section(handle, true); +} + +int tracecmd_write_options(struct tracecmd_output *handle) +{ + if (!HAS_SECTIONS(handle)) + return write_options_v6(handle); + return write_options(handle); +} + +static int append_options_v6(struct tracecmd_output *handle) +{ + struct tracecmd_option *options; + unsigned short option; + unsigned short endian2; + unsigned int endian4; + off_t offset; + int r; + + /* + * We can append only if options are already written and tracing data + * is not yet written + */ + if (handle->file_state != TRACECMD_FILE_OPTIONS) + return -1; + + if (do_lseek(handle, 0, SEEK_END) == (off_t)-1) + return -1; + offset = do_lseek(handle, -2, SEEK_CUR); + if (offset == (off_t)-1) + return -1; + + r = do_preed(handle, &option, 2, offset); + if (r != 2 || option != TRACECMD_OPTION_DONE) + return -1; + + list_for_each_entry(options, &handle->options, list) { + endian2 = convert_endian_2(handle, options->id); + if (do_write_check(handle, &endian2, 2)) + return -1; + + endian4 = convert_endian_4(handle, options->size); + if (do_write_check(handle, &endian4, 4)) + return -1; + + /* Save the data location in case it needs to be updated */ + options->offset = do_lseek(handle, 0, SEEK_CUR); + + if (do_write_check(handle, options->data, + options->size)) + return -1; + } + + option = TRACECMD_OPTION_DONE; + + if (do_write_check(handle, &option, 2)) + return -1; + + return 0; +} + +int tracecmd_append_options(struct tracecmd_output *handle) +{ + if (!HAS_SECTIONS(handle)) + return append_options_v6(handle); + return write_options(handle); +} + +static struct tracecmd_option * +add_buffer_option_v6(struct tracecmd_output *handle, const char *name, int cpus) +{ + struct tracecmd_option *option; + char *buf; + int size = 8 + strlen(name) + 1; + + buf = calloc(1, size); + if (!buf) { + tracecmd_warning("Failed to malloc buffer"); + return NULL; + } + *(tsize_t *)buf = 0; + strcpy(buf + 8, name); + + option = tracecmd_add_option(handle, TRACECMD_OPTION_BUFFER, size, buf); + free(buf); + + /* + * In case a buffer instance has different number of CPUs as the + * local machine. + */ + if (cpus) + tracecmd_add_option(handle, TRACECMD_OPTION_CPUCOUNT, + sizeof(int), &cpus); + + return option; +} + +int tracecmd_add_buffer_info(struct tracecmd_output *handle, const char *name, int cpus) +{ + struct tracecmd_buffer *buf; + + buf = calloc(1, sizeof(struct tracecmd_buffer)); + if (!buf) + return -1; + buf->name = strdup(name); + buf->cpus = cpus; + if (!buf->name) { + free(buf); + return -1; + } + list_add_tail(&buf->list, &handle->buffers); + return 0; +} + +int tracecmd_write_buffer_info(struct tracecmd_output *handle) +{ + struct tracecmd_option *option; + struct tracecmd_buffer *buf; + + if (HAS_SECTIONS(handle)) + return 0; + + list_for_each_entry(buf, &handle->buffers, list) { + option = add_buffer_option_v6(handle, buf->name, buf->cpus); + if (!option) + return -1; + buf->option = option; + } + + return 0; +} + +static tsize_t get_buffer_file_offset(struct tracecmd_output *handle, const char *name) +{ + struct tracecmd_buffer *buf; + + list_for_each_entry(buf, &handle->buffers, list) { + if (!strcmp(name, buf->name)) { + if (!buf->option) + break; + return buf->option->offset; + } + } + return 0; +} + +int tracecmd_write_cmdlines(struct tracecmd_output *handle) +{ + enum tracecmd_section_flags flags = 0; + bool compress = false; + tsize_t offset; + int ret; + + if (!check_out_state(handle, TRACECMD_FILE_CMD_LINES)) { + tracecmd_warning("Cannot write command lines into the file, unexpected state 0x%X", + handle->file_state); + return -1; + } + + if (handle->compress) + compress = true; + + if (compress) + flags |= TRACECMD_SEC_FL_COMPRESS; + offset = out_write_section_header(handle, TRACECMD_OPTION_CMDLINES, + "command lines", flags, true); + if (offset == (off64_t)-1) + return -1; + + out_compression_start(handle, compress); + + ret = save_tracing_file_data(handle, "saved_cmdlines"); + if (ret < 0) { + out_compression_reset(handle, compress); + return ret; + } + + if (out_compression_end(handle, compress)) + return -1; + + if (out_update_section_header(handle, offset)) + return -1; + + handle->file_state = TRACECMD_FILE_CMD_LINES; + return 0; +} + +static char *get_clock(struct tracecmd_output *handle) +{ + struct tracefs_instance *inst; + + if (handle->trace_clock) + return handle->trace_clock; + + /* + * If no clock is set on this handle, get the trace clock of + * the top instance in the handle's tracing dir + */ + if (!handle->tracing_dir) { + handle->trace_clock = tracefs_get_clock(NULL); + return handle->trace_clock; + } + + inst = tracefs_instance_alloc(handle->tracing_dir, NULL); + if (!inst) + return NULL; + handle->trace_clock = tracefs_get_clock(inst); + tracefs_instance_free(inst); + return handle->trace_clock; +} + +__hidden struct tracecmd_option * +out_add_buffer_option(struct tracecmd_output *handle, const char *name, + unsigned short id, unsigned long long data_offset, + int cpus, struct data_file_write *cpu_data, int page_size) +{ + struct tracecmd_option *option; + int i, j = 0, k = 0; + int *cpu_ids = NULL; + struct iovec *vect; + char *clock; + + if (!HAS_SECTIONS(handle)) + return NULL; + + clock = get_clock(handle); + if (!clock) { + tracecmd_warning("Could not find clock, set to 'local'"); + clock = "local"; + } + + /* + * Buffer flyrecord option: + * - trace data offset in the file + * - buffer name + * - buffer clock + * - page size + * - CPU count + * - for each CPU: + * - CPU id + * - CPU trace data offset in the file + * - CPU trace data size + */ + + /* + * Buffer latency option: + * - trace data offset in the file + * - buffer name + * - buffer clock + */ + + /* + * 5 : offset, name, clock, page size, count + * 3 : cpu offset, name, clock + */ + vect = calloc(5 + (cpus * 3), sizeof(struct iovec)); + if (!vect) + return NULL; + if (cpus) { + cpu_ids = calloc(cpus, sizeof(int)); + if (!cpu_ids) { + free(vect); + return NULL; + } + } + vect[j].iov_base = (void *) &data_offset; + vect[j++].iov_len = 8; + vect[j].iov_base = (void *) name; + vect[j++].iov_len = strlen(name) + 1; + vect[j].iov_base = (void *) clock; + vect[j++].iov_len = strlen(clock) + 1; + if (id == TRACECMD_OPTION_BUFFER) { + vect[j].iov_base = &page_size; + vect[j++].iov_len = 4; + vect[j].iov_base = (void *) &k; + vect[j++].iov_len = 4; + for (i = 0; i < cpus; i++) { + if (!cpu_data[i].file_size) + continue; + cpu_ids[i] = i; + vect[j].iov_base = &cpu_ids[i]; + vect[j++].iov_len = 4; + vect[j].iov_base = &cpu_data[i].data_offset; + vect[j++].iov_len = 8; + vect[j].iov_base = &cpu_data[i].write_size; + vect[j++].iov_len = 8; + k++; + } + } + + option = tracecmd_add_option_v(handle, id, vect, j); + free(vect); + free(cpu_ids); + + return option; +} + +struct tracecmd_output *tracecmd_create_file_latency(const char *output_file, int cpus, + int file_version, const char *compression) +{ + enum tracecmd_section_flags flags = 0; + struct tracecmd_output *handle; + tsize_t offset; + char *path; + + handle = tracecmd_output_create(output_file); + if (!handle) + return NULL; + + if (file_version && tracecmd_output_set_version(handle, file_version)) + goto out_free; + + if (compression) { + if (tracecmd_output_set_compression(handle, compression)) + goto out_free; + } else if (file_version >= FILE_VERSION_COMPRESSION) { + tracecmd_output_set_compression(handle, "any"); + } + + if (tracecmd_output_write_headers(handle, NULL)) + goto out_free; + /* + * Save the command lines; + */ + if (tracecmd_write_cmdlines(handle) < 0) + goto out_free; + + if (tracecmd_write_cpus(handle, cpus) < 0) + goto out_free; + if (tracecmd_write_buffer_info(handle) < 0) + goto out_free; + if (tracecmd_write_options(handle) < 0) + goto out_free; + + if (!check_out_state(handle, TRACECMD_FILE_CPU_LATENCY)) { + tracecmd_warning("Cannot write latency data into the file, unexpected state 0x%X", + handle->file_state); + goto out_free; + } + + if (!HAS_SECTIONS(handle) && do_write_check(handle, "latency ", 10)) + goto out_free; + + path = get_tracing_file(handle, "trace"); + if (!path) + goto out_free; + + offset = do_lseek(handle, 0, SEEK_CUR); + if (HAS_SECTIONS(handle) && + !out_add_buffer_option(handle, "", TRACECMD_OPTION_BUFFER_TEXT, + offset, 0, NULL, getpagesize())) + goto out_free; + if (handle->compress) + flags |= TRACECMD_SEC_FL_COMPRESS; + + offset = out_write_section_header(handle, TRACECMD_OPTION_BUFFER_TEXT, + "buffer latency", flags, false); + + copy_file_compress(handle, path, NULL); + if (out_update_section_header(handle, offset)) + goto out_free; + + put_tracing_file(path); + + handle->file_state = TRACECMD_FILE_CPU_LATENCY; + + if (HAS_SECTIONS(handle)) + tracecmd_write_options(handle); + + return handle; + +out_free: + tracecmd_output_close(handle); + return NULL; +} + +static int save_clock(struct tracecmd_output *handle, char *clock) +{ + unsigned long long endian8; + char *str = NULL; + int ret; + + ret = asprintf(&str, "[%s]", clock); + if (ret < 0) + return -1; + + endian8 = convert_endian_8(handle, strlen(str)); + ret = do_write_check(handle, &endian8, 8); + if (ret) + goto out; + ret = do_write_check(handle, str, strlen(str)); + +out: + free(str); + return ret; +} + +static int update_buffer_cpu_offset_v6(struct tracecmd_output *handle, + const char *name, tsize_t offset) +{ + tsize_t b_offset; + tsize_t current; + + if (!name) + name = ""; + + b_offset = get_buffer_file_offset(handle, name); + if (!b_offset) { + tracecmd_warning("Cannot find description for buffer %s", name); + return -1; + } + + current = do_lseek(handle, 0, SEEK_CUR); + + /* Go to the option data, where will write the offest */ + if (do_lseek(handle, b_offset, SEEK_SET) == (off64_t)-1) { + tracecmd_warning("could not seek to %lld", b_offset); + return -1; + } + + if (do_write_check(handle, &offset, 8)) + return -1; + + /* Go back to end of file */ + if (do_lseek(handle, current, SEEK_SET) == (off64_t)-1) { + tracecmd_warning("could not seek to %lld", offset); + return -1; + } + return 0; +} + +__hidden int out_write_emty_cpu_data(struct tracecmd_output *handle, int cpus) +{ + unsigned long long zero = 0; + char *clock; + int ret; + int i; + + if (HAS_SECTIONS(handle)) + return 0; + + ret = handle->file_state == TRACECMD_FILE_CPU_FLYRECORD ? 0 : + check_file_state(handle->file_version, + handle->file_state, + TRACECMD_FILE_CPU_FLYRECORD); + if (ret < 0) { + tracecmd_warning("Cannot write trace data into the file, unexpected state 0x%X", + handle->file_state); + return ret; + } + + if (do_write_check(handle, "flyrecord", 10)) + return -1; + + for (i = 0; i < cpus; i++) { + /* Write 0 for trace data offset and size */ + if (do_write_check(handle, &zero, 8)) + return -1; + + if (do_write_check(handle, &zero, 8)) + return -1; + } + clock = get_clock(handle); + if (clock && save_clock(handle, clock)) + return -1; + + handle->file_state = TRACECMD_FILE_CPU_FLYRECORD; + return 0; +} + +__hidden int out_write_cpu_data(struct tracecmd_output *handle, + int cpus, struct cpu_data_source *data, const char *buff_name) +{ + struct data_file_write *data_files = NULL; + enum tracecmd_section_flags flags = 0; + tsize_t data_offs, offset; + unsigned long long endian8; + unsigned long long read_size; + int page_size; + char *clock; + char *str; + int ret; + int i; + + /* This can be called multiple times (when recording instances) */ + ret = handle->file_state == TRACECMD_FILE_CPU_FLYRECORD ? 0 : + check_file_state(handle->file_version, + handle->file_state, + TRACECMD_FILE_CPU_FLYRECORD); + if (ret < 0) { + tracecmd_warning("Cannot write trace data into the file, unexpected state 0x%X", + handle->file_state); + goto out_free; + } + + if (*buff_name == '\0') + page_size = handle->page_size; + else + page_size = get_trace_page_size(handle, buff_name); + + data_offs = do_lseek(handle, 0, SEEK_CUR); + if (!HAS_SECTIONS(handle) && do_write_check(handle, "flyrecord", 10)) + goto out_free; + + if (handle->compress) + flags |= TRACECMD_SEC_FL_COMPRESS; + if (asprintf(&str, "buffer flyrecord %s", buff_name) < 1) + goto out_free; + offset = out_write_section_header(handle, TRACECMD_OPTION_BUFFER, str, flags, false); + free(str); + if (offset == (off_t)-1) + goto out_free; + + data_files = calloc(cpus, sizeof(*data_files)); + if (!data_files) + goto out_free; + + for (i = 0; i < cpus; i++) { + data_files[i].file_size = data[i].size; + /* + * Place 0 for the data offset and size, and save the offsets to + * updated them with the correct data later. + */ + if (!HAS_SECTIONS(handle)) { + endian8 = 0; + data_files[i].file_data_offset = do_lseek(handle, 0, SEEK_CUR); + if (do_write_check(handle, &endian8, 8)) + goto out_free; + data_files[i].file_write_size = do_lseek(handle, 0, SEEK_CUR); + if (do_write_check(handle, &endian8, 8)) + goto out_free; + } + } + + if (!HAS_SECTIONS(handle)) { + update_buffer_cpu_offset_v6(handle, buff_name, data_offs); + clock = get_clock(handle); + if (clock && save_clock(handle, clock)) + goto out_free; + } + + for (i = 0; i < cpus; i++) { + data_files[i].data_offset = do_lseek(handle, 0, SEEK_CUR); + /* Page align offset */ + data_files[i].data_offset += page_size - 1; + data_files[i].data_offset &= ~(page_size - 1); + + ret = do_lseek(handle, data_files[i].data_offset, SEEK_SET); + if (ret == (off64_t)-1) + goto out_free; + + if (!tracecmd_get_quiet(handle)) + fprintf(stderr, "CPU%d data recorded at offset=0x%llx\n", + i, (unsigned long long)data_files[i].data_offset); + + if (data[i].size) { + if (lseek64(data[i].fd, data[i].offset, SEEK_SET) == (off64_t)-1) + goto out_free; + read_size = out_copy_fd_compress(handle, data[i].fd, + data[i].size, &data_files[i].write_size, + page_size); + + if (read_size != data_files[i].file_size) { + errno = EINVAL; + tracecmd_warning("did not match size of %lld to %lld", + read_size, data_files[i].file_size); + goto out_free; + } + } else { + data_files[i].write_size = 0; + } + + if (!HAS_SECTIONS(handle)) { + /* Write the real CPU data offset in the file */ + if (do_lseek(handle, data_files[i].file_data_offset, SEEK_SET) == (off64_t)-1) + goto out_free; + endian8 = convert_endian_8(handle, data_files[i].data_offset); + if (do_write_check(handle, &endian8, 8)) + goto out_free; + /* Write the real CPU data size in the file */ + if (do_lseek(handle, data_files[i].file_write_size, SEEK_SET) == (off64_t)-1) + goto out_free; + endian8 = convert_endian_8(handle, data_files[i].write_size); + if (do_write_check(handle, &endian8, 8)) + goto out_free; + offset = data_files[i].data_offset + data_files[i].write_size; + if (do_lseek(handle, offset, SEEK_SET) == (off64_t)-1) + goto out_free; + } + if (!tracecmd_get_quiet(handle)) { + fprintf(stderr, " %llu bytes in size", + (unsigned long long)data_files[i].write_size); + if (flags & TRACECMD_SEC_FL_COMPRESS) + fprintf(stderr, " (%llu uncompressed)", + (unsigned long long)data_files[i].file_size); + fprintf(stderr, "\n"); + } + } + + if (HAS_SECTIONS(handle) && + !out_add_buffer_option(handle, buff_name, TRACECMD_OPTION_BUFFER, + data_offs, cpus, data_files, page_size)) + goto out_free; + + free(data_files); + if (do_lseek(handle, 0, SEEK_END) == (off64_t)-1) + return -1; + + if (out_update_section_header(handle, offset)) + goto out_free; + + handle->file_state = TRACECMD_FILE_CPU_FLYRECORD; + + if (HAS_SECTIONS(handle)) + tracecmd_write_options(handle); + + return 0; + + out_free: + do_lseek(handle, 0, SEEK_END); + free(data_files); + return -1; +} + +int tracecmd_write_cpu_data(struct tracecmd_output *handle, + int cpus, char * const *cpu_data_files, const char *buff_name) +{ + struct cpu_data_source *data; + struct stat st; + int size = 0; + int ret; + int i; + + if (!buff_name) + buff_name = ""; + + data = calloc(cpus, sizeof(struct cpu_data_source)); + if (!data) + return -1; + + for (i = 0; i < cpus; i++) { + ret = stat(cpu_data_files[i], &st); + if (ret < 0) { + tracecmd_warning("can not stat '%s'", cpu_data_files[i]); + break; + } + data[i].fd = open(cpu_data_files[i], O_RDONLY); + if (data[i].fd < 0) { + tracecmd_warning("Can't read '%s'", data[i].fd); + break; + } + + data[i].size = st.st_size; + data[i].offset = 0; + size += st.st_size; + } + + if (i < cpus) + ret = -1; + else + ret = out_write_cpu_data(handle, cpus, data, buff_name); + + for (i--; i >= 0; i--) + close(data[i].fd); + + free(data); + return ret; +} + +int tracecmd_append_cpu_data(struct tracecmd_output *handle, + int cpus, char * const *cpu_data_files) +{ + int ret; + + ret = tracecmd_write_cpus(handle, cpus); + if (ret) + return ret; + ret = tracecmd_write_buffer_info(handle); + if (ret) + return ret; + ret = tracecmd_write_options(handle); + if (ret) + return ret; + + return tracecmd_write_cpu_data(handle, cpus, cpu_data_files, NULL); +} + +int tracecmd_append_buffer_cpu_data(struct tracecmd_output *handle, + const char *name, int cpus, char * const *cpu_data_files) +{ + return tracecmd_write_cpu_data(handle, cpus, cpu_data_files, name); +} + +struct tracecmd_output *tracecmd_get_output_handle_fd(int fd) +{ + struct tracecmd_output *handle = NULL; + struct tracecmd_input *ihandle; + const char *cname = NULL; + const char *cver = NULL; + int fd2; + + /* Move the file descriptor to the beginning */ + if (lseek(fd, 0, SEEK_SET) == (off_t)-1) + return NULL; + + /* dup fd to be used by the ihandle bellow */ + fd2 = dup(fd); + if (fd2 < 0) + return NULL; + + /* get a input handle from this */ + ihandle = tracecmd_alloc_fd(fd2, TRACECMD_FL_LOAD_NO_PLUGINS); + if (!ihandle) + return NULL; + tracecmd_read_headers(ihandle, 0); + + /* move the file descriptor to the end */ + if (lseek(fd, 0, SEEK_END) == (off_t)-1) + goto out_free; + + /* create a partial output handle */ + handle = calloc(1, sizeof(*handle)); + if (!handle) + goto out_free; + + handle->fd = fd; + + /* get tep, state, endian and page size */ + handle->file_state = tracecmd_get_file_state(ihandle); + /* Use the tep of the ihandle for later writes */ + handle->pevent = tracecmd_get_tep(ihandle); + tep_ref(handle->pevent); + handle->page_size = tracecmd_page_size(ihandle); + handle->file_version = tracecmd_get_in_file_version(ihandle); + handle->options_start = get_last_option_offset(ihandle); + handle->strings_offs = get_meta_strings_size(ihandle); + list_head_init(&handle->options); + list_head_init(&handle->buffers); + + if (!tracecmd_get_file_compress_proto(ihandle, &cname, &cver)) { + handle->compress = tracecmd_compress_alloc(cname, cver, handle->fd, + handle->pevent, handle->msg_handle); + if (!handle->compress) + goto out_free; + } + tracecmd_close(ihandle); + + return handle; + + out_free: + tracecmd_close(ihandle); + free(handle); + return NULL; +} + +/** + * tracecmd_output_create - Create new output handle to a trace file with given name + * @output_file: Name of the trace file that will be created. + * + * The @output_file parameter can be NULL. In this case the output handle is created + * and initialized, but is not associated with a file. + * + * Returns pointer to created outpuy handle, or NULL in case of an error. + */ +struct tracecmd_output *tracecmd_output_create(const char *output_file) +{ + struct tracecmd_output *out; + int fd = -1; + + if (output_file) { + fd = open(output_file, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, 0644); + if (fd < 0) + return NULL; + } + out = tracecmd_output_create_fd(fd); + if (!out && fd >= 0) { + close(fd); + unlink(output_file); + } + + return out; +} + +/** + * tracecmd_copy - copy the headers of one trace.dat file for another + * @ihandle: input handle of the trace.dat file to copy + * @file: the trace.dat file to create + * @state: what data will be copied from the source handle + * @file_version: version of the output file + * @compression: compression of the output file, can be one of: + * NULL - inherit compression from the input file + * "any" - compress the output file with the best available algorithm + * "none" - do not compress the output file + * algorithm_name - compress the output file with specified algorithm + * + * Reads the header information and creates a new trace data file + * with the same characteristics (events and all) and returns + * tracecmd_output handle to this new file. + */ +struct tracecmd_output *tracecmd_copy(struct tracecmd_input *ihandle, const char *file, + enum tracecmd_file_states state, int file_version, + const char *compression) +{ + enum tracecmd_file_states fstate; + struct tracecmd_output *handle; + + handle = tracecmd_output_create(file); + if (!handle) + return NULL; + + if (tracecmd_output_set_from_input(handle, ihandle)) + goto out_free; + + if (file_version >= FILE_VERSION_MIN) + tracecmd_output_set_version(handle, file_version); + + if (compression && tracecmd_output_set_compression(handle, compression)) + goto out_free; + + output_write_init(handle); + fstate = state > TRACECMD_FILE_CPU_COUNT ? TRACECMD_FILE_CPU_COUNT : state; + if (tracecmd_copy_headers(ihandle, handle, 0, fstate) < 0) + goto out_free; + + if (tracecmd_copy_buffer_descr(ihandle, handle) < 0) + goto out_free; + + if (state >= TRACECMD_FILE_OPTIONS && + tracecmd_copy_options(ihandle, handle) < 0) + goto out_free; + + if (state >= TRACECMD_FILE_CPU_LATENCY && + tracecmd_copy_trace_data(ihandle, handle) < 0) + goto out_free; + + if (HAS_SECTIONS(handle)) + tracecmd_write_options(handle); + + /* The file is all ready to have cpu data attached */ + return handle; + +out_free: + if (handle) + tracecmd_output_close(handle); + + unlink(file); + return NULL; +} + +__hidden void out_set_file_state(struct tracecmd_output *handle, int new_state) +{ + handle->file_state = new_state; +} + +__hidden bool check_out_state(struct tracecmd_output *handle, int new_state) +{ + return check_file_state(handle->file_version, handle->file_state, new_state); +} + +__hidden bool out_check_compression(struct tracecmd_output *handle) +{ + return (handle->compress != NULL); +} + +__hidden int out_save_options_offset(struct tracecmd_output *handle, unsigned long long start) +{ + unsigned long long new, en8; + + if (HAS_SECTIONS(handle)) { + /* Append to the previous options section, if any */ + if (!handle->options_start) + return -1; + + new = do_lseek(handle, 0, SEEK_CUR); + if (do_lseek(handle, handle->options_start, SEEK_SET) == (off64_t)-1) + return -1; + + en8 = convert_endian_8(handle, start); + if (do_write_check(handle, &en8, 8)) + return -1; + + handle->options_start = new; + if (do_lseek(handle, new, SEEK_SET) == (off64_t)-1) + return -1; + } else { + handle->options_start = start; + } + + return 0; +} + +/** + * tracecmd_get_out_file_version - return the trace.dat file version + * @handle: output handle for the trace.dat file + */ +unsigned long tracecmd_get_out_file_version(struct tracecmd_output *handle) +{ + return handle->file_version; +} + +unsigned long long tracecmd_get_out_file_offset(struct tracecmd_output *handle) +{ + return do_lseek(handle, 0, SEEK_CUR); +} |