| // SPDX-License-Identifier: LGPL-2.1 |
| /* |
| * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> |
| * |
| */ |
| #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; |
| unsigned long long options_next; |
| 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 |
| tcmd_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 off_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 tcmd_msg_lseek(handle->msg_handle, offset, whence); |
| |
| return lseek(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 tcmd_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 tcmd_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 tcmd_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 tcmd_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_flush(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); |
| } |
| } |
| |
| void tracecmd_output_close(struct tracecmd_output *handle) |
| { |
| if (!handle) |
| return; |
| |
| tracecmd_output_flush(handle); |
| |
| 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 (tcmd_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 |
| tcmd_out_copy_fd_compress(struct tracecmd_output *handle, int fd, |
| unsigned long long max, unsigned long long *write_size, |
| int page) |
| { |
| size_t rsize = 0; |
| size_t wsize = 0; |
| size_t 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 = tcmd_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 |
| tcmd_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 (tcmd_do_write_check(handle, &endian2, 2)) |
| return (off_t)-1; |
| |
| /* Section flags */ |
| endian2 = convert_endian_2(handle, flags); |
| if (tcmd_do_write_check(handle, &endian2, 2)) |
| return (off_t)-1; |
| |
| /* Section description */ |
| if (description) |
| desc = add_string(handle, description); |
| else |
| desc = -1; |
| endian4 = convert_endian_4(handle, desc); |
| if (tcmd_do_write_check(handle, &endian4, 4)) |
| return (off_t)-1; |
| |
| offset = do_lseek(handle, 0, SEEK_CUR); |
| size = 0; |
| /* Reserve for section size */ |
| if (tcmd_do_write_check(handle, &size, 8)) |
| return (off_t)-1; |
| return offset; |
| } |
| |
| __hidden int tcmd_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) == (off_t)-1) |
| return -1; |
| |
| endian8 = convert_endian_8(handle, size); |
| if (tcmd_do_write_check(handle, &endian8, 8)) |
| return -1; |
| if (do_lseek(handle, current, SEEK_SET) == (off_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 (!tcmd_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 = tcmd_out_write_section_header(handle, TRACECMD_OPTION_STRINGS, "strings", flags, false); |
| if (offset == (off_t)-1) |
| return -1; |
| |
| tcmd_out_compression_start(handle, compress); |
| |
| if (tcmd_do_write_check(handle, handle->strings, handle->strings_p)) |
| goto error; |
| |
| if (tcmd_out_compression_end(handle, compress)) |
| goto error; |
| |
| if (tcmd_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: |
| tcmd_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 (!tcmd_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 = tcmd_out_write_section_header(handle, TRACECMD_OPTION_HEADER_INFO, |
| "headers", flags, true); |
| if (offset == (off_t)-1) { |
| put_tracing_file(path); |
| return -1; |
| } |
| |
| tcmd_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 (tcmd_do_write_check(handle, "header_page", 12)) |
| goto out_close; |
| size = 0; |
| if (tcmd_do_write_check(handle, &size, 8)) |
| goto out_close; |
| if (tcmd_do_write_check(handle, "header_event", 13)) |
| goto out_close; |
| if (tcmd_do_write_check(handle, &size, 8)) |
| goto out_close; |
| if (tcmd_out_compression_end(handle, compress)) |
| goto out_close; |
| if (tcmd_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_free; |
| } |
| |
| /* unfortunately, you can not stat debugfs files for size */ |
| size = get_size_fd(fd); |
| |
| if (tcmd_do_write_check(handle, "header_page", 12)) |
| goto out_free; |
| endian8 = convert_endian_8(handle, size); |
| if (tcmd_do_write_check(handle, &endian8, 8)) |
| goto out_free; |
| check_size = copy_file_fd(handle, fd, 0); |
| if (size != check_size) { |
| tracecmd_warning("wrong size for '%s' size=%lld read=%lld", path, size, check_size); |
| errno = EINVAL; |
| goto out_free; |
| } |
| put_tracing_file(path); |
| |
| path = get_tracing_file(handle, "events/header_event"); |
| if (!path) |
| goto out_close; |
| |
| close(fd); |
| fd = open(path, O_RDONLY); |
| if (fd < 0) { |
| tracecmd_warning("can't read '%s'", path); |
| goto out_free; |
| } |
| |
| size = get_size_fd(fd); |
| |
| if (tcmd_do_write_check(handle, "header_event", 13)) |
| goto out_free; |
| endian8 = convert_endian_8(handle, size); |
| if (tcmd_do_write_check(handle, &endian8, 8)) |
| goto out_free; |
| check_size = copy_file_fd(handle, fd, 0); |
| close(fd); |
| if (size != check_size) { |
| tracecmd_warning("wrong size for '%s'", path); |
| goto out_free; |
| } |
| put_tracing_file(path); |
| if (tcmd_out_compression_end(handle, compress)) |
| goto out_close; |
| |
| if (tcmd_out_update_section_header(handle, offset)) |
| goto out_close; |
| handle->file_state = TRACECMD_FILE_HEADERS; |
| |
| return 0; |
| |
| out_free: |
| put_tracing_file(path); |
| out_close: |
| tcmd_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 (tcmd_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 (tcmd_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) { |
| put_tracing_file(events_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 (!tcmd_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 = tcmd_out_write_section_header(handle, TRACECMD_OPTION_FTRACE_EVENTS, |
| "ftrace events", flags, true); |
| if (offset == (off_t)-1) |
| return -1; |
| |
| create_event_list_item(handle, &systems, &list); |
| tcmd_out_compression_start(handle, compress); |
| |
| ret = copy_event_system(handle, systems); |
| if (!ret) |
| ret = tcmd_out_compression_end(handle, compress); |
| else |
| tcmd_out_compression_reset(handle, compress); |
| |
| free_list_events(systems); |
| if (ret) |
| return ret; |
| if (tcmd_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 (!tcmd_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 = tcmd_out_write_section_header(handle, TRACECMD_OPTION_EVENT_FORMATS, |
| "events format", flags, true); |
| if (offset == (off_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++; |
| tcmd_out_compression_start(handle, compress); |
| ret = -1; |
| endian4 = convert_endian_4(handle, count); |
| if (tcmd_do_write_check(handle, &endian4, 4)) |
| goto out_free; |
| |
| ret = 0; |
| for (slist = systems; !ret && slist; slist = slist->next) { |
| if (tcmd_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 = tcmd_out_compression_end(handle, compress); |
| if (ret) |
| goto out_free; |
| ret = tcmd_out_update_section_header(handle, offset); |
| |
| out_free: |
| if (!ret) |
| handle->file_state = TRACECMD_FILE_ALL_EVENTS; |
| else |
| tcmd_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 (!tcmd_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 = tcmd_out_write_section_header(handle, TRACECMD_OPTION_KALLSYMS, |
| "kallsyms", flags, true); |
| if (offset == (off_t)-1) |
| return -1; |
| |
| tcmd_out_compression_start(handle, compress); |
| ret = stat(path, &st); |
| if (ret < 0) { |
| /* not found */ |
| size = 0; |
| endian4 = convert_endian_4(handle, size); |
| ret = tcmd_do_write_check(handle, &endian4, 4); |
| goto out; |
| } |
| size = get_size(path); |
| endian4 = convert_endian_4(handle, size); |
| ret = tcmd_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 = tcmd_out_compression_end(handle, compress); |
| if (ret) |
| goto out; |
| |
| ret = tcmd_out_update_section_header(handle, offset); |
| out: |
| if (!ret) |
| handle->file_state = TRACECMD_FILE_KALLSYMS; |
| else |
| tcmd_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 (!tcmd_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 = tcmd_out_write_section_header(handle, TRACECMD_OPTION_PRINTK, "printk", flags, true); |
| if (offset == (off_t)-1) { |
| put_tracing_file(path); |
| return -1; |
| } |
| |
| tcmd_out_compression_start(handle, compress); |
| ret = stat(path, &st); |
| if (ret < 0) { |
| /* not found */ |
| size = 0; |
| endian4 = convert_endian_4(handle, size); |
| if (tcmd_do_write_check(handle, &endian4, 4)) |
| goto fail; |
| goto out; |
| } |
| size = get_size(path); |
| endian4 = convert_endian_4(handle, size); |
| if (tcmd_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 (tcmd_out_compression_end(handle, compress)) |
| return -1; |
| |
| if (tcmd_out_update_section_header(handle, offset)) |
| return -1; |
| handle->file_state = TRACECMD_FILE_PRINTK; |
| return 0; |
| fail: |
| put_tracing_file(path); |
| tcmd_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; |
| off_t check_size; |
| off_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 (tcmd_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 (tcmd_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 (tcmd_do_write_check(handle, name, strlen(name) + 1)) |
| return -1; |
| |
| if (tcmd_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 (tcmd_do_write_check(handle, buf, 10)) |
| return -1; |
| |
| sprintf(buf, "%lu", handle->file_version); |
| if (tcmd_do_write_check(handle, buf, strlen(buf) + 1)) |
| return -1; |
| |
| if (handle->big_endian) |
| buf[0] = 1; |
| else |
| buf[0] = 0; |
| if (tcmd_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 (tcmd_do_write_check(handle, buf, 1)) |
| return -1; |
| |
| endian4 = convert_endian_4(handle, handle->page_size); |
| if (tcmd_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 (tcmd_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 (!tcmd_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 = tcmd_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 (!tcmd_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 (tcmd_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 (tcmd_do_write_check(handle, &endian2, 2)) |
| return -1; |
| |
| endian4 = convert_endian_4(handle, options->size); |
| if (tcmd_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 (tcmd_do_write_check(handle, options->data, |
| options->size)) |
| return -1; |
| } |
| |
| option = TRACECMD_OPTION_DONE; |
| |
| if (tcmd_do_write_check(handle, &option, 2)) |
| return -1; |
| |
| handle->file_state = TRACECMD_FILE_OPTIONS; |
| return 0; |
| } |
| |
| static int update_options_start(struct tracecmd_output *handle, off_t offset) |
| { |
| if (do_lseek(handle, handle->options_start, SEEK_SET) == (off_t)-1) |
| return -1; |
| offset = convert_endian_8(handle, offset); |
| if (tcmd_do_write_check(handle, &offset, 8)) |
| return -1; |
| return 0; |
| } |
| |
| /** |
| * tracecmd_pepare_options - perpare a previous options for the next |
| * @handle: The handle to update the options for. |
| * @offset: The offset to set the previous options to. |
| * @whence: Where in the file to offset from. |
| * |
| * In a case of cached writes for network access, the options offset |
| * cannot be written once it goes over the network. This is used |
| * to update the next options to a known location. |
| * |
| * tracecmd_write_options() must be called when the offset is at the next |
| * location, otherwise the data file will end up corrupted. |
| * |
| * Returns zero on success and -1 on error. |
| */ |
| int tracecmd_prepare_options(struct tracecmd_output *handle, off_t offset, int whence) |
| { |
| tsize_t curr; |
| int ret; |
| |
| /* No options to start with? */ |
| if (!handle->options_start) |
| return 0; |
| |
| curr = do_lseek(handle, 0, SEEK_CUR); |
| |
| switch (whence) { |
| case SEEK_SET: |
| /* just use offset */ |
| break; |
| case SEEK_CUR: |
| offset += curr; |
| break; |
| case SEEK_END: |
| offset = do_lseek(handle, offset, SEEK_END); |
| if (offset == (off_t)-1) |
| return -1; |
| break; |
| } |
| ret = update_options_start(handle, offset); |
| if (ret < 0) |
| return -1; |
| |
| handle->options_next = offset; |
| |
| curr = do_lseek(handle, curr, SEEK_SET); |
| |
| return curr == -1 ? -1 : 0; |
| } |
| |
| static tsize_t write_options_start(struct tracecmd_output *handle) |
| { |
| tsize_t offset; |
| int ret; |
| |
| offset = do_lseek(handle, 0, SEEK_CUR); |
| |
| if (handle->options_next) { |
| /* options_start was already updated */ |
| if (handle->options_next != offset) { |
| tracecmd_warning("Options offset (%lld) does not match expected (%lld)", |
| offset, handle->options_next); |
| return -1; |
| } |
| handle->options_next = 0; |
| /* Will be updated at the end */ |
| handle->options_start = 0; |
| } |
| |
| /* Append to the previous options section, if any */ |
| if (handle->options_start) { |
| ret = update_options_start(handle, offset); |
| if (ret < 0) |
| return -1; |
| offset = do_lseek(handle, offset, SEEK_SET); |
| if (offset == (off_t)-1) |
| return -1; |
| } |
| |
| return tcmd_out_write_section_header(handle, TRACECMD_OPTION_DONE, "options", 0, false); |
| } |
| |
| static tsize_t write_options_end(struct tracecmd_output *handle, tsize_t offset) |
| { |
| unsigned long long endian8; |
| unsigned short endian2; |
| unsigned int endian4; |
| |
| endian2 = convert_endian_2(handle, TRACECMD_OPTION_DONE); |
| if (tcmd_do_write_check(handle, &endian2, 2)) |
| return -1; |
| endian4 = convert_endian_4(handle, 8); |
| if (tcmd_do_write_check(handle, &endian4, 4)) |
| return -1; |
| endian8 = 0; |
| handle->options_start = do_lseek(handle, 0, SEEK_CUR); |
| if (tcmd_do_write_check(handle, &endian8, 8)) |
| return -1; |
| if (tcmd_out_update_section_header(handle, offset)) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int write_options(struct tracecmd_output *handle) |
| { |
| struct tracecmd_option *options; |
| 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; |
| } |
| } |
| /* |
| * Even if there are no new options, if options_next is set, it requires |
| * adding a new empty options section as the previous one already |
| * points to it. |
| */ |
| if (!new && !handle->options_next) |
| return 0; |
| |
| offset = write_options_start(handle); |
| 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 (tcmd_do_write_check(handle, &endian2, 2)) |
| return -1; |
| endian4 = convert_endian_4(handle, options->size); |
| if (tcmd_do_write_check(handle, &endian4, 4)) |
| return -1; |
| /* Save the data location */ |
| options->offset = do_lseek(handle, 0, SEEK_CUR); |
| if (tcmd_do_write_check(handle, options->data, options->size)) |
| return -1; |
| } |
| |
| return write_options_end(handle, offset); |
| } |
| |
| /** |
| * tcmd_get_options - Get the current options from the output file handle |
| * @handle: The output file descriptor that has options. |
| * @len: Returns the length of the buffer allocated and returned. |
| * |
| * Reads the options that have not been written to the file yet, |
| * puts them into an allocated buffer and sets @len to the size |
| * added. Used by trace-msg.c to send options over the network. |
| * |
| * Note, the options cannot be referenced again once this is called. |
| * New options can be added and referenced. |
| * |
| * Returns an allocated buffer (must be freed with free()) that contains |
| * the options to send, with @len set to the size of the content. |
| * NULL on error (and @len is undefined). |
| */ |
| __hidden void *tcmd_get_options(struct tracecmd_output *handle, size_t *len) |
| { |
| struct tracecmd_msg_handle msg_handle; |
| struct tracecmd_output out_handle; |
| struct tracecmd_option *options; |
| unsigned short endian2; |
| unsigned int endian4; |
| tsize_t offset; |
| void *buf = NULL; |
| |
| /* Use the msg_cache as our output */ |
| memset(&msg_handle, 0, sizeof(msg_handle)); |
| msg_handle.cfd = -1; |
| if (tracecmd_msg_handle_cache(&msg_handle) < 0) |
| return NULL; |
| |
| out_handle = *handle; |
| out_handle.fd = msg_handle.cfd; |
| out_handle.msg_handle = &msg_handle; |
| |
| 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 (tcmd_do_write_check(&out_handle, &endian2, 2)) |
| goto out; |
| endian4 = convert_endian_4(handle, options->size); |
| if (tcmd_do_write_check(&out_handle, &endian4, 4)) |
| goto out; |
| /* The option can not be referenced again */ |
| options->offset = -1; |
| if (tcmd_do_write_check(&out_handle, options->data, options->size)) |
| goto out; |
| } |
| |
| offset = do_lseek(&out_handle, 0, SEEK_CUR); |
| if (offset == (off_t)-1) |
| goto out; |
| buf = malloc(offset); |
| if (!buf) |
| goto out; |
| |
| if (do_lseek(&out_handle, 0, SEEK_SET) == (off_t)-1) |
| goto out; |
| *len = read(msg_handle.cfd, buf, offset); |
| if (*len != offset) { |
| free(buf); |
| buf = NULL; |
| } |
| |
| out: |
| close(msg_handle.cfd); |
| return buf; |
| } |
| |
| /** |
| * tcmd_append_options - Append options to the file |
| * @handle: The output file descriptor that has options. |
| * @buf: The options to append. |
| * @len: The length of @buf. |
| * |
| * Will add an options section header for the content of @buf to |
| * be written as options into the @handle. |
| * Used by trace-msg.c to retrieve options over the network. |
| * |
| * Returns 0 on success and -1 on error. |
| */ |
| __hidden int tcmd_append_options(struct tracecmd_output *handle, void *buf, |
| size_t len) |
| { |
| tsize_t offset; |
| |
| offset = write_options_start(handle); |
| if (offset == (off_t)-1) |
| return -1; |
| |
| if (tcmd_do_write_check(handle, buf, len)) |
| return -1; |
| |
| return write_options_end(handle, offset); |
| } |
| |
| 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 (tcmd_do_write_check(handle, &endian2, 2)) |
| return -1; |
| |
| endian4 = convert_endian_4(handle, options->size); |
| if (tcmd_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 (tcmd_do_write_check(handle, options->data, |
| options->size)) |
| return -1; |
| } |
| |
| option = TRACECMD_OPTION_DONE; |
| |
| if (tcmd_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 (!tcmd_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 = tcmd_out_write_section_header(handle, TRACECMD_OPTION_CMDLINES, |
| "command lines", flags, true); |
| if (offset == (off_t)-1) |
| return -1; |
| |
| tcmd_out_compression_start(handle, compress); |
| |
| ret = save_tracing_file_data(handle, "saved_cmdlines"); |
| if (ret < 0) { |
| tcmd_out_compression_reset(handle, compress); |
| return ret; |
| } |
| |
| if (tcmd_out_compression_end(handle, compress)) |
| return -1; |
| |
| if (tcmd_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 * |
| tcmd_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 (!tcmd_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) && tcmd_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) && |
| !tcmd_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 = tcmd_out_write_section_header(handle, TRACECMD_OPTION_BUFFER_TEXT, |
| "buffer latency", flags, false); |
| |
| copy_file_compress(handle, path, NULL); |
| if (tcmd_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 = tcmd_do_write_check(handle, &endian8, 8); |
| if (ret) |
| goto out; |
| ret = tcmd_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) == (off_t)-1) { |
| tracecmd_warning("could not seek to %lld", b_offset); |
| return -1; |
| } |
| |
| if (tcmd_do_write_check(handle, &offset, 8)) |
| return -1; |
| |
| /* Go back to end of file */ |
| if (do_lseek(handle, current, SEEK_SET) == (off_t)-1) { |
| tracecmd_warning("could not seek to %lld", offset); |
| return -1; |
| } |
| return 0; |
| } |
| |
| __hidden int tcmd_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 : |
| tcmd_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 (tcmd_do_write_check(handle, "flyrecord", 10)) |
| return -1; |
| |
| for (i = 0; i < cpus; i++) { |
| /* Write 0 for trace data offset and size */ |
| if (tcmd_do_write_check(handle, &zero, 8)) |
| return -1; |
| |
| if (tcmd_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 tcmd_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 : |
| tcmd_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) && tcmd_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 = tcmd_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 (tcmd_do_write_check(handle, &endian8, 8)) |
| goto out_free; |
| data_files[i].file_write_size = do_lseek(handle, 0, SEEK_CUR); |
| if (tcmd_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 == (off_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 (lseek(data[i].fd, data[i].offset, SEEK_SET) == (off_t)-1) |
| goto out_free; |
| read_size = tcmd_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 %llu to %llu", |
| 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) == (off_t)-1) |
| goto out_free; |
| endian8 = convert_endian_8(handle, data_files[i].data_offset); |
| if (tcmd_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) == (off_t)-1) |
| goto out_free; |
| endian8 = convert_endian_8(handle, data_files[i].write_size); |
| if (tcmd_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) == (off_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) && |
| !tcmd_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) == (off_t)-1) |
| return -1; |
| |
| if (tcmd_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 = tcmd_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 = tcmd_get_last_option_offset(ihandle); |
| handle->strings_offs = tcmd_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 && |
| tcmd_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 tcmd_out_set_file_state(struct tracecmd_output *handle, int new_state) |
| { |
| handle->file_state = new_state; |
| } |
| |
| __hidden bool tcmd_check_out_state(struct tracecmd_output *handle, int new_state) |
| { |
| return tcmd_check_file_state(handle->file_version, handle->file_state, new_state); |
| } |
| |
| __hidden bool tcmd_out_check_compression(struct tracecmd_output *handle) |
| { |
| return (handle->compress != NULL); |
| } |
| |
| __hidden int tcmd_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) == (off_t)-1) |
| return -1; |
| |
| en8 = convert_endian_8(handle, start); |
| if (tcmd_do_write_check(handle, &en8, 8)) |
| return -1; |
| |
| handle->options_start = new; |
| if (do_lseek(handle, new, SEEK_SET) == (off_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; |
| } |
| |
| size_t tracecmd_get_out_file_offset(struct tracecmd_output *handle) |
| { |
| return do_lseek(handle, 0, SEEK_CUR); |
| } |